/* ============================================================
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 (
);
};
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 (
);
};
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 (
);
};
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.back}
{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 => (
setInserted(i => i + v)}>€{v}
))}
go('cancelConfirm')}>{t.cancel}
go('mixCard')} disabled={inserted < cashAmt}>
{t.continue} → {t.mixCardStep}
);
};
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 (
);
};
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)}
go('receiptChoice')}>
{t.continue}
);
};
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.back}
{t.emailEnter}
{t.emailEnterSub}
{email || 'name'}
@
{domain || 'domain.com'}
{['student.metropolitan.edu.gr', 'gmail.com', 'hotmail.com', 'yahoo.gr', 'outlook.com'].map(d => (
setDomain(d)}>@{d}
))}
{lang === 'el' ? 'Συμβουλή για demo: αγγίξτε ένα domain παραπάνω και ένα όνομα παρακάτω.' : 'Demo: tap a domain above and a name below.'}
{['maria.papadopoulou', 'dimitris.k', 'guest', 'parent.123'].map(n => (
setEmail(n)}>{n}
))}
go('emailFail')}>{lang === 'el' ? 'Demo: αποτυχία' : 'Demo: fail'}
go('receiptSent')} disabled={!valid}>
{lang === 'el' ? 'Αποστολή απόδειξης' : 'Send receipt'}
);
};
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 && go(dest2)}>{secondary} }
go(dest1)}>{primary}
);
};
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 (
{t.cancelNo}
go('attract')}>{t.cancelYes}
);
};
window.ScreenCancelConfirm = ScreenCancelConfirm;
/* ============================================================
Inactivity-timeout warning (modal)
============================================================ */
const InactivityModal = ({ onKeep, onEnd, secs }) => {
const t = useT();
return (
{t.timeoutTitle}
{t.timeoutSub}
{secs}
{t.timeoutEnd}
{t.timeoutKeep}
);
};
window.InactivityModal = InactivityModal;