- 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
442 lines
13 KiB
JavaScript
442 lines
13 KiB
JavaScript
/**
|
|
* Player Bank App - Vanilla JS "React-like" 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. Bank Application Components ---
|
|
|
|
// Global State
|
|
const [getView, setView] = createSignal("login"); // 'login', 'dashboard'
|
|
const [getBalance, setBalance] = createSignal(1250000);
|
|
const [getPending, setPending] = createSignal(45250); // Mock pending earnings
|
|
const [getTransactions, setTransactions] = createSignal([
|
|
{
|
|
id: 1,
|
|
type: "credit",
|
|
desc: "Contract Payment: OP-442",
|
|
amount: 150000,
|
|
date: "2026-02-05",
|
|
},
|
|
{
|
|
id: 2,
|
|
type: "debit",
|
|
desc: "Equipment Purchase: Ammunition",
|
|
amount: -4500,
|
|
date: "2026-02-04",
|
|
},
|
|
{
|
|
id: 3,
|
|
type: "debit",
|
|
desc: "Vehicle Maintenance",
|
|
amount: -1200,
|
|
date: "2026-02-03",
|
|
},
|
|
]);
|
|
|
|
// Header
|
|
function Header() {
|
|
return h(
|
|
"div",
|
|
{ className: "header" },
|
|
h(
|
|
"h1",
|
|
{
|
|
style: { cursor: "pointer" },
|
|
onClick: () => setView("login"),
|
|
},
|
|
"Global Financial Network",
|
|
),
|
|
h("p", null, "Secure Banking"),
|
|
);
|
|
}
|
|
|
|
// Login View
|
|
function BankLogin() {
|
|
const handleSubmit = (e) => {
|
|
e.preventDefault();
|
|
setView("dashboard");
|
|
};
|
|
|
|
return h(
|
|
"div",
|
|
{ className: "card", style: { maxWidth: "400px", margin: "0 auto" } },
|
|
h("h2", null, "Secure Access"),
|
|
h(
|
|
"form",
|
|
{ onSubmit: handleSubmit },
|
|
h(
|
|
"div",
|
|
null,
|
|
h("label", null, "Account ID"),
|
|
h("input", { type: "text", placeholder: "xxxx-xxxx-xxxx" }),
|
|
),
|
|
h(
|
|
"div",
|
|
null,
|
|
h("label", null, "Security PIN"),
|
|
h("input", { type: "password", placeholder: "••••" }),
|
|
),
|
|
h(
|
|
"div",
|
|
{ className: "form-actions" },
|
|
h(
|
|
"button",
|
|
{ type: "submit", style: { width: "100%" } },
|
|
"Authenticate",
|
|
),
|
|
h(
|
|
"p",
|
|
{
|
|
style: {
|
|
fontSize: "0.8rem",
|
|
color: "var(--text-muted)",
|
|
marginTop: "1rem",
|
|
},
|
|
},
|
|
"Authorized Personnel Only",
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Transaction History Helper
|
|
function TransactionHistory() {
|
|
const transactions = getTransactions();
|
|
|
|
return h(
|
|
"div",
|
|
{ className: "card" },
|
|
h(
|
|
"h3",
|
|
{
|
|
style: {
|
|
textAlign: "left",
|
|
borderBottom: "1px solid var(--border)",
|
|
paddingBottom: "1rem",
|
|
marginBottom: "1rem",
|
|
},
|
|
},
|
|
"Recent Transactions",
|
|
),
|
|
h(
|
|
"ul",
|
|
{ style: { listStyle: "none", padding: 0 } },
|
|
...transactions.map((tx) =>
|
|
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.desc),
|
|
h(
|
|
"div",
|
|
{
|
|
style: {
|
|
fontSize: "0.85rem",
|
|
color: "var(--text-muted)",
|
|
},
|
|
},
|
|
tx.date,
|
|
),
|
|
),
|
|
h(
|
|
"div",
|
|
{
|
|
style: {
|
|
fontWeight: "700",
|
|
color:
|
|
tx.type === "credit"
|
|
? "#10b981"
|
|
: "#ef4444",
|
|
},
|
|
},
|
|
(tx.type === "credit" ? "+" : "") +
|
|
"$" +
|
|
Math.abs(tx.amount).toLocaleString(),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Transfer Form
|
|
function TransferForm() {
|
|
const handleSubmit = (e) => {
|
|
e.preventDefault();
|
|
const formData = new FormData(e.target);
|
|
const amount = parseFloat(formData.get("amount"));
|
|
|
|
if (amount > 0 && amount <= getBalance()) {
|
|
setBalance((prev) => prev - amount);
|
|
const newTx = {
|
|
id: Date.now(),
|
|
type: "debit",
|
|
desc: "Transfer to " + formData.get("recipient"),
|
|
amount: -amount,
|
|
date: new Date().toISOString().split("T")[0],
|
|
};
|
|
setTransactions((prev) => [newTx, ...prev]);
|
|
}
|
|
};
|
|
|
|
return h(
|
|
"div",
|
|
{ className: "card" },
|
|
h("h2", null, "Wire Transfer"),
|
|
h(
|
|
"form",
|
|
{ onSubmit: handleSubmit },
|
|
h(
|
|
"div",
|
|
null,
|
|
h("label", null, "Recipient Name / GUID"),
|
|
h("input", {
|
|
name: "recipient",
|
|
type: "text",
|
|
placeholder: "Enter Name or GUID",
|
|
}),
|
|
),
|
|
h(
|
|
"div",
|
|
null,
|
|
h("label", null, "Amount"),
|
|
h("input", {
|
|
name: "amount",
|
|
type: "number",
|
|
placeholder: "0.00",
|
|
}),
|
|
),
|
|
h("button", { type: "submit" }, "Send Funds"),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Dashboard View
|
|
function BankDashboard() {
|
|
return h(
|
|
"div",
|
|
{ className: "content" },
|
|
// Top Row: Balance
|
|
h(
|
|
"div",
|
|
{ className: "card", style: { gridColumn: "span 2" } },
|
|
h(
|
|
"h2",
|
|
{
|
|
style: {
|
|
fontSize: "1.2rem",
|
|
color: "var(--text-muted)",
|
|
textTransform: "uppercase",
|
|
letterSpacing: "0.05em",
|
|
},
|
|
},
|
|
"Total Balance",
|
|
),
|
|
h(
|
|
"div",
|
|
{
|
|
style: {
|
|
fontSize: "2.8rem",
|
|
fontWeight: "800",
|
|
color: "var(--primary-hover)",
|
|
margin: "1rem 0",
|
|
},
|
|
},
|
|
"$" + getBalance().toLocaleString(),
|
|
),
|
|
h(
|
|
"div",
|
|
{
|
|
style: {
|
|
textAlign: "center",
|
|
marginBottom: "1.5rem",
|
|
color: "var(--text-muted)",
|
|
fontSize: "1.2rem",
|
|
},
|
|
},
|
|
"Pending: ",
|
|
h(
|
|
"span",
|
|
{ style: { color: "#fbbf24", fontWeight: "bold" } },
|
|
"$" + getPending().toLocaleString(),
|
|
),
|
|
),
|
|
h(
|
|
"div",
|
|
{
|
|
style: {
|
|
display: "flex",
|
|
gap: "1rem",
|
|
justifyContent: "center",
|
|
},
|
|
},
|
|
h(
|
|
"button",
|
|
{
|
|
onClick: () => {
|
|
const pending = getPending();
|
|
if (pending > 0) {
|
|
setBalance((prev) => prev + pending);
|
|
setPending(0);
|
|
const newTx = {
|
|
id: Date.now(),
|
|
type: "credit",
|
|
desc: "Field Deposit",
|
|
amount: pending,
|
|
date: new Date()
|
|
.toISOString()
|
|
.split("T")[0],
|
|
};
|
|
setTransactions((prev) => [newTx, ...prev]);
|
|
}
|
|
},
|
|
style: {
|
|
opacity: getPending() > 0 ? "1" : "0.5",
|
|
cursor: getPending() > 0 ? "pointer" : "default",
|
|
},
|
|
},
|
|
"Deposit Pending",
|
|
),
|
|
h(
|
|
"button",
|
|
{
|
|
style: {
|
|
background: "var(--bg-surface-hover)",
|
|
color: "var(--text-main)",
|
|
border: "1px solid var(--border)",
|
|
},
|
|
},
|
|
"Statement",
|
|
),
|
|
),
|
|
),
|
|
// Middle Row: Transfer Form
|
|
TransferForm(),
|
|
// Bottom Row: History (Full Width in simplified grid, or separate)
|
|
TransactionHistory(),
|
|
);
|
|
}
|
|
|
|
// Footer
|
|
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"),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Main App
|
|
function App() {
|
|
const view = getView();
|
|
|
|
let mainContent;
|
|
if (view === "login") {
|
|
mainContent = BankLogin();
|
|
} else if (view === "dashboard") {
|
|
mainContent = BankDashboard();
|
|
}
|
|
|
|
return h(
|
|
"main",
|
|
null,
|
|
h("div", { className: "container" }, Header(), mainContent),
|
|
Footer(),
|
|
);
|
|
}
|
|
|
|
// Mount
|
|
const root = document.getElementById("app");
|
|
render(App, root);
|