345 lines
12 KiB
JavaScript
345 lines
12 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)
|
|
),
|
|
h('button', {
|
|
className: 'btn-signout',
|
|
onClick: () => sendEvent('bank::close', {})
|
|
}, 'Sign Out')
|
|
)
|
|
)
|
|
);
|
|
}
|
|
|
|
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('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();
|
|
}
|