/* ============================================================ Screens — Part 1 Attract → Identify → Student Login → 2FA → Guest RF → Dues → Amount → Payment Method ============================================================ */ /* ============================================================ ATTRACT — fullscreen, touch-to-start ============================================================ */ const ScreenAttract = () => { const t = useT(); const { go, setLang } = window.AppState; const [tick, setTick] = React.useState(0); React.useEffect(() => { const i = setInterval(() => setTick(x => x + 1), 1000); return () => clearInterval(i); }, []); return (
go('identify')}>
{t.kioskName}

{window.AppState.lang === 'el' ? 'Πληρώστε εύκολα,\nάμεσα,\nμε ασφάλεια.' : 'Pay easily,\ninstantly,\nsecurely.'}

{t.touchToStart}
e.stopPropagation()} style={{ display: 'flex', gap: 16 }}>
{t.secured} v1.0 · MC Athens · Akadimias 42
); }; window.ScreenAttract = ScreenAttract; /* ============================================================ IDENTIFY — pick student or guest path ============================================================ */ const ScreenIdentify = () => { const t = useT(); const { go } = window.AppState; return (

{t.welcome}

{t.welcomeSub}

{ window.AppState.payerType = 'student'; go('studentLogin'); }} /> { window.AppState.payerType = 'guest'; go('guestScan'); }} />
{t.help}
{t.helpDesc}
); }; window.ScreenIdentify = ScreenIdentify; /* ============================================================ STUDENT LOGIN ============================================================ */ const ScreenStudentLogin = () => { const t = useT(); const { go, back } = window.AppState; const [u, setU] = React.useState(''); const [p, setP] = React.useState(''); const [focus, setFocus] = React.useState('u'); const [showP, setShowP] = React.useState(false); const onKey = (k) => { if (focus === 'u') setU(x => (x + k).slice(0, 32)); else setP(x => (x + k).slice(0, 32)); }; const onDel = () => focus === 'u' ? setU(x => x.slice(0,-1)) : setP(x => x.slice(0,-1)); const onClr = () => focus === 'u' ? setU('') : setP(''); const submit = () => { if (u.length >= 3 && p.length >= 3) go('twoFA'); }; const fieldCls = (active) => ({ background: active ? 'var(--n-0)' : 'var(--n-50)', border: active ? '3px solid var(--mc-ink)' : '3px solid var(--n-200)', borderRadius: 16, padding: '24px 28px', minHeight: 100, display: 'flex', alignItems: 'center', cursor: 'pointer', fontSize: 32, fontFamily: 'var(--font-mono)', fontWeight: 500, }); return (
go('cancelConfirm')} />

{t.studentLogin}

{t.studentLoginSub}

{t.username}
setFocus('u')}> {u || 'student.name'} {focus === 'u' && }
{t.password}
setFocus('p')}> {p ? (showP ? p : '•'.repeat(p.length)) : '••••••••'}
{window.AppState.lang === 'el' ? 'Χρησιμοποιήστε τα ίδια στοιχεία που χρησιμοποιείτε στο Classter portal.' : 'Use the same credentials as your Classter portal.'}
= 3 && p.length >= 3} />
{window.AppState.lang === 'el' ? 'Πληκτρολογήστε αριθμούς. Για γράμματα χρησιμοποιήστε το πληκτρολόγιο της οθόνης.' : 'Type numbers here. For letters use the on-screen keyboard.'}
); }; window.ScreenStudentLogin = ScreenStudentLogin; /* ============================================================ TWO-FACTOR (MS Authenticator) ============================================================ */ const ScreenTwoFA = () => { const t = useT(); const { go, back } = window.AppState; const [code, setCode] = React.useState(''); const submit = () => { if (code.length === 6) go('dues'); }; return (
go('cancelConfirm')} />

{t.twoFA}

{t.twoFASub}

{t.code6}
{Array.from({ length: 6 }).map((_, i) => (
{code[i] || ''}
))}
Microsoft Authenticator
{window.AppState.lang === 'el' ? 'Ελέγξτε ειδοποιήσεις στην εφαρμογή σας.' : 'Check the notifications in your app.'}
setCode(c => (c + k).slice(0, 6))} onDelete={() => setCode(c => c.slice(0, -1))} onClear={() => setCode('')} onSubmit={submit} submitLabel={t.continue} canSubmit={code.length === 6} />
); }; window.ScreenTwoFA = ScreenTwoFA; /* ============================================================ GUEST SCAN — RF code ============================================================ */ const ScreenGuestScan = () => { const t = useT(); const { go, back } = window.AppState; const [manual, setManual] = React.useState(false); const [rf, setRf] = React.useState(''); const [scanning, setScanning] = React.useState(true); const [error, setError] = React.useState(false); // Simulate auto-scan: 3.5s after entering, an RF appears (unless user toggles to manual) React.useEffect(() => { if (manual) return; const t1 = setTimeout(() => { setScanning(false); setRf('RF18 5390 0000 4321 0987 65432'); setTimeout(() => go('dues'), 1200); }, 3500); return () => clearTimeout(t1); }, [manual]); const submitManual = () => { if (rf.length < 23) { setError(true); return; } if (rf.startsWith('99')) { go('invalidRF'); return; } go('dues'); }; return (
go('cancelConfirm')} />

{t.guestScan}

{t.guestScanSub}

{!manual ? (
{scanning ? (<> {window.AppState.lang === 'el' ? 'Σάρωση…' : 'Scanning…'}) : (<> {t.rfFound} {rf})}
) : (
{t.rfPlaceholder}
{rf || RF__ ____ ____ ____ ____ _____} {!error && }
{error &&
{window.AppState.lang === 'el' ? 'Συμπληρώστε όλα τα ψηφία.' : 'Please complete all digits.'}
}
{ setError(false); setRf(r => (r + k).slice(0, 27)); }} onDelete={() => setRf(r => r.slice(0, -1))} onClear={() => setRf('')} onSubmit={submitManual} submitLabel={t.continue} canSubmit={rf.length >= 23} />
{window.AppState.lang === 'el' ? 'Συμβουλή: για την demo πληκτρολογήστε «99» στην αρχή για άκυρο RF.' : 'Demo tip: type "99" first for invalid RF.'}
)}
); }; window.ScreenGuestScan = ScreenGuestScan; /* ============================================================ DUES list ============================================================ */ const SAMPLE_DUES = [ { id: 'd1', rf: 'RF18 5390 0000 4321 0987 65432', program: { el: 'BSc Πληροφορική (3ο έτος)', en: 'BSc Computer Science (Year 3)' }, annualBalance: 4250, overdue: 1450, dueDate: '15/05/2026' }, { id: 'd2', rf: 'RF55 7821 0001 9988 7766 12340', program: { el: 'Σεμινάριο Διοίκησης Έργων', en: 'Project Management Seminar' }, annualBalance: 800, overdue: 0, dueDate: '30/06/2026' }, ]; const ScreenDues = () => { const t = useT(); const lang = window.AppState.lang; const { go, back } = window.AppState; const [sel, setSel] = React.useState(SAMPLE_DUES[0].id); const choose = () => { const d = SAMPLE_DUES.find(x => x.id === sel); window.AppState.selectedDue = d; go('amount'); }; return (
go('cancelConfirm')} />

{t.yourDues}

{t.yourDuesSub}

{SAMPLE_DUES.map(d => ( ))}
); }; window.ScreenDues = ScreenDues; /* ============================================================ AMOUNT selection ============================================================ */ const ScreenAmount = () => { const t = useT(); const lang = window.AppState.lang; const { go, back } = window.AppState; const due = window.AppState.selectedDue || SAMPLE_DUES[0]; const [mode, setMode] = React.useState(due.overdue > 0 ? 'overdue' : 'installment'); const [custom, setCustom] = React.useState(''); const installment = Math.round(due.annualBalance / 4); const amount = mode === 'overdue' ? due.overdue : mode === 'installment' ? installment : Number(custom) || 0; const valid = amount >= 10 && amount <= due.annualBalance; const proceed = () => { window.AppState.amount = amount; go('payMethod'); }; const tile = (key, title, sub, value) => ( ); return (
go('cancelConfirm')} />

{t.amountTitle}

{t.amountSub}

{lang === 'el' ? 'Επιλεγμένη οφειλή' : 'Selected obligation'}
{due.program[lang]}
{t.annualBalance}
{fmtEUR(due.annualBalance, lang)}
{due.overdue > 0 && tile('overdue', t.payOverdue, lang === 'el' ? 'Εξόφληση όλου του ληξιπρόθεσμου ποσού' : 'Settle all overdue', fmtEUR(due.overdue, lang))} {tile('installment', t.payInstallment, lang === 'el' ? 'Πληρωμή 1/4 του ετήσιου' : 'Pay one quarter', fmtEUR(installment, lang))} {tile('custom', t.payCustom, t.minAmount, custom ? fmtEUR(Number(custom), lang) : '—')}
{mode === 'custom' && (
{t.customAmount}
{custom || '0'}
{t.minAmount} · {t.maxAmount} · {fmtEUR(due.annualBalance, lang)}
setCustom(c => (c + k).slice(0, 6).replace(/^0+/, '') || k)} onDelete={() => setCustom(c => c.slice(0, -1))} onClear={() => setCustom('')} onSubmit={proceed} submitLabel={t.continue} canSubmit={valid} />
)}
{mode !== 'custom' && (
)}
); }; window.ScreenAmount = ScreenAmount; /* ============================================================ PAYMENT METHOD selection ============================================================ */ const ScreenPayMethod = () => { const t = useT(); const lang = window.AppState.lang; const { go, back } = window.AppState; const amount = window.AppState.amount || 0; const choose = (m) => { window.AppState.method = m; if (m === 'card') go('cardPay'); else if (m === 'iris') go('irisPay'); else if (m === 'cash') go('cashPay'); else if (m === 'mix') go('mixIntro'); }; return (
go('cancelConfirm')} />

{t.payMethod}

{t.payMethodSub}

{lang === 'el' ? 'Ποσό προς πληρωμή' : 'Amount to pay'}
{fmtEUR(amount, lang)}
choose('card')} /> choose('iris')} /> choose('cash')} /> choose('mix')} badge={lang === 'el' ? 'Νέο' : 'New'} />
); }; window.ScreenPayMethod = ScreenPayMethod;