// ============================================================ // Saeedalo — Client-Side App Engine // User management, bank account generation, session handling // ============================================================ const DB_KEY = 'saeedalo_users'; const SESS_KEY = 'saeedalo_session'; // ── User Storage ───────────────────────────────────────────── function getUsers() { try { return JSON.parse(localStorage.getItem(DB_KEY) || '[]'); } catch { return []; } } function saveUsers(users) { localStorage.setItem(DB_KEY, JSON.stringify(users)); } function getUser(id) { return getUsers().find(u => u.id === id) || null; } function updateUser(id, patch) { const users = getUsers(); const idx = users.findIndex(u => u.id === id); if (idx >= 0) { users[idx] = { ...users[idx], ...patch }; saveUsers(users); return users[idx]; } return null; } // ── Session ─────────────────────────────────────────────────── function getSession() { try { return JSON.parse(sessionStorage.getItem(SESS_KEY) || localStorage.getItem(SESS_KEY) || 'null'); } catch { return null; } } function setSession(user, remember = false) { const s = JSON.stringify({ id: user.id, email: user.email, ts: Date.now() }); sessionStorage.setItem(SESS_KEY, s); if (remember) localStorage.setItem(SESS_KEY, s); } function clearSession() { sessionStorage.removeItem(SESS_KEY); localStorage.removeItem(SESS_KEY); } function requireAuth() { const s = getSession(); if (!s) { window.location.href = '/login.html'; return null; } const user = getUser(s.id); if (!user) { clearSession(); window.location.href = '/login.html'; return null; } return user; } // ── Bank Account Generator ──────────────────────────────────── function randDigits(n) { return Array.from({ length: n }, () => Math.floor(Math.random() * 10)).join(''); } function generateIBAN(country) { const map = { DE: '1301', GB: '2000', FR: '3000', AE: '9999' }; const bban = randDigits(18); return `${country}${map[country] || '00'}${bban}`; } function generateBankAccounts() { return { USD: { accountNumber: randDigits(10), routingNumber: '026073150', bankName: 'Saeedalo Bank (US)', swift: 'SAEDUS33' }, EUR: { iban: generateIBAN('DE'), bic: 'SAEDDE1XXXX', bankName: 'Saeedalo Bank (EU)' }, GBP: { accountNumber: randDigits(8), sortCode: '20-00-00', bankName: 'Saeedalo Bank (UK)', swift: 'SAEDGB2L' }, AED: { iban: generateIBAN('AE'), bic: 'SAEDAEADXXX', bankName: 'Saeedalo Bank (UAE)' }, SAR: { iban: 'SA' + randDigits(22), bankName: 'Saeedalo Bank (KSA)' }, PKR: { accountNumber: randDigits(16), bankName: 'Saeedalo Bank (PK)' }, INR: { accountNumber: randDigits(12), ifsc: 'SAED0001234', bankName: 'Saeedalo Bank (India)' }, }; } // ── User Registration ───────────────────────────────────────── function registerUser(data) { const users = getUsers(); if (users.find(u => u.email.toLowerCase() === data.email.toLowerCase())) { throw new Error('An account with this email already exists. Please sign in.'); } const id = 'USR-' + Date.now() + '-' + randDigits(4); const bankAccounts = generateBankAccounts(); const wallets = Object.keys(bankAccounts).map((cur, i) => ({ id: 'WAL-' + randDigits(8), currency: cur, balance: 0, reserved: 0, status: 'ACTIVE', isPrimary: i === 0, ...bankAccounts[cur], createdAt: new Date().toISOString() })); const user = { id, email: data.email.toLowerCase(), passwordHash: btoa(data.password), // simple encoding for demo firstName: data.firstName, lastName: data.lastName, phone: data.phone || '', country: data.country || '', accountType: data.accountType || 'personal', kycStatus: 'NOT_STARTED', kycTier: 'TIER_0', businessProfile: data.businessProfile || null, wallets, transactions: [], cards: [], referralCode: 'SAE' + randDigits(6), createdAt: new Date().toISOString(), lastLoginAt: new Date().toISOString(), notifications: [ { id:'N1', type:'WELCOME', title:'Welcome to Saeedalo!', message:'Your account is ready. Complete verification to unlock higher limits.', read:false, createdAt:new Date().toISOString() }, { id:'N2', type:'KYC', title:'Verify your identity', message:'Complete Tier 1 verification to increase your limits to $10,000/day.', read:false, createdAt:new Date().toISOString() } ] }; users.push(user); saveUsers(users); return user; } // ── Login ───────────────────────────────────────────────────── function loginUser(email, password, remember) { const users = getUsers(); const user = users.find(u => u.email.toLowerCase() === email.toLowerCase()); if (!user) throw new Error('No account found with this email address.'); if (user.passwordHash !== btoa(password)) throw new Error('Incorrect password. Please try again.'); updateUser(user.id, { lastLoginAt: new Date().toISOString() }); setSession(user, remember); return user; } // ── KYC ────────────────────────────────────────────────────── function submitKYC(userId, data) { return updateUser(userId, { kycData: data, kycStatus: 'UNDER_REVIEW', kycSubmittedAt: new Date().toISOString() }); } function approveKYC(userId, tier) { return updateUser(userId, { kycStatus: 'APPROVED', kycTier: tier, kycApprovedAt: new Date().toISOString() }); } // ── Transactions ────────────────────────────────────────────── function addTransaction(userId, tx) { const user = getUser(userId); if (!user) return; const newTx = { id: 'TXN-' + randDigits(10), ...tx, createdAt: new Date().toISOString(), status: 'COMPLETED' }; const txs = [...(user.transactions || []), newTx]; updateUser(userId, { transactions: txs.slice(-200) }); // keep last 200 return newTx; } // ── Wallet Credit/Debit ─────────────────────────────────────── function creditWallet(userId, currency, amount, description) { const user = getUser(userId); if (!user) return; const wallets = user.wallets.map(w => w.currency === currency ? { ...w, balance: (Number(w.balance) + amount) } : w ); updateUser(userId, { wallets }); addTransaction(userId, { type:'DEPOSIT', currency, amount, description, walletId: user.wallets.find(w=>w.currency===currency)?.id }); } function debitWallet(userId, currency, amount, description) { const user = getUser(userId); if (!user) return false; const wallet = user.wallets.find(w => w.currency === currency); if (!wallet || Number(wallet.balance) < amount) return false; const wallets = user.wallets.map(w => w.currency === currency ? { ...w, balance: Math.max(0, Number(w.balance) - amount) } : w ); updateUser(userId, { wallets }); addTransaction(userId, { type:'WITHDRAWAL', currency, amount: -amount, description, walletId: wallet.id }); return true; } // ── FX Rates (approximate) ──────────────────────────────────── const FX = { USD:1,EUR:0.921,GBP:0.786,AED:3.672,SAR:3.751,PKR:278.5,INR:83.47,CAD:1.364,AUD:1.512,SGD:1.342,JPY:157.4,CHF:0.897,CNY:7.25 }; function convert(amount, from, to) { const rate = FX[to] / FX[from]; const fee = amount * 0.006; return { received: ((amount - fee) * rate).toFixed(2), rate: rate.toFixed(4), fee: fee.toFixed(2) }; } // ── KYC Tier Limits ─────────────────────────────────────────── const LIMITS = { TIER_0: { single:500, daily:500, monthly:1000 }, TIER_1: { single:5000, daily:10000, monthly:25000 }, TIER_2: { single:50000, daily:100000, monthly:500000 }, TIER_3: { single:1e6, daily:5e6, monthly:50e6 }, }; function getLimits(tier) { return LIMITS[tier] || LIMITS.TIER_0; } // ── Helpers ─────────────────────────────────────────────────── function formatCurrency(amount, currency) { return new Intl.NumberFormat('en-US', { style:'currency', currency, minimumFractionDigits:2 }).format(amount); } function formatDate(iso) { return new Intl.DateTimeFormat('en-US', { month:'short', day:'numeric', hour:'2-digit', minute:'2-digit' }).format(new Date(iso)); } function maskAccount(num) { if (!num) return ''; return '····' + num.slice(-4); } function totalBalanceUSD(wallets) { return wallets.reduce((s,w) => s + (Number(w.balance) / (FX[w.currency] || 1)), 0); } function getInitials(user) { return ((user.firstName||'')[0]+(user.lastName||'')[0]).toUpperCase() || 'U'; }