Jacob Schmidt d178e39164 Refactor client UI stores and normalize docs formatting
- Rework org and store UI state modules (rename/move store/getter files, add runtime and bridge wiring)
- Update store UI components and page structure (navbar/cart split, new StoreView flow)
- Apply broad markdown/YAML/HTML/CSS/JS formatting cleanup across docs, templates, and workflows
2026-03-10 19:13:30 -05:00

576 lines
17 KiB
JavaScript

/**
* Bank App - Vanilla JS Implementation matching WIP UI
*/
//=============================================================================
// #region LIBRARY - DOM Helper
//=============================================================================
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 if (
key === "disabled" ||
key === "checked" ||
key === "selected" ||
key === "readonly"
) {
if (value) el[key] = true;
} 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) => {
if (c instanceof Node) el.appendChild(c);
});
}
});
return el;
}
let _rootContainer = null;
let _rootComponent = null;
function render(component, container) {
_rootContainer = container;
_rootComponent = component;
_render();
}
function _render() {
if (_rootContainer && _rootComponent) {
_rootContainer.innerHTML = "";
_rootContainer.appendChild(_rootComponent());
}
}
//=============================================================================
// #region UI COMPONENTS
//=============================================================================
function Navbar() {
const state = store.getState();
const uid = state.uid || "Unknown";
return h(
"nav",
{ className: "navbar" },
h(
"div",
{ className: "navbar-inner" },
h(
"div",
{ className: "navbar-brand" },
h(
"span",
{ className: "navbar-title" },
"FDIC - Global Financial Network",
),
),
h(
"div",
{ className: "navbar-profile" },
h(
"div",
{ className: "profile-info" },
h("span", { className: "profile-label" }, "Account"),
h("span", { className: "profile-id" }, uid),
),
),
),
);
}
function WindowTitleBar() {
return h(
"div",
{ className: "window-titlebar" },
h(
"div",
{ className: "window-titlebar-brand" },
h(
"span",
{ className: "window-titlebar-kicker" },
"FDIC Workspace",
),
h(
"span",
{ className: "window-titlebar-title" },
"Global Financial Network",
),
),
h(
"div",
{ className: "window-titlebar-controls" },
h(
"button",
{
type: "button",
className: "window-control-btn",
disabled: true,
title: "Minimize unavailable",
"aria-label": "Minimize unavailable",
},
"-",
),
h(
"button",
{
type: "button",
className: "window-control-btn",
disabled: true,
title: "Maximize unavailable",
"aria-label": "Maximize unavailable",
},
"[ ]",
),
h(
"button",
{
type: "button",
className: "window-control-btn is-close",
onClick: () => sendEvent("bank::close", {}),
title: "Close",
"aria-label": "Close banking interface",
},
"X",
),
),
);
}
function TransactionHistory() {
const state = store.getState();
const transactions = state.transactions || [];
return h(
"div",
{ className: "card" },
h(
"h3",
{
style: {
textAlign: "left",
borderBottom: "1px solid var(--border)",
paddingBottom: "1rem",
marginBottom: "1rem",
},
},
"Recent Transactions",
),
transactions.length === 0
? h(
"p",
{ style: { color: "var(--text-muted)" } },
"No transactions yet",
)
: h(
"ul",
{ style: { listStyle: "none", padding: 0, margin: 0 } },
transactions.slice(0, 10).map((tx) => {
const isCredit = tx.type === "Deposit";
return h(
"li",
{
style: {
display: "flex",
justifyContent: "space-between",
padding: "0.75rem 0",
borderBottom:
"1px solid var(--bg-surface-hover)",
},
},
h(
"div",
{ style: { textAlign: "left" } },
h(
"div",
{ style: { fontWeight: "500" } },
tx.type,
),
h(
"div",
{
style: {
fontSize: "0.85rem",
color: "var(--text-muted)",
},
},
tx.date,
),
),
h(
"div",
{
style: {
fontWeight: "700",
color: isCredit ? "#10b981" : "#ef4444",
},
},
(isCredit ? "+" : "-") +
"$" +
Math.abs(tx.amount).toLocaleString(),
),
);
}),
),
);
}
function DepositWithdrawForm() {
const state = store.getState();
const bankBalance = state.accounts.bank;
const cashBalance = state.accounts.cash;
const getAmount = () => {
const input = document.getElementById("deposit-withdraw-amount");
return parseFloat(input?.value) || 0;
};
const clearInput = () => {
const input = document.getElementById("deposit-withdraw-amount");
if (input) input.value = "";
};
const handleDeposit = () => {
const amount = getAmount();
if (!amount || amount <= 0) {
console.log("Please enter a valid amount");
return;
}
if (amount > cashBalance) {
console.log("Insufficient cash");
return;
}
sendEvent("bank::deposit", { amount });
store.dispatch(deposit(amount));
clearInput();
};
const handleWithdraw = () => {
const amount = getAmount();
if (!amount || amount <= 0) {
console.log("Please enter a valid amount");
return;
}
if (amount > bankBalance) {
console.log("Insufficient funds");
return;
}
sendEvent("bank::withdraw", { amount });
store.dispatch(withdraw(amount));
clearInput();
};
return h(
"div",
{ className: "card" },
h("h2", null, "Deposit / Withdraw"),
h(
"div",
{ className: "balance-info" },
h(
"div",
{ className: "balance-info-item" },
h("span", { className: "balance-info-label" }, "Cash"),
h(
"span",
{ className: "balance-info-value cash" },
"$" + cashBalance.toLocaleString(),
),
),
h(
"div",
{ className: "balance-info-item" },
h("span", { className: "balance-info-label" }, "Bank"),
h(
"span",
{ className: "balance-info-value" },
"$" + bankBalance.toLocaleString(),
),
),
),
h(
"div",
{ className: "deposit-withdraw-form" },
h("input", {
id: "deposit-withdraw-amount",
type: "number",
placeholder: "Enter amount...",
min: "1",
}),
h(
"div",
{ className: "deposit-withdraw-buttons" },
h(
"button",
{ onClick: handleDeposit, disabled: cashBalance <= 0 },
"Deposit",
),
h(
"button",
{ onClick: handleWithdraw, disabled: bankBalance <= 0 },
"Withdraw",
),
),
),
);
}
function TransferForm() {
const state = store.getState();
const players = state.accounts.players || {};
const currentUid = state.uid;
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const amount = parseFloat(formData.get("amount"));
const playerId = formData.get("playerId");
if (!amount || amount <= 0) {
console.log("Please enter a valid amount");
return;
}
const currentState = store.getState();
if (!playerId) {
console.log("Please select a recipient");
return;
}
if (amount > currentState.accounts.bank) {
console.log("Insufficient funds");
return;
}
sendEvent("bank::transfer", { from: "bank", amount, target: playerId });
store.dispatch(transfer("bank", amount, "player"));
e.target.reset();
};
// Build player options
const playerOptions = [
h(
"option",
{ value: "", disabled: true, selected: true },
"Select player...",
),
];
Object.keys(players).forEach((uid) => {
if (uid !== currentUid && players[uid]?.name) {
playerOptions.push(h("option", { value: uid }, players[uid].name));
}
});
return h(
"div",
{ className: "card" },
h("h2", null, "Wire Transfer"),
h(
"form",
{ onSubmit: handleSubmit },
h(
"div",
null,
h("label", null, "Recipient"),
h("select", { name: "playerId" }, playerOptions),
),
h(
"div",
null,
h("label", null, "Amount"),
h("input", {
name: "amount",
type: "number",
placeholder: "0.00",
}),
),
h("button", { type: "submit" }, "Send Funds"),
),
);
}
function BankDashboard() {
const state = store.getState();
const bankBalance = state.accounts.bank;
const earnings = state.accounts.earnings;
return h(
"div",
{ className: "content" },
h(
"div",
{ className: "card", style: { gridColumn: "span 2" } },
h(
"h2",
{
style: {
fontSize: "1.2rem",
color: "var(--text-muted)",
textTransform: "uppercase",
letterSpacing: "0.05em",
},
},
"Account Balance",
),
h(
"div",
{
style: {
fontSize: "2.8rem",
fontWeight: "800",
color: "var(--primary-hover)",
margin: "1rem 0",
},
},
"$" + bankBalance.toLocaleString(),
),
h(
"div",
{
style: {
textAlign: "center",
color: "var(--text-muted)",
fontSize: "1.1rem",
marginBottom: "1rem",
},
},
"Pending: ",
h(
"span",
{ style: { color: "#fbbf24", fontWeight: "bold" } },
"$" + earnings.toLocaleString(),
),
),
h(
"div",
{ className: "deposit-earnings-button" },
h(
"button",
{
onClick: () => {
sendEvent("bank::depositEarnings", {
amount: earnings,
});
store.dispatch(depositEarnings(earnings));
},
disabled: earnings <= 0,
style: { width: "25%" },
},
"Deposit Earnings",
),
),
),
DepositWithdrawForm(),
TransferForm(),
h("div", { style: { gridColumn: "span 2" } }, TransactionHistory()),
);
}
function Footer() {
return h(
"div",
{ className: "footer" },
h(
"div",
{ className: "wrapper" },
h(
"div",
null,
h("h3", null, "Secure Banking"),
h(
"ul",
{ style: { listStyleType: "none", padding: 0 } },
h("li", null, "FDIC Insured"),
h("li", null, "Fraud Protection"),
h("li", null, "24/7 Support"),
h("li", null, "API Access"),
),
),
h(
"div",
null,
h("h3", null, "Notices"),
h(
"ul",
{ style: { listStyleType: "none", padding: 0 } },
h("li", null, "Terms of Service"),
h("li", null, "Privacy Policy"),
h("li", null, "Interest Rates"),
h("li", null, "Report Fraud"),
),
),
),
);
}
function App() {
return h(
"div",
{ className: "app-shell" },
WindowTitleBar(),
h(
"main",
null,
Navbar(),
h("div", { className: "container" }, BankDashboard()),
Footer(),
),
);
}
//=============================================================================
// #region ARMA 3 INTEGRATION
//=============================================================================
function sendEvent(event, data) {
if (typeof A3API !== "undefined") {
A3API.SendAlert(JSON.stringify({ event, data }));
} else {
console.log("Event:", event, "Data:", data);
}
}
//=============================================================================
// #region INITIALIZATION
//=============================================================================
let initialized = false;
function initBank() {
if (initialized) return;
const root = document.getElementById("app");
if (root) {
if (typeof store !== "undefined") {
store.subscribe(() => _render());
}
render(App, root);
initialized = true;
console.log("[Bank] Interface initialized");
}
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initBank);
} else {
initBank();
}