/** * 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(); }