Jacob Schmidt 603963c935 Refactor bank UI to bridge-driven single-page flow
- Replace separate bank/ATM pages with a unified `index.html` app bundle
- Split bank init into `initClass`, `initSessionService`, and `initUIBridge`
- Route UI events through `BankUIBridge` and refresh session payloads after sync
2026-03-14 12:11:34 -05:00

1651 lines
52 KiB
JavaScript

/* Generated by tools/build-webui.mjs for Bank UI app. Do not edit directly. */
(function () {
const runtime = window.ForgeWebUI;
const BankApp = (window.BankApp = window.BankApp || {});
BankApp.runtime = runtime;
window.AppRuntime = runtime;
})();
(function () {
const BankApp = (window.BankApp = window.BankApp || {});
const defaultSession = {
mode: "bank",
orgFunds: 0,
orgName: "",
playerName: "",
transferTargets: [],
uid: "",
};
const defaultAccount = {
bank: 0,
cash: 0,
earnings: 0,
pin: "1234",
transactions: [],
};
function cloneValue(value) {
return JSON.parse(JSON.stringify(value));
}
function replaceObject(target, source) {
Object.keys(target).forEach((key) => delete target[key]);
Object.assign(target, cloneValue(source));
}
BankApp.data = {
account: Object.assign({}, defaultAccount),
session: Object.assign({}, defaultSession),
applyHydratePayload(payload) {
replaceObject(
this.session,
Object.assign({}, defaultSession, payload?.session || {}),
);
replaceObject(
this.account,
Object.assign({}, defaultAccount, payload?.account || {}),
);
},
};
})();
(function () {
const BankApp = (window.BankApp = window.BankApp || {});
const { createSignal } = BankApp.runtime;
class BankStore {
constructor() {
[this.getMode, this.setMode] = createSignal("bank");
[this.getNotice, this.setNotice] = createSignal({
text: "",
type: "",
});
[this.getPendingAction, this.setPendingAction] = createSignal("");
[this.getAtmView, this.setAtmView] = createSignal("pin");
[this.getEnteredPin, this.setEnteredPin] = createSignal("");
[this.getCustomAmount, this.setCustomAmount] = createSignal("");
[this.getAccountVersion, this.setAccountVersion] = createSignal(0);
[this.getSessionVersion, this.setSessionVersion] = createSignal(0);
}
finishAction() {
this.setPendingAction("");
}
hydrateFromPayload(payload) {
const mode = String(payload?.session?.mode || "bank")
.trim()
.toLowerCase();
const currentMode = this.getMode();
const currentAtmView = this.getAtmView();
this.setMode(mode === "atm" ? "atm" : "bank");
this.setPendingAction("");
this.setNotice({ text: "", type: "" });
this.setEnteredPin("");
this.setCustomAmount("");
this.setAccountVersion(this.getAccountVersion() + 1);
this.setSessionVersion(this.getSessionVersion() + 1);
if (mode === "atm") {
this.setAtmView(currentMode === "atm" ? currentAtmView : "pin");
return;
}
this.setAtmView("dashboard");
}
resetAtm() {
this.setEnteredPin("");
this.setCustomAmount("");
this.setAtmView("pin");
}
startAction(action) {
this.setPendingAction(
String(action || "")
.trim()
.toLowerCase(),
);
}
}
BankApp.store = new BankStore();
})();
(function () {
const BankApp = (window.BankApp = window.BankApp || {});
const store = BankApp.store;
const bridge = window.ForgeWebUI.createBridge({
closeEvent: "bank::close",
globalName: "ForgeBridge",
readyEvent: "bank::ready",
});
function hydrate(payloadData) {
BankApp.data.applyHydratePayload(payloadData);
store.hydrateFromPayload(payloadData);
}
bridge.on("bank::hydrate", hydrate);
bridge.on("bank::sync", hydrate);
bridge.on("bank::notice", (payloadData) => {
if (BankApp.actions) {
BankApp.actions.showNotice(
payloadData.type || "error",
payloadData.message || "Bank notice received.",
);
}
});
BankApp.bridge = {
notifyReady() {
return bridge.ready({ loaded: true });
},
receive: bridge.receive,
requestClose() {
return bridge.close({});
},
requestDeposit(payload) {
return bridge.send("bank::deposit::request", payload);
},
requestDepositEarnings(payload) {
return bridge.send("bank::depositEarnings::request", payload);
},
requestRefresh() {
return bridge.send("bank::refresh", {});
},
requestTransfer(payload) {
return bridge.send("bank::transfer::request", payload);
},
requestWithdraw(payload) {
return bridge.send("bank::withdraw::request", payload);
},
sendEvent: bridge.send,
};
})();
(function () {
const BankApp = (window.BankApp = window.BankApp || {});
const store = BankApp.store;
let noticeTimer = null;
function getAccount() {
return BankApp.data?.account || {};
}
function getSession() {
return BankApp.data?.session || {};
}
function normalizeAmount(value) {
const amount = Math.floor(Number(value || 0));
return Number.isFinite(amount) ? amount : 0;
}
function showNotice(type, text) {
store.setNotice({ type, text });
if (noticeTimer) {
clearTimeout(noticeTimer);
}
noticeTimer = setTimeout(() => {
store.setNotice({ text: "", type: "" });
noticeTimer = null;
}, 3200);
}
function closeBank() {
const bridge = BankApp.bridge;
if (bridge && typeof bridge.requestClose === "function") {
const sent = bridge.requestClose();
if (sent) {
return true;
}
}
showNotice("error", "Bank bridge is unavailable.");
return false;
}
function refreshBank() {
const bridge = BankApp.bridge;
if (bridge && typeof bridge.requestRefresh === "function") {
const sent = bridge.requestRefresh();
if (sent) {
return true;
}
}
showNotice("error", "Bank refresh bridge is unavailable.");
return false;
}
function requestDeposit(amountValue) {
const amount = normalizeAmount(amountValue);
const account = getAccount();
if (amount <= 0) {
showNotice("error", "Enter a valid deposit amount.");
return false;
}
if (amount > Number(account.cash || 0)) {
showNotice("error", "Cash on hand cannot cover that deposit.");
return false;
}
const bridge = BankApp.bridge;
if (!bridge || typeof bridge.requestDeposit !== "function") {
showNotice("error", "Deposit bridge is unavailable.");
return false;
}
store.startAction("deposit");
const sent = bridge.requestDeposit({ amount });
if (!sent) {
store.finishAction();
showNotice("error", "Deposit bridge is unavailable.");
return false;
}
return true;
}
function requestWithdraw(amountValue) {
const amount = normalizeAmount(amountValue);
const account = getAccount();
if (amount <= 0) {
showNotice("error", "Enter a valid withdrawal amount.");
return false;
}
if (amount > Number(account.bank || 0)) {
showNotice("error", "Bank balance cannot cover that withdrawal.");
return false;
}
const bridge = BankApp.bridge;
if (!bridge || typeof bridge.requestWithdraw !== "function") {
showNotice("error", "Withdraw bridge is unavailable.");
return false;
}
store.startAction("withdraw");
const sent = bridge.requestWithdraw({ amount });
if (!sent) {
store.finishAction();
showNotice("error", "Withdraw bridge is unavailable.");
return false;
}
return true;
}
function requestTransfer(targetUid, amountValue) {
const amount = normalizeAmount(amountValue);
const session = getSession();
const account = getAccount();
const targetId = String(targetUid || "").trim();
if (!targetId) {
showNotice("error", "Select a transfer recipient.");
return false;
}
if (targetId === String(session.uid || "")) {
showNotice("error", "You cannot transfer funds to yourself.");
return false;
}
if (amount <= 0) {
showNotice("error", "Enter a valid transfer amount.");
return false;
}
if (amount > Number(account.bank || 0)) {
showNotice("error", "Bank balance cannot cover that transfer.");
return false;
}
const bridge = BankApp.bridge;
if (!bridge || typeof bridge.requestTransfer !== "function") {
showNotice("error", "Transfer bridge is unavailable.");
return false;
}
store.startAction("transfer");
const sent = bridge.requestTransfer({
amount,
from: "bank",
target: targetId,
});
if (!sent) {
store.finishAction();
showNotice("error", "Transfer bridge is unavailable.");
return false;
}
return true;
}
function requestDepositEarnings(amountValue) {
const amount = normalizeAmount(amountValue);
const account = getAccount();
if (amount <= 0) {
showNotice("error", "No earnings are available to deposit.");
return false;
}
if (amount > Number(account.earnings || 0)) {
showNotice(
"error",
"Pending earnings cannot cover that deposit request.",
);
return false;
}
const bridge = BankApp.bridge;
if (!bridge || typeof bridge.requestDepositEarnings !== "function") {
showNotice("error", "Earnings bridge is unavailable.");
return false;
}
store.startAction("depositearnings");
const sent = bridge.requestDepositEarnings({ amount });
if (!sent) {
store.finishAction();
showNotice("error", "Earnings bridge is unavailable.");
return false;
}
return true;
}
function appendPinDigit(digit) {
const nextDigit = String(digit || "").trim();
if (!nextDigit) {
return;
}
const currentPin = String(store.getEnteredPin() || "");
if (currentPin.length >= 4) {
return;
}
store.setEnteredPin(currentPin + nextDigit);
}
function backspacePin() {
const currentPin = String(store.getEnteredPin() || "");
store.setEnteredPin(currentPin.slice(0, -1));
}
function clearPin() {
store.setEnteredPin("");
}
function submitPin() {
const enteredPin = String(store.getEnteredPin() || "");
const actualPin = String(getAccount().pin || "1234");
if (enteredPin.length !== 4) {
showNotice("error", "Enter your four-digit access PIN.");
return false;
}
if (enteredPin !== actualPin) {
clearPin();
showNotice("error", "Incorrect PIN.");
return false;
}
clearPin();
store.setAtmView("menu");
return true;
}
function selectAtmView(view) {
const nextView = String(view || "").trim();
if (!nextView) {
return false;
}
if (nextView === "pin") {
store.resetAtm();
return true;
}
store.setCustomAmount("");
store.setAtmView(nextView);
return true;
}
function appendCustomAmountDigit(digit) {
const nextDigit = String(digit || "").trim();
if (!nextDigit) {
return;
}
const currentValue = String(store.getCustomAmount() || "");
if (currentValue.length >= 7) {
return;
}
store.setCustomAmount(currentValue + nextDigit);
}
function backspaceCustomAmount() {
const currentValue = String(store.getCustomAmount() || "");
store.setCustomAmount(currentValue.slice(0, -1));
}
function clearCustomAmount() {
store.setCustomAmount("");
}
function submitCustomAmount(kind) {
const amount = normalizeAmount(store.getCustomAmount());
const nextKind = String(kind || "")
.trim()
.toLowerCase();
if (amount <= 0) {
showNotice("error", "Enter a valid transaction amount.");
return false;
}
const success =
nextKind === "deposit"
? requestDeposit(amount)
: requestWithdraw(amount);
if (success) {
store.setCustomAmount("");
store.setAtmView("menu");
}
return success;
}
function requestAtmAmount(kind, amount) {
const nextKind = String(kind || "")
.trim()
.toLowerCase();
const success =
nextKind === "deposit"
? requestDeposit(amount)
: requestWithdraw(amount);
if (success) {
store.setAtmView("menu");
}
return success;
}
BankApp.actions = {
appendCustomAmountDigit,
appendPinDigit,
backspaceCustomAmount,
backspacePin,
clearCustomAmount,
clearPin,
closeBank,
refreshBank,
requestAtmAmount,
requestDeposit,
requestDepositEarnings,
requestTransfer,
requestWithdraw,
selectAtmView,
showNotice,
submitCustomAmount,
submitPin,
};
})();
(function () {
const BankApp = (window.BankApp = window.BankApp || {});
const { h } = BankApp.runtime;
const store = BankApp.store;
const { account } = BankApp.data;
function formatCurrency(value) {
return `$${Math.round(Number(value || 0)).toLocaleString()}`;
}
function pending(actionName) {
return store.getPendingAction() === actionName;
}
function statCard(label, value, tone = "") {
return h(
"div",
{
className: tone
? `bank-stat-card is-${tone}`
: "bank-stat-card",
},
h("span", { className: "bank-stat-label" }, label),
h("span", { className: "bank-stat-value" }, value),
);
}
function metricCard(label, value, copy, tone = "") {
return h(
"div",
{
className: tone
? `bank-metric-card is-${tone}`
: "bank-metric-card",
},
h("span", { className: "bank-eyebrow" }, label),
h("span", { className: "bank-metric-value" }, value),
h("span", { className: "bank-metric-copy" }, copy),
);
}
function pinIndicators(value) {
const pin = String(value || "");
return h(
"div",
{ className: "bank-pin-indicators" },
[0, 1, 2, 3].map((index) =>
h("span", {
className:
index < pin.length
? "bank-pin-indicator is-filled"
: "bank-pin-indicator",
}),
),
);
}
function readInputValue(id) {
return document.getElementById(id)?.value || "";
}
function clearInputValue(id) {
const input = document.getElementById(id);
if (input) {
input.value = "";
}
}
function keypad(onDigit, onBackspace, onClear, onEnter) {
const keys = ["1", "2", "3", "4", "5", "6", "7", "8", "9"];
return h(
"div",
{ className: "bank-keypad" },
keys.map((digit) =>
h(
"button",
{
type: "button",
className: "bank-key",
onClick: () => onDigit(digit),
},
digit,
),
),
h(
"button",
{
type: "button",
className: "bank-key is-muted",
onClick: onClear,
},
"C",
),
h(
"button",
{
type: "button",
className: "bank-key",
onClick: () => onDigit("0"),
},
"0",
),
h(
"button",
{
type: "button",
className: "bank-key is-accent",
onClick: onEnter,
},
"Enter",
),
h(
"button",
{
type: "button",
className: "bank-key is-wide",
onClick: onBackspace,
},
"Backspace",
),
);
}
function transactionRows() {
const transactions = Array.isArray(account.transactions)
? account.transactions
: [];
if (transactions.length === 0) {
return h(
"div",
{ className: "bank-empty-state" },
h("h3", { className: "bank-empty-title" }, "No transactions"),
h(
"p",
{ className: "bank-empty-copy" },
"Deposits, withdrawals, and transfers will appear here after the account begins moving funds.",
),
);
}
return h(
"div",
{ className: "bank-history-list" },
transactions
.slice(0, 8)
.map((entry) =>
h(
"div",
{ className: "bank-history-row" },
h(
"div",
{ className: "bank-history-copy" },
h(
"span",
{ className: "bank-history-title" },
entry.type || "Transaction",
),
h(
"span",
{ className: "bank-history-meta" },
entry.date || "Pending timestamp",
),
),
h(
"span",
{ className: "bank-history-value" },
formatCurrency(entry.amount || 0),
),
),
),
);
}
BankApp.componentFns = BankApp.componentFns || {};
Object.assign(BankApp.componentFns, {
clearInputValue,
formatCurrency,
keypad,
metricCard,
pending,
pinIndicators,
readInputValue,
statCard,
transactionRows,
});
})();
(function () {
const BankApp = (window.BankApp = window.BankApp || {});
const { h } = BankApp.runtime;
const store = BankApp.store;
const actions = BankApp.actions;
const { account, session } = BankApp.data;
const { formatCurrency, statCard } = BankApp.componentFns;
BankApp.componentFns = BankApp.componentFns || {};
BankApp.componentFns.BankSidebar = function BankSidebar() {
store.getAccountVersion();
store.getSessionVersion();
return h(
"aside",
{ className: "bank-sidebar" },
h(
"section",
{ className: "bank-module" },
h(
"div",
{ className: "bank-module-header" },
h(
"div",
null,
h("span", { className: "bank-eyebrow" }, "Account"),
h(
"h2",
{ className: "bank-section-title" },
"Balances",
),
),
h("span", { className: "bank-pill" }, "Live"),
),
h(
"div",
{ className: "bank-summary-grid" },
statCard("Bank", formatCurrency(account.bank), "accent"),
statCard("Cash", formatCurrency(account.cash)),
statCard(
"Earnings",
formatCurrency(account.earnings),
account.earnings > 0 ? "warning" : "",
),
statCard(
"Org Funds",
formatCurrency(session.orgFunds),
session.orgFunds > 0 ? "success" : "",
),
),
),
h(
"section",
{ className: "bank-module" },
h(
"div",
{ className: "bank-module-header" },
h(
"div",
null,
h("span", { className: "bank-eyebrow" }, "Profile"),
h(
"h2",
{ className: "bank-section-title" },
"Account Holder",
),
),
h(
"button",
{
type: "button",
className: "bank-btn bank-btn-secondary",
onClick: () => actions.refreshBank(),
},
"Refresh",
),
),
h(
"div",
{ className: "bank-profile-stack" },
statCard("Name", session.playerName || "Unknown"),
statCard("UID", session.uid || "-"),
statCard(
"Organization",
session.orgName || "No active organization",
),
),
),
);
};
})();
(function () {
const BankApp = (window.BankApp = window.BankApp || {});
const { h } = BankApp.runtime;
const store = BankApp.store;
const { account, session } = BankApp.data;
const { formatCurrency } = BankApp.componentFns;
BankApp.componentFns = BankApp.componentFns || {};
BankApp.componentFns.BankFooter = function BankFooter() {
store.getAccountVersion();
store.getSessionVersion();
const sections = [
{
title: "Banking Resources",
items: [
"Account Access Policy",
"Transfer & Wire Guidelines",
"Cash Handling Schedule",
"Terminal Security Notice",
],
},
{
title: "Bank Support",
items: session.orgName
? [
`Organization: ${session.orgName}`,
`Treasury Reference: ${formatCurrency(session.orgFunds)}`,
`${session.transferTargets.length} transfer recipient(s) currently visible.`,
`Primary Ledger: ${formatCurrency(account.bank)}`,
]
: [
"Organization: No active treasury link",
`${session.transferTargets.length} transfer recipient(s) currently visible.`,
`Primary Ledger: ${formatCurrency(account.bank)}`,
`Cash On Hand: ${formatCurrency(account.cash)}`,
],
},
];
return h(
"footer",
{ className: "bank-footer-bar" },
h(
"div",
{ className: "bank-footer" },
...sections.map((section) =>
h(
"div",
{ className: "bank-footer-block" },
h(
"h3",
{ className: "bank-footer-title" },
section.title,
),
h(
"ul",
{ className: "bank-footer-list" },
...(section.items || []).map((item) =>
h(
"li",
{ className: "bank-footer-copy" },
item,
),
),
),
),
),
),
);
};
})();
(function () {
const BankApp = (window.BankApp = window.BankApp || {});
const { h } = BankApp.runtime;
const store = BankApp.store;
const actions = BankApp.actions;
const { account, session } = BankApp.data;
const {
clearInputValue,
formatCurrency,
metricCard,
pending,
readInputValue,
transactionRows,
} = BankApp.componentFns;
function trackAccount() {
store.getAccountVersion();
}
function trackSession() {
store.getSessionVersion();
}
function pageHeader() {
trackSession();
return h(
"div",
{ className: "bank-page-header" },
h(
"div",
null,
h("span", { className: "bank-eyebrow" }, "Treasury Desk"),
h("h1", { className: "bank-title" }, "Personal Banking"),
),
h(
"span",
{ className: "bank-pill" },
session.playerName || "Account Holder",
),
);
}
function summarySection() {
trackAccount();
trackSession();
return h(
"section",
{ className: "bank-page-section bank-summary-section" },
h(
"div",
{ className: "bank-section-header" },
h(
"div",
null,
h("span", { className: "bank-eyebrow" }, "Overview"),
h(
"h2",
{ className: "bank-section-title" },
"Financial Position",
),
),
h("span", { className: "bank-pill" }, "Banking Desk"),
),
h(
"div",
{ className: "bank-summary-band" },
metricCard(
"Primary Balance",
formatCurrency(account.bank),
"Available for transfers and withdrawals.",
"accent",
),
metricCard(
"Cash On Hand",
formatCurrency(account.cash),
"Funds currently carried by the player.",
),
metricCard(
"Pending Earnings",
formatCurrency(account.earnings),
"Ready to sweep into the main account ledger.",
account.earnings > 0 ? "warning" : "",
),
metricCard(
"Org Snapshot",
formatCurrency(session.orgFunds),
"Reference value pulled from the organization treasury.",
session.orgFunds > 0 ? "success" : "",
),
),
);
}
function actionSections() {
trackSession();
return h(
"div",
{ className: "bank-action-sections" },
h(
"section",
{ className: "bank-page-section" },
h(
"div",
{ className: "bank-section-header" },
h(
"div",
null,
h("span", { className: "bank-eyebrow" }, "Movement"),
h(
"h2",
{ className: "bank-section-title" },
"Deposit / Withdraw",
),
),
),
h(
"div",
{ className: "bank-form-stack" },
h("input", {
id: "bank-amount-input",
className: "bank-input",
type: "number",
min: "1",
placeholder: "Enter amount",
}),
h(
"div",
{ className: "bank-action-row" },
h(
"button",
{
type: "button",
className: "bank-btn bank-btn-primary",
disabled: pending("deposit"),
onClick: () => {
const sent = actions.requestDeposit(
readInputValue("bank-amount-input"),
);
if (sent) {
clearInputValue("bank-amount-input");
}
},
},
pending("deposit") ? "Depositing..." : "Deposit",
),
h(
"button",
{
type: "button",
className: "bank-btn bank-btn-secondary",
disabled: pending("withdraw"),
onClick: () => {
const sent = actions.requestWithdraw(
readInputValue("bank-amount-input"),
);
if (sent) {
clearInputValue("bank-amount-input");
}
},
},
pending("withdraw") ? "Withdrawing..." : "Withdraw",
),
),
),
),
h(
"section",
{ className: "bank-page-section" },
h(
"div",
{ className: "bank-section-header" },
h(
"div",
null,
h("span", { className: "bank-eyebrow" }, "Transfer"),
h(
"h2",
{ className: "bank-section-title" },
"Wire Funds",
),
),
),
h(
"div",
{ className: "bank-form-stack" },
h(
"select",
{
id: "bank-transfer-target",
className: "bank-select",
},
h(
"option",
{ value: "" },
session.transferTargets.length > 0
? "Select recipient"
: "No available recipients",
),
session.transferTargets.map((entry) =>
h(
"option",
{ value: entry.uid },
entry.name || entry.uid,
),
),
),
h("input", {
id: "bank-transfer-amount",
className: "bank-input",
type: "number",
min: "1",
placeholder: "Enter transfer amount",
}),
h(
"button",
{
type: "button",
className: "bank-btn bank-btn-primary",
disabled:
pending("transfer") ||
session.transferTargets.length === 0,
onClick: () => {
const sent = actions.requestTransfer(
readInputValue("bank-transfer-target"),
readInputValue("bank-transfer-amount"),
);
if (sent) {
clearInputValue("bank-transfer-amount");
}
},
},
pending("transfer")
? "Transferring..."
: "Transfer Funds",
),
),
),
);
}
function supportSection() {
trackAccount();
return h(
"div",
{ className: "bank-support-sections" },
h(
"section",
{ className: "bank-page-section" },
h(
"div",
{ className: "bank-section-header" },
h(
"div",
null,
h("span", { className: "bank-eyebrow" }, "Sweep"),
h(
"h2",
{ className: "bank-section-title" },
"Deposit Earnings",
),
),
),
h(
"p",
{ className: "bank-card-copy" },
"Sweep pending earnings into the primary account when you want them reflected in the main balance.",
),
h(
"button",
{
type: "button",
className: "bank-btn bank-btn-primary",
disabled:
pending("depositearnings") ||
Number(account.earnings || 0) <= 0,
onClick: () =>
actions.requestDepositEarnings(account.earnings),
},
pending("depositearnings")
? "Depositing..."
: "Deposit Earnings",
),
),
);
}
function historySection() {
trackAccount();
return h(
"section",
{ className: "bank-page-section bank-history-section" },
h(
"div",
{ className: "bank-section-header" },
h(
"div",
null,
h("span", { className: "bank-eyebrow" }, "History"),
h(
"h2",
{ className: "bank-section-title" },
"Recent Transactions",
),
),
),
transactionRows(),
);
}
BankApp.componentFns = BankApp.componentFns || {};
BankApp.componentFns.BankPageHeader = pageHeader;
BankApp.componentFns.BankSummarySection = summarySection;
BankApp.componentFns.BankActionSections = actionSections;
BankApp.componentFns.BankSupportSection = supportSection;
BankApp.componentFns.BankHistorySection = historySection;
})();
(function () {
const BankApp = (window.BankApp = window.BankApp || {});
const { h } = BankApp.runtime;
const store = BankApp.store;
const actions = BankApp.actions;
const { account } = BankApp.data;
const { formatCurrency, keypad, pinIndicators } = BankApp.componentFns;
function atmMenuCard() {
return h(
"div",
{ className: "bank-atm-action-grid" },
h(
"button",
{
type: "button",
className: "bank-btn bank-btn-primary",
onClick: () => actions.selectAtmView("withdraw"),
},
"Withdraw Cash",
),
h(
"button",
{
type: "button",
className: "bank-btn bank-btn-primary",
onClick: () => actions.selectAtmView("deposit"),
},
"Deposit Cash",
),
h(
"button",
{
type: "button",
className: "bank-btn bank-btn-secondary",
onClick: () => actions.selectAtmView("balance"),
},
"Check Balance",
),
h(
"button",
{
type: "button",
className: "bank-btn bank-btn-secondary",
onClick: () => actions.closeBank(),
},
"Exit Terminal",
),
);
}
function atmAmountMenu(kind) {
const label = kind === "deposit" ? "Deposit" : "Withdraw";
const amounts = [20, 50, 100, 500];
return h(
"div",
{ className: "bank-atm-action-grid" },
amounts.map((amount) =>
h(
"button",
{
type: "button",
className: "bank-btn bank-btn-primary",
onClick: () => actions.requestAtmAmount(kind, amount),
},
`${label} ${formatCurrency(amount)}`,
),
),
h(
"button",
{
type: "button",
className: "bank-btn bank-btn-secondary",
onClick: () =>
actions.selectAtmView(
kind === "deposit"
? "customDeposit"
: "customWithdraw",
),
},
"Custom Amount",
),
h(
"button",
{
type: "button",
className: "bank-btn bank-btn-secondary",
onClick: () => actions.selectAtmView("menu"),
},
"Back",
),
);
}
function atmCustomAmount(kind) {
const label = kind === "deposit" ? "Deposit" : "Withdraw";
return h(
"div",
{ className: "bank-atm-stack" },
h(
"div",
{ className: "bank-pin-display" },
store.getCustomAmount()
? formatCurrency(store.getCustomAmount())
: "$0",
),
keypad(
actions.appendCustomAmountDigit,
actions.backspaceCustomAmount,
actions.clearCustomAmount,
() => actions.submitCustomAmount(kind),
),
h(
"button",
{
type: "button",
className: "bank-btn bank-btn-secondary",
onClick: () => actions.selectAtmView("menu"),
},
`Cancel ${label}`,
),
);
}
BankApp.componentFns = BankApp.componentFns || {};
BankApp.componentFns.ATMView = function ATMView() {
store.getAccountVersion();
const atmViewName = store.getAtmView();
const enteredPin = String(store.getEnteredPin() || "");
let title = "Terminal Access";
let copy =
"Authenticate with the four-digit account PIN before using the terminal.";
let content = null;
switch (atmViewName) {
case "menu":
title = "ATM Menu";
copy =
"Select a banking action. The ATM can deposit, withdraw, and show the live account balance.";
content = atmMenuCard();
break;
case "withdraw":
title = "Withdraw Cash";
copy =
"Choose a preset amount or enter a custom amount for withdrawal.";
content = atmAmountMenu("withdraw");
break;
case "deposit":
title = "Deposit Cash";
copy =
"Move cash on hand back into the main bank balance from the terminal.";
content = atmAmountMenu("deposit");
break;
case "customWithdraw":
title = "Custom Withdraw";
copy = "Enter the exact withdrawal amount.";
content = atmCustomAmount("withdraw");
break;
case "customDeposit":
title = "Custom Deposit";
copy = "Enter the exact deposit amount.";
content = atmCustomAmount("deposit");
break;
case "balance":
title = "Available Balance";
copy = "Current bank balance available at this terminal.";
content = h(
"div",
{ className: "bank-atm-stack" },
h(
"div",
{ className: "bank-balance-display" },
formatCurrency(account.bank),
),
h(
"button",
{
type: "button",
className: "bank-btn bank-btn-primary",
onClick: () => actions.selectAtmView("menu"),
},
"Return to Menu",
),
);
break;
default:
content = h(
"div",
{ className: "bank-atm-stack" },
h(
"div",
{ className: "bank-pin-display" },
pinIndicators(enteredPin),
),
keypad(
actions.appendPinDigit,
actions.backspacePin,
actions.clearPin,
actions.submitPin,
),
h(
"button",
{
type: "button",
className: "bank-btn bank-btn-secondary",
onClick: () => actions.closeBank(),
},
"Exit Terminal",
),
);
break;
}
return h(
"div",
{ className: "bank-atm-shell" },
h(
"section",
{ className: "bank-atm-panel" },
h(
"div",
{ className: "bank-panel-header" },
h(
"div",
null,
h("span", { className: "bank-eyebrow" }, "ATM"),
h("h1", { className: "bank-title" }, title),
),
h("span", { className: "bank-pill" }, "Secure Terminal"),
),
h("p", { className: "bank-panel-copy" }, copy),
content,
),
);
};
})();
(function () {
const BankApp = (window.BankApp = window.BankApp || {});
const { h } = BankApp.runtime;
const WindowTitleBar = window.SharedUI.componentFns.WindowTitleBar;
const store = BankApp.store;
const actions = BankApp.actions;
BankApp.componentFns = BankApp.componentFns || {};
BankApp.componentFns.NoticeLayer = function NoticeLayer() {
const notice = store.getNotice();
if (!notice.text) {
return null;
}
return h(
"div",
{ className: "bank-notice-stack" },
h(
"div",
{
className:
notice.type === "error"
? "bank-notice is-error"
: "bank-notice is-success",
},
notice.text,
),
);
};
BankApp.components = BankApp.components || {};
BankApp.components.App = function App() {
const mode = store.getMode();
return h(
"div",
{ className: mode === "atm" ? "bank-shell is-atm" : "bank-shell" },
mode === "atm"
? null
: WindowTitleBar({
kicker: "FORGE Finance",
title: "Global Banking Network",
onClose: () => actions.closeBank(),
closeLabel: "Close banking interface",
}),
h("div", { id: "bank-notice-root" }),
mode === "atm"
? h("div", { id: "bank-atm-root" })
: [
h(
"div",
{
className: "bank-scroll-shell",
"data-preserve-scroll-id": "bank-page-scroll",
},
[
h(
"div",
{ className: "bank-layout" },
h("div", { id: "bank-sidebar-root" }),
h(
"main",
{ className: "bank-main" },
h(
"div",
{ className: "bank-page" },
h("div", {
id: "bank-page-header-root",
}),
h(
"p",
{ className: "bank-page-copy" },
"Manage deposits, withdrawals, transfers, and earnings sweeps from the same shared financial console.",
),
h("div", {
className: "bank-page-divider",
}),
h(
"div",
{ className: "bank-page-body" },
h("div", {
id: "bank-summary-section-root",
}),
h("div", {
id: "bank-action-sections-root",
}),
h("div", {
id: "bank-support-section-root",
}),
h("div", {
id: "bank-history-section-root",
}),
),
),
),
),
h("div", { id: "bank-footer-root" }),
],
),
],
);
};
})();
(function () {
const ForgeWebUI = window.ForgeWebUI;
const BankApp = window.BankApp;
const islandDefinitions = [
{
id: "bank-notice-root",
preserveScroll: false,
render: () => BankApp.componentFns.NoticeLayer(),
},
{
id: "bank-sidebar-root",
preserveScroll: false,
render: () => BankApp.componentFns.BankSidebar(),
},
{
id: "bank-page-header-root",
preserveScroll: false,
render: () => BankApp.componentFns.BankPageHeader(),
},
{
id: "bank-summary-section-root",
preserveScroll: false,
render: () => BankApp.componentFns.BankSummarySection(),
},
{
id: "bank-action-sections-root",
preserveScroll: false,
render: () => BankApp.componentFns.BankActionSections(),
},
{
id: "bank-support-section-root",
preserveScroll: false,
render: () => BankApp.componentFns.BankSupportSection(),
},
{
id: "bank-history-section-root",
preserveScroll: false,
render: () => BankApp.componentFns.BankHistorySection(),
},
{
id: "bank-atm-root",
preserveScroll: false,
render: () => BankApp.componentFns.ATMView(),
},
{
id: "bank-footer-root",
preserveScroll: false,
render: () => BankApp.componentFns.BankFooter(),
},
];
function createIslandManager() {
const mounts = new Map();
function sync() {
islandDefinitions.forEach((definition) => {
const container = document.getElementById(definition.id);
const current = mounts.get(definition.id);
if (!container) {
if (current) {
current.handle.dispose();
mounts.delete(definition.id);
}
return;
}
if (current && current.container === container) {
return;
}
if (current) {
current.handle.dispose();
}
const handle = ForgeWebUI.mount(container, definition.render, {
preserveScroll: definition.preserveScroll,
});
mounts.set(definition.id, {
container,
handle,
});
});
}
return {
sync,
};
}
const app = ForgeWebUI.createApp({
name: "bank",
root: "#app",
setup({ root }) {
const islandManager = createIslandManager();
ForgeWebUI.mount(root, () => BankApp.components.App(), {
preserveScroll: false,
});
if (BankApp.bridge) {
BankApp.bridge.notifyReady();
}
ForgeWebUI.effect(() => {
BankApp.store.getMode();
requestAnimationFrame(() => {
islandManager.sync();
});
});
},
});
app.start();
})();