/* ============================================================ Screens — Part 2 Card / IRIS / Cash / Mix payment screens Confirmation, processing, success, receipt All edge cases ============================================================ */ /* ============================================================ CARD payment ============================================================ */ const ScreenCardPay = () => { const t = useT(); const lang = window.AppState.lang; const { go, back } = window.AppState; const amount = window.AppState.amount || 0; const [stage, setStage] = React.useState('insert'); // insert | processing React.useEffect(() => { if (stage === 'insert') { const t1 = setTimeout(() => setStage('processing'), 4000); return () => clearTimeout(t1); } if (stage === 'processing') { const t1 = setTimeout(() => { // 90% success, 10% fail (deterministic via demo flag) if (window.AppState.forceCardDecline) { window.AppState.forceCardDecline = false; go('cardDeclined'); } else go('success'); }, 2400); return () => clearTimeout(t1); } }, [stage]); return (
go('cancelConfirm')} />

{stage === 'insert' ? t.cardInsert : t.cardProcessing}

{stage === 'insert' ? t.cardInsertSub : t.cardProcessingSub}

PAX IM30
{lang === 'el' ? 'Ποσό' : 'Amount'}
{fmtEUR(amount, lang)}
{stage === 'processing' &&
{t.cardProcessing}
}
Visa · Mastercard · Maestro · Apple Pay · Google Pay
); }; window.ScreenCardPay = ScreenCardPay; /* ============================================================ IRIS payment ============================================================ */ const ScreenIrisPay = () => { const t = useT(); const lang = window.AppState.lang; const { go, back } = window.AppState; const amount = window.AppState.amount || 0; const [secs, setSecs] = React.useState(180); const [seed, setSeed] = React.useState('IRIS-' + Date.now()); React.useEffect(() => { if (secs <= 0) { go('irisExpired'); return; } const t1 = setTimeout(() => setSecs(s => s - 1), 1000); return () => clearTimeout(t1); }, [secs]); const mins = Math.floor(secs / 60), ss = String(secs % 60).padStart(2,'0'); return (
go('cancelConfirm')} />

{t.irisScan}

{t.irisScanSub}

{t.irisExpiresIn}{mins}:{ss}
{t.amount} {fmtEUR(amount, lang)}
); }; window.ScreenIrisPay = ScreenIrisPay; /* ============================================================ CASH payment ============================================================ */ const ScreenCashPay = () => { const t = useT(); const lang = window.AppState.lang; const { go, back } = window.AppState; const amount = window.AppState.amount || 0; const [inserted, setInserted] = React.useState(0); const remaining = Math.max(0, amount - inserted); const change = Math.max(0, inserted - amount); const denoms = [5, 10, 20, 50, 100]; const insert = (v) => setInserted(i => i + v); return (
go('cancelConfirm')} />

{t.cashInsert}

{t.cashInsertSub}

{/* Live counter */}
{t.cashInserted}
{fmtEUR(inserted, lang)}
{t.amount}
{fmtEUR(amount, lang)}
{remaining > 0 ? (
{t.cashRemaining}
{fmtEUR(remaining, lang)}
) : (
{t.cashChange}
{fmtEUR(change, lang)}
)}
{/* "Slot" visual */}
{lang === 'el' ? 'Σχισμή χαρτονομισμάτων Cashmatic VP27' : 'Cashmatic VP27 banknote slot'}
{/* Demo denominations */}
{lang === 'el' ? 'Demo: εισαγωγή χαρτονομίσματος' : 'Demo: insert banknote'}
{denoms.map(v => ( ))}
); }; window.ScreenCashPay = ScreenCashPay; /* ============================================================ MIX payment — intro + cash step + electronic step ============================================================ */ const ScreenMixIntro = () => { const t = useT(); const lang = window.AppState.lang; const { go, back } = window.AppState; const amount = window.AppState.amount || 0; const [cashPart, setCashPart] = React.useState(String(Math.round(amount / 2))); const cashAmt = Math.min(amount, Number(cashPart) || 0); const remaining = amount - cashAmt; const proceed = () => { window.AppState.mixCash = cashAmt; window.AppState.mixRemaining = remaining; go('mixCash'); }; return (
go('cancelConfirm')} />

{t.mixIntro}

{t.mixIntroSub}

{t.amount}
{fmtEUR(amount, lang)}
{lang === 'el' ? 'Ποσό σε μετρητά' : 'Cash portion'}
{cashPart || '0'}
{lang === 'el' ? 'Υπόλοιπο με κάρτα/IRIS' : 'Remainder by card/IRIS'}
{fmtEUR(remaining, lang)}
setCashPart(c => (c + k).slice(0, 5).replace(/^0+/,'') || k)} onDelete={() => setCashPart(c => c.slice(0,-1))} onClear={() => setCashPart('')} onSubmit={proceed} submitLabel={t.continue} canSubmit={cashAmt >= 1 && remaining >= 1} />
); }; window.ScreenMixIntro = ScreenMixIntro; const ScreenMixCash = () => { const t = useT(); const lang = window.AppState.lang; const { go } = window.AppState; const cashAmt = window.AppState.mixCash || 0; const [inserted, setInserted] = React.useState(0); const remaining = Math.max(0, cashAmt - inserted); return (
go('cancelConfirm')} />
{t.mixCashStep}

{t.cashInsert}

{t.cashInsertSub}

{t.cashInserted} / {t.amount}
{fmtEUR(inserted, lang)} / {fmtEUR(cashAmt, lang)}
{[5,10,20,50,100].map(v => ( ))}
); }; window.ScreenMixCash = ScreenMixCash; const ScreenMixCard = () => { const t = useT(); const lang = window.AppState.lang; const { go } = window.AppState; const remaining = window.AppState.mixRemaining || 0; const [stage, setStage] = React.useState('insert'); React.useEffect(() => { if (stage === 'insert') { const x = setTimeout(() => setStage('processing'), 3500); return () => clearTimeout(x); } if (stage === 'processing') { const x = setTimeout(() => go('success'), 2400); return () => clearTimeout(x); } }, [stage]); return (
go('cancelConfirm')} />
{t.mixCardStep}

{stage === 'insert' ? t.cardInsert : t.cardProcessing}

{stage === 'insert' ? t.cardInsertSub : t.cardProcessingSub}

PAX IM30 · {lang === 'el' ? 'Υπόλοιπο για πληρωμή' : 'Remaining'}
{fmtEUR(remaining, lang)}
{stage === 'processing' &&
{t.cardProcessing}
}
); }; window.ScreenMixCard = ScreenMixCard; /* ============================================================ SUCCESS + RECEIPT flow ============================================================ */ const ScreenSuccess = () => { const t = useT(); const lang = window.AppState.lang; const { go } = window.AppState; const amount = window.AppState.amount || 0; const txnId = React.useMemo(() => 'MC-' + new Date().getFullYear() + '-' + Math.floor(Math.random() * 900000 + 100000), []); window.AppState.txnId = txnId; return (
{t.transactionId}
{txnId}
{t.paidAmount}
{fmtEUR(amount, lang)}
); }; window.ScreenSuccess = ScreenSuccess; const ScreenReceiptChoice = () => { const t = useT(); const { go } = window.AppState; return (

{t.receiptTitle}

{t.receiptSub}

go('receiptEmail')} badge={window.AppState.lang === 'el' ? 'Σύσταση' : 'Recommended'} /> go('receiptPrinted')} /> go('receiptPrinted')} /> go('attract')} />
); }; window.ScreenReceiptChoice = ScreenReceiptChoice; const ScreenReceiptEmail = () => { const t = useT(); const lang = window.AppState.lang; const { go, back } = window.AppState; const [email, setEmail] = React.useState(''); const [domain, setDomain] = React.useState(''); const valid = /\S+@\S+\.\S+/.test(email + '@' + domain) && email.length > 1 && domain.length > 3; return (

{t.emailEnter}

{t.emailEnterSub}

{email || 'name'} @ {domain || 'domain.com'}
{['student.metropolitan.edu.gr', 'gmail.com', 'hotmail.com', 'yahoo.gr', 'outlook.com'].map(d => ( ))}
{lang === 'el' ? 'Συμβουλή για demo: αγγίξτε ένα domain παραπάνω και ένα όνομα παρακάτω.' : 'Demo: tap a domain above and a name below.'}
{['maria.papadopoulou', 'dimitris.k', 'guest', 'parent.123'].map(n => ( ))}
); }; window.ScreenReceiptEmail = ScreenReceiptEmail; const ReceiptCard = () => { const lang = window.AppState.lang; const amount = window.AppState.amount || 0; const txnId = window.AppState.txnId || 'MC-2026-000000'; const t = useT(); return (
METROPOLITAN COLLEGE
{lang === 'el' ? 'Αθήνα · Ακαδημίας 42' : 'Athens · Akadimias 42'}
{t.transactionId}{txnId}
{t.receivedAt}27/04/2026 14:23
{t.method}{(window.AppState.method || 'card').toUpperCase()}
{t.paidAmount}{fmtEUR(amount, lang)}
{lang === 'el' ? 'Δεν αποτελεί νόμιμο παραστατικό. Νόμιμο παραστατικό αποστέλλεται μέσω email.' : 'Not a legal document. The legal invoice is sent by email.'}
); }; const ScreenReceiptSent = () => { const t = useT(); const { go } = window.AppState; React.useEffect(() => { const x = setTimeout(() => go('attract'), 6000); return () => clearTimeout(x); }, []); return (
); }; window.ScreenReceiptSent = ScreenReceiptSent; const ScreenReceiptPrinted = () => { const t = useT(); const { go } = window.AppState; React.useEffect(() => { const x = setTimeout(() => go('attract'), 6000); return () => clearTimeout(x); }, []); return (
); }; window.ScreenReceiptPrinted = ScreenReceiptPrinted; /* ============================================================ EDGE CASES ============================================================ */ const SimpleStatusScreen = ({ kind, icon, titleKey, subKey, primary, secondary, dest1, dest2 }) => { const t = useT(); const { go } = window.AppState; return (
{secondary && }
); }; const ScreenInvalidRF = () => { const t = useT(); return ; }; window.ScreenInvalidRF = ScreenInvalidRF; const ScreenNoDues = () => { const t = useT(); return ; }; window.ScreenNoDues = ScreenNoDues; const ScreenCardDeclined = () => { const t = useT(); return ; }; window.ScreenCardDeclined = ScreenCardDeclined; const ScreenIrisExpired = () => { const t = useT(); return ; }; window.ScreenIrisExpired = ScreenIrisExpired; const ScreenFakeNote = () => { const t = useT(); const { go } = window.AppState; React.useEffect(() => { const x = setTimeout(() => go('cashPay'), 5000); return () => clearTimeout(x); }, []); return ; }; window.ScreenFakeNote = ScreenFakeNote; const ScreenInsufficientChange = () => { const t = useT(); return ; }; window.ScreenInsufficientChange = ScreenInsufficientChange; const ScreenCashJam = () => { const t = useT(); return ; }; window.ScreenCashJam = ScreenCashJam; const ScreenEmailFail = () => { const t = useT(); return ; }; window.ScreenEmailFail = ScreenEmailFail; const ScreenSessionExpired = () => { const t = useT(); return ; }; window.ScreenSessionExpired = ScreenSessionExpired; const ScreenClassterDown = () => { const t = useT(); return ; }; window.ScreenClassterDown = ScreenClassterDown; /* ============================================================ Cancel-confirmation modal ============================================================ */ const ScreenCancelConfirm = () => { const t = useT(); const { go, back } = window.AppState; return (
); }; window.ScreenCancelConfirm = ScreenCancelConfirm; /* ============================================================ Inactivity-timeout warning (modal) ============================================================ */ const InactivityModal = ({ onKeep, onEnd, secs }) => { const t = useT(); return (

{t.timeoutTitle}

{t.timeoutSub}

{secs}
); }; window.InactivityModal = InactivityModal;