/** * ATM App - Vanilla JS Kiosk Implementation */ // --- 1. The "Library" Logic (Reused) --- function h(tag, props = {}, ...children) { const el = document.createElement(tag); if (props) { Object.entries(props).forEach(([key, value]) => { if (key.startsWith("on") && typeof value === "function") { el.addEventListener(key.substring(2).toLowerCase(), value); } else if (key === "className") { el.className = value; } else if (key === "style" && typeof value === "object") { Object.assign(el.style, value); } else { el.setAttribute(key, value); } }); } children.forEach((child) => { if (typeof child === "string" || typeof child === "number") { el.appendChild(document.createTextNode(child)); } else if (child instanceof Node) { el.appendChild(child); } else if (Array.isArray(child)) { child.forEach((c) => el.appendChild(c)); } }); return el; } let _rootContainer = null; let _rootComponent = null; function render(component, container) { _rootContainer = container; _rootComponent = component; _render(); } function _render() { _rootContainer.innerHTML = ""; _rootContainer.appendChild(_rootComponent()); } const createSignal = (initialValue) => { let _val = initialValue; const getValue = () => _val; const setValue = (newValue) => { _val = typeof newValue === "function" ? newValue(_val) : newValue; _render(); }; return [getValue, setValue]; }; // --- 2. ATM Application Components --- // Global State const [getView, setView] = createSignal("pin"); // 'pin', 'menu', 'withdraw', 'custom_withdraw', 'balance' const [getPin, setPin] = createSignal(""); const [getCustomAmount, setCustomAmount] = createSignal(""); // For custom withdrawal const [getBalance, setBalance] = createSignal(1250000); // Shared mockup balance const [getMessage, setMessage] = createSignal(""); // For feedback // Header function Header() { return h( "div", { className: "header", style: { marginBottom: "2rem" } }, h("h1", null, "ATM TERMINAL"), h("p", null, "Global Financial Network"), ); } // PIN Entry View function PinView() { const currentPin = getPin(); const handleNumClick = (num) => { if (currentPin.length < 4) { setPin((prev) => prev + num); } }; const handleClear = () => setPin(""); const handleEnter = () => { if (currentPin.length === 4) { // Mock auth success setView("menu"); } else { setMessage("Invalid PIN Length"); setTimeout(() => setMessage(""), 2000); } }; return h( "div", { className: "card", style: { padding: "3rem 2rem" } }, h("h2", null, "Enter Security PIN"), h( "div", { className: "pin-display" }, currentPin.replace(/./g, "•") || "----", ), h("p", { style: { color: "red", height: "1.5rem" } }, getMessage()), h( "div", { className: "numpad" }, ["1", "2", "3", "4", "5", "6", "7", "8", "9"].map((num) => h("button", { onClick: () => handleNumClick(num) }, num), ), h( "button", { style: { background: "#ef4444", color: "white" }, onClick: handleClear, }, "C", ), h("button", { onClick: () => handleNumClick("0") }, "0"), h( "button", { style: { background: "#10b981", color: "white" }, onClick: handleEnter, }, "↵", ), ), ); } // Main Menu View function MenuView() { return h( "div", { className: "kiosk-content" }, h( "h2", { style: { textAlign: "center", marginBottom: "1rem" } }, "Select Transaction", ), h( "div", { className: "kiosk-menu-stack" }, h( "button", { className: "kiosk-btn", onClick: () => setView("withdraw") }, "Withdraw Cash", ), h( "button", { className: "kiosk-btn", onClick: () => setView("balance") }, "Check Balance", ), h( "button", { className: "kiosk-btn", style: { background: "var(--bg-surface)", color: "var(--text-main)", border: "1px solid var(--border)", }, onClick: () => { setPin(""); setView("pin"); }, }, "Cancel Transaction", ), ), ); } // Withdraw View function WithdrawView() { const handleWithdraw = (amount) => { if (getBalance() >= amount) { setBalance((prev) => prev - amount); setMessage(`Please take your cash: $${amount}`); setTimeout(() => { setMessage(""); setView("menu"); }, 3000); } else { setMessage("Insufficient Funds"); setTimeout(() => setMessage(""), 2000); } }; if (getMessage()) { return h( "div", { className: "card", style: { padding: "4rem", textAlign: "center" }, }, h("h2", { style: { color: "var(--primary)" } }, getMessage()), ); } return h( "div", { className: "kiosk-content" }, h( "h2", { style: { textAlign: "center", marginBottom: "1rem" } }, "Select Amount", ), h( "div", { className: "kiosk-grid" }, h( "button", { className: "kiosk-btn", onClick: () => handleWithdraw(20) }, "$20", ), h( "button", { className: "kiosk-btn", onClick: () => handleWithdraw(50) }, "$50", ), h( "button", { className: "kiosk-btn", onClick: () => handleWithdraw(100) }, "$100", ), h( "button", { className: "kiosk-btn", onClick: () => { setCustomAmount(""); setView("custom_withdraw"); }, }, "Other Amount", ), h( "button", { className: "kiosk-btn", style: { gridColumn: "span 2", background: "var(--text-muted)", }, onClick: () => setView("menu"), }, "Cancel", ), ), ); } // Custom Withdraw View function CustomWithdrawView() { const currentAmount = getCustomAmount(); const handleNumClick = (num) => { if (currentAmount.length < 5) { // Limit to 5 digits for safety setCustomAmount((prev) => prev + num); } }; const handleClear = () => setCustomAmount(""); const handleEnter = () => { const amount = parseInt(currentAmount, 10); if (amount > 0) { if (getBalance() >= amount) { setBalance((prev) => prev - amount); setMessage(`Please take your cash: $${amount}`); setTimeout(() => { setMessage(""); setView("menu"); }, 3000); } else { setMessage("Insufficient Funds"); setTimeout(() => setMessage(""), 2000); } } else { setMessage("Invalid Amount"); setTimeout(() => setMessage(""), 2000); } }; if (getMessage()) { return h( "div", { className: "card", style: { padding: "4rem", textAlign: "center" }, }, h("h2", { style: { color: "var(--primary)" } }, getMessage()), ); } return h( "div", { className: "card", style: { padding: "3rem 2rem" } }, h("h2", null, "Enter Amount"), h( "div", { className: "pin-display" }, currentAmount ? `$${currentAmount}` : "$0", ), h( "div", { className: "numpad" }, ["1", "2", "3", "4", "5", "6", "7", "8", "9"].map((num) => h("button", { onClick: () => handleNumClick(num) }, num), ), h( "button", { style: { background: "#ef4444", color: "white" }, onClick: handleClear, }, "C", ), h("button", { onClick: () => handleNumClick("0") }, "0"), h( "button", { style: { background: "#10b981", color: "white" }, onClick: handleEnter, }, "↵", ), ), h( "button", { style: { width: "100%", marginTop: "2rem", padding: "1rem", background: "var(--text-muted)", }, onClick: () => setView("withdraw"), }, "Cancel", ), ); } // Balance View function BalanceView() { return h( "div", { className: "card", style: { textAlign: "center", padding: "3rem" } }, h("h2", { style: { color: "var(--text-muted)" } }, "Available Balance"), h( "div", { style: { fontSize: "4rem", fontWeight: "800", margin: "2rem 0", color: "var(--primary-hover)", }, }, "$" + getBalance().toLocaleString(), ), h( "button", { className: "kiosk-btn", style: { width: "100%", maxWidth: "300px", margin: "0 auto" }, onClick: () => setView("menu"), }, "Return to Menu", ), ); } // Main App function App() { const view = getView(); let mainContent; if (view === "pin") { mainContent = PinView(); } else if (view === "menu") { mainContent = MenuView(); } else if (view === "withdraw") { mainContent = WithdrawView(); } else if (view === "custom_withdraw") { mainContent = CustomWithdrawView(); } else if (view === "balance") { mainContent = BalanceView(); } return h( "main", null, h("div", { className: "container" }, Header(), mainContent), ); } // Mount const root = document.getElementById("app"); render(App, root);