266 lines
9.4 KiB
JavaScript
266 lines
9.4 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);
|