Implemented features: - High-performance Rust extension with Redis persistence - Actor/player management with loadout, position, and state tracking - Banking system with deposit, withdraw, and transfer operations - Physical and virtual garage/locker systems for vehicle and equipment storage - Organization management with member tracking and permissions - Client-side UI with React-like state management - Server-side event-driven architecture with CBA Events - Security: Self-transfer prevention at multiple layers - Logging system with per-module log files - ICOM module for inter-server communication Co-Authored-By: Warp <agent@warp.dev>
381 lines
11 KiB
JavaScript
381 lines
11 KiB
JavaScript
/**
|
|
* ATM Interface
|
|
* Handles banking transactions with PIN authentication
|
|
*/
|
|
|
|
// ============================================================================
|
|
// STATE
|
|
// ============================================================================
|
|
|
|
let enteredPin = '';
|
|
let currentView = 'welcomeView';
|
|
let previousView = 'welcomeView';
|
|
// ============================================================================
|
|
// VIEW MANAGEMENT
|
|
// ============================================================================
|
|
|
|
function showView(viewId) {
|
|
// Hide all views
|
|
document.querySelectorAll('.atm-view').forEach(view => {
|
|
view.style.display = 'none';
|
|
});
|
|
|
|
// Show selected view
|
|
const view = document.getElementById(viewId);
|
|
if (view) {
|
|
view.style.display = 'flex';
|
|
previousView = currentView;
|
|
currentView = viewId;
|
|
|
|
// Update balance displays when showing certain views
|
|
if (viewId === 'menuView' || viewId === 'balanceView' || viewId === 'depositView') {
|
|
updateBalances();
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// PIN AUTHENTICATION
|
|
// ============================================================================
|
|
|
|
function generateKeypad() {
|
|
const keypad = document.getElementById('keypad');
|
|
if (!keypad) return;
|
|
|
|
// Define keypad layout
|
|
const keys = [
|
|
{ value: '1', label: '1', type: 'number' },
|
|
{ value: '2', label: '2', type: 'number' },
|
|
{ value: '3', label: '3', type: 'number' },
|
|
{ value: '4', label: '4', type: 'number' },
|
|
{ value: '5', label: '5', type: 'number' },
|
|
{ value: '6', label: '6', type: 'number' },
|
|
{ value: '7', label: '7', type: 'number' },
|
|
{ value: '8', label: '8', type: 'number' },
|
|
{ value: '9', label: '9', type: 'number' },
|
|
{ value: 'clear', label: 'Clear', type: 'action', class: 'key-clear' },
|
|
{ value: '0', label: '0', type: 'number' },
|
|
{ value: 'enter', label: 'Enter', type: 'action', class: 'key-enter' }
|
|
];
|
|
|
|
// Clear existing keypad
|
|
keypad.innerHTML = '';
|
|
|
|
// Generate buttons
|
|
keys.forEach(key => {
|
|
const button = document.createElement('button');
|
|
button.className = `key-btn${key.class ? ' ' + key.class : ''}`;
|
|
button.textContent = key.label;
|
|
|
|
// Add click handler
|
|
if (key.type === 'number') {
|
|
button.onclick = () => enterPin(key.value);
|
|
} else if (key.value === 'clear') {
|
|
button.onclick = () => clearPin();
|
|
} else if (key.value === 'enter') {
|
|
button.onclick = () => submitPin();
|
|
}
|
|
|
|
keypad.appendChild(button);
|
|
});
|
|
}
|
|
|
|
function enterPin(digit) {
|
|
if (enteredPin.length < 4) {
|
|
enteredPin += digit;
|
|
updatePinDisplay();
|
|
}
|
|
}
|
|
|
|
function clearPin() {
|
|
enteredPin = '';
|
|
updatePinDisplay();
|
|
}
|
|
|
|
function updatePinDisplay() {
|
|
const dots = document.querySelectorAll('.pin-dot');
|
|
dots.forEach((dot, index) => {
|
|
if (index < enteredPin.length) {
|
|
dot.classList.add('filled');
|
|
} else {
|
|
dot.classList.remove('filled');
|
|
}
|
|
});
|
|
}
|
|
|
|
function submitPin() {
|
|
if (enteredPin.length !== 4) {
|
|
showError('Please enter a 4-digit PIN');
|
|
return;
|
|
}
|
|
|
|
// In a real implementation, this would validate with the server
|
|
const currentState = store.getState();
|
|
if (enteredPin === currentState.pin) {
|
|
enteredPin = '';
|
|
updatePinDisplay();
|
|
showView('menuView');
|
|
} else {
|
|
showError('Incorrect PIN');
|
|
clearPin();
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// BALANCE MANAGEMENT
|
|
// ============================================================================
|
|
|
|
function updateBalances() {
|
|
const currentState = store.getState();
|
|
|
|
// Update all balance displays
|
|
const cashElements = ['cashBalance', 'cashBalanceDetail', 'availableCash'];
|
|
const bankElements = ['bankBalance', 'bankBalanceDetail'];
|
|
|
|
cashElements.forEach(id => {
|
|
const el = document.getElementById(id);
|
|
if (el) el.textContent = `$${currentState.accounts.cash.toLocaleString()}`;
|
|
});
|
|
|
|
bankElements.forEach(id => {
|
|
const el = document.getElementById(id);
|
|
if (el) el.textContent = `$${currentState.accounts.bank.toLocaleString()}`;
|
|
});
|
|
|
|
const totalEl = document.getElementById('totalBalance');
|
|
if (totalEl) {
|
|
const total = currentState.accounts.cash + currentState.accounts.bank;
|
|
totalEl.textContent = `$${total.toLocaleString()}`;
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// WITHDRAW OPERATIONS
|
|
// ============================================================================
|
|
|
|
function withdrawAmount(amount) {
|
|
const currentState = store.getState();
|
|
|
|
if (amount > currentState.accounts.bank) {
|
|
showError('Insufficient funds');
|
|
return;
|
|
}
|
|
|
|
store.dispatch(withdraw(amount));
|
|
sendEvent('atm::withdraw', { amount: amount });
|
|
showSuccess(`Withdrew $${amount.toLocaleString()}`);
|
|
}
|
|
|
|
function withdrawCustom() {
|
|
const input = document.getElementById('withdrawInput');
|
|
const amount = parseFloat(input.value);
|
|
|
|
if (!amount || amount <= 0) {
|
|
showError('Please enter a valid amount');
|
|
return;
|
|
}
|
|
|
|
const currentState = store.getState();
|
|
if (amount > currentState.accounts.bank) {
|
|
showError('Insufficient funds');
|
|
return;
|
|
}
|
|
|
|
store.dispatch(withdraw(amount));
|
|
sendEvent('atm::withdraw', { amount: amount });
|
|
input.value = '';
|
|
showSuccess(`Withdrew $${amount.toLocaleString()}`);
|
|
}
|
|
|
|
// ============================================================================
|
|
// DEPOSIT OPERATIONS
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Deposits specified amount into bank account
|
|
* @deprecated Use store actions instead
|
|
*/
|
|
function depositAmount() {
|
|
const input = document.getElementById('depositInput');
|
|
const amount = parseFloat(input.value);
|
|
|
|
if (!amount || amount <= 0) {
|
|
showError('Please enter a valid amount');
|
|
return;
|
|
}
|
|
|
|
const currentState = store.getState();
|
|
if (amount > currentState.accounts.cash) {
|
|
showError('Insufficient cash');
|
|
return;
|
|
}
|
|
|
|
store.dispatch(deposit(amount));
|
|
sendEvent('atm::deposit', { amount: amount });
|
|
input.value = '';
|
|
showSuccess(`Deposited $${amount.toLocaleString()}`);
|
|
}
|
|
/**
|
|
* Deposits all available cash into bank account
|
|
* @deprecated Use store actions instead
|
|
*/
|
|
function depositAll() {
|
|
const currentState = store.getState();
|
|
|
|
if (currentState.accounts.cash <= 0) {
|
|
showError('No cash to deposit');
|
|
return;
|
|
}
|
|
|
|
const amount = currentState.accounts.cash;
|
|
store.dispatch(deposit(amount));
|
|
sendEvent('atm::deposit', { amount: amount });
|
|
showSuccess(`Deposited $${amount.toLocaleString()}`);
|
|
}
|
|
|
|
// ============================================================================
|
|
// TRANSFER OPERATIONS
|
|
// ============================================================================
|
|
/**
|
|
* Transfers specified amount from bank account to player account
|
|
* @deprecated Use store actions instead
|
|
*/
|
|
function transferFunds() {
|
|
const playerIdInput = document.getElementById('transferPlayerId');
|
|
const amountInput = document.getElementById('transferAmount');
|
|
|
|
const playerId = playerIdInput.value.trim();
|
|
const amount = parseFloat(amountInput.value);
|
|
|
|
if (!playerId) {
|
|
showError('Please enter a player ID');
|
|
return;
|
|
}
|
|
|
|
if (!amount || amount <= 0) {
|
|
showError('Please enter a valid amount');
|
|
return;
|
|
}
|
|
|
|
const currentState = store.getState();
|
|
if (amount > currentState.accounts.bank) {
|
|
showError('Insufficient funds');
|
|
return;
|
|
}
|
|
|
|
store.dispatch(transfer('bank', amount, 'player'));
|
|
sendEvent('atm::transfer', {
|
|
playerId: playerId,
|
|
amount: amount
|
|
});
|
|
|
|
playerIdInput.value = '';
|
|
amountInput.value = '';
|
|
|
|
showSuccess(`Transferred $${amount.toLocaleString()} to Player ${playerId}`);
|
|
}
|
|
|
|
// ============================================================================
|
|
// RESULT SCREENS
|
|
// ============================================================================
|
|
|
|
function showSuccess(message) {
|
|
document.getElementById('successMessage').textContent = message;
|
|
showView('successView');
|
|
updateBalances();
|
|
}
|
|
|
|
function showError(message) {
|
|
document.getElementById('errorMessage').textContent = message;
|
|
showView('errorView');
|
|
}
|
|
|
|
function goBackFromError() {
|
|
// If error happened during PIN entry, go back to PIN view
|
|
// Otherwise go back to menu view
|
|
if (previousView === 'pinView') {
|
|
showView('pinView');
|
|
} else {
|
|
showView('menuView');
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// ATM CONTROL
|
|
// ============================================================================
|
|
|
|
function exitATM() {
|
|
enteredPin = '';
|
|
updatePinDisplay();
|
|
sendEvent('atm::close', {});
|
|
showView('welcomeView');
|
|
}
|
|
|
|
// ============================================================================
|
|
// ARMA 3 INTEGRATION
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Sends an event to Arma 3
|
|
* @param {string} event - Event name
|
|
* @param {Object} data - Event data
|
|
*/
|
|
function sendEvent(event, data) {
|
|
if (typeof A3API !== 'undefined') {
|
|
A3API.SendAlert(JSON.stringify({
|
|
event: event,
|
|
data: data
|
|
}));
|
|
} else {
|
|
console.log('Event:', event, 'Data:', data);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// INITIALIZATION
|
|
// ============================================================================
|
|
|
|
function initATM() {
|
|
// Subscribe to store updates
|
|
if (typeof store !== 'undefined') {
|
|
store.subscribe(() => {
|
|
updateBalances();
|
|
});
|
|
}
|
|
|
|
// Generate keypad
|
|
generateKeypad();
|
|
|
|
// Show welcome screen
|
|
showView('welcomeView');
|
|
|
|
// Update initial balances
|
|
updateBalances();
|
|
|
|
console.log('[ATM] Interface initialized');
|
|
}
|
|
|
|
// Auto-initialize
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', initATM);
|
|
} else {
|
|
initATM();
|
|
}
|
|
|
|
// ============================================================================
|
|
// GLOBAL EXPORTS
|
|
// ============================================================================
|
|
|
|
window.showView = showView;
|
|
window.generateKeypad = generateKeypad;
|
|
window.enterPin = enterPin;
|
|
window.clearPin = clearPin;
|
|
window.submitPin = submitPin;
|
|
window.withdrawAmount = withdrawAmount;
|
|
window.withdrawCustom = withdrawCustom;
|
|
window.depositAmount = depositAmount;
|
|
window.depositAll = depositAll;
|
|
window.transferFunds = transferFunds;
|
|
window.goBackFromError = goBackFromError;
|
|
window.exitATM = exitATM;
|