You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Web-Dev-For-Beginners/7-bank-project/solution/app.js

331 lines
9.3 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const serverUrl = 'http://localhost:5000/api';
const storageKey = 'savedAccount';
const accountsKey = 'accounts'; // New key for all accounts
// ---------------------------------------------------------------------------
// Router
// ---------------------------------------------------------------------------
const routes = {
  '/dashboard': { title: 'My Account', templateId: 'dashboard', init: refresh },
  '/login': { title: 'Login', templateId: 'login' }
};
function navigate(path) {
  window.history.pushState({}, path, window.location.origin + path);
  updateRoute();
}
function updateRoute() {
  const path = window.location.pathname;
  const route = routes[path];
  if (!route) {
    return navigate('/dashboard');
  }
  const template = document.getElementById(route.templateId);
  const view = template.content.cloneNode(true);
  const app = document.getElementById('app');
  app.innerHTML = '';
  app.appendChild(view);
  if (typeof route.init === 'function') {
    route.init();
  }
  document.title = route.title;
}
// ---------------------------------------------------------------------------
// API interactions (replaced with localStorage logic)
// ---------------------------------------------------------------------------
function getAccounts() {
  return JSON.parse(localStorage.getItem(accountsKey) || '[]');
}
function saveAccounts(accounts) {
  localStorage.setItem(accountsKey, JSON.stringify(accounts));
}
function findAccount(user) {
  const accounts = getAccounts();
  return accounts.find(acc => acc.user === user) || null;
}
async function getAccount(user) {
  // Simulate async
  return new Promise(resolve => {
    setTimeout(() => {
      const acc = findAccount(user);
      if (!acc) resolve({ error: 'Account not found' });
      else resolve(acc);
    }, 100);
  });
}
async function createAccount(accountJson) {
  return new Promise(resolve => {
    setTimeout(() => {
      let data;
      try {
        data = JSON.parse(accountJson);
      } catch (e) {
        return resolve({ error: 'Malformed account data' });
      }
      if (!data.user) return resolve({ error: 'Username required' });
      if (findAccount(data.user)) return resolve({ error: 'User already exists' });
      // Set up initial account structure
      const newAcc = {
        user: data.user,
        description: data.description || '',
        balance: 0,
        currency: data.currency || 'USD',
        transactions: []
      };
      const accounts = getAccounts();
      accounts.push(newAcc);
      saveAccounts(accounts);
      resolve(newAcc);
    }, 100);
  });
}
async function createTransaction(user, transactionJson) {
  return new Promise(resolve => {
    setTimeout(() => {
      const accounts = getAccounts();
      const idx = accounts.findIndex(acc => acc.user === user);
      if (idx === -1) return resolve({ error: 'Account not found' });
      const tx = JSON.parse(transactionJson);
      tx.amount = parseFloat(tx.amount);
      tx.date = tx.date || new Date().toISOString().slice(0, 10);
      accounts[idx].balance += tx.amount;
      accounts[idx].transactions.push(tx);
      saveAccounts(accounts);
      resolve(tx);
    }, 100);
  });
}
// ---------------------------------------------------------------------------
// Global state
// ---------------------------------------------------------------------------
let state = Object.freeze({
  account: null
});
function updateState(property, newData) {
  state = Object.freeze({
    ...state,
    [property]: newData
  });
  localStorage.setItem(storageKey, JSON.stringify(state.account));
}
// ---------------------------------------------------------------------------
// Login/register
// ---------------------------------------------------------------------------
// Client-side validation for the login form
function validateLogin(form) {
const user = form.user.value.trim();
if (user === '') {
updateElement('loginError', 'Username cannot be empty.');
return false;
}
updateElement('loginError', ''); // Clear previous error
return true;
}
// Client-side validation for the register form
function validateRegister(form) {
const formData = new FormData(form);
const data = Object.fromEntries(formData);
updateElement('registerError', ''); // Clear previous error
if (data.user.trim() === '') {
updateElement('registerError', 'Username is required.');
return false;
}
if (data.currency.trim() === '') {
updateElement('registerError', 'Currency is required.');
return false;
}
return true;
}
async function login() {
  const loginForm = document.getElementById('loginForm');
 
// Run client-side validation first
if (!validateLogin(loginForm)) {
return;
}
  const user = loginForm.user.value;
  const data = await getAccount(user);
  if (data.error) {
    return updateElement('loginError', data.error);
  }
  updateState('account', data);
  navigate('/dashboard');
}
async function register() {
  const registerForm = document.getElementById('registerForm');
// Run client-side validation first
if (!validateRegister(registerForm)) {
return;
}
  const formData = new FormData(registerForm);
  const data = Object.fromEntries(formData);
  const jsonData = JSON.stringify(data);
  const result = await createAccount(jsonData);
  if (result.error) {
    return updateElement('registerError', result.error);
  }
  updateState('account', result);
  navigate('/dashboard');
}
// ---------------------------------------------------------------------------
// Dashboard
// ---------------------------------------------------------------------------
async function updateAccountData() {
  const account = state.account;
  if (!account) {
    return logout();
  }
  const data = await getAccount(account.user);
  if (data.error) {
    return logout();
  }
  updateState('account', data);
}
async function refresh() {
  await updateAccountData();
  updateDashboard();
}
function updateDashboard() {
  const account = state.account;
  if (!account) {
    return logout();
  }
  updateElement('description', account.description);
  updateElement('balance', account.balance.toFixed(2));
  updateElement('currency', account.currency); // Fixed ID for currency display
  // Update transactions
  const transactionsRows = document.createDocumentFragment();
  for (const transaction of account.transactions) {
    const transactionRow = createTransactionRow(transaction);
    transactionsRows.appendChild(transactionRow);
  }
  updateElement('transactions', transactionsRows);
}
function createTransactionRow(transaction) {
  const template = document.getElementById('transaction');
  const transactionRow = template.content.cloneNode(true);
  const tr = transactionRow.querySelector('tr');
  tr.children[0].textContent = transaction.date;
  tr.children[1].textContent = transaction.object;
  tr.children[2].textContent = transaction.amount.toFixed(2);
  return transactionRow;
}
function addTransaction() {
  const dialog = document.getElementById('transactionDialog');
  dialog.classList.add('show');
  // Reset form
  const transactionForm = document.getElementById('transactionForm');
  transactionForm.reset();
  // Set date to today
  transactionForm.date.valueAsDate = new Date();
}
async function confirmTransaction() {
  const dialog = document.getElementById('transactionDialog');
  dialog.classList.remove('show');
  const transactionForm = document.getElementById('transactionForm');
  const formData = new FormData(transactionForm);
  const jsonData = JSON.stringify(Object.fromEntries(formData));
  const data = await createTransaction(state.account.user, jsonData);
  if (data.error) {
    return updateElement('transactionError', data.error);
  }
  // Update local state with new transaction
  const newAccount = {
    ...state.account,
    balance: state.account.balance + data.amount,
    transactions: [...state.account.transactions, data]
  }
  updateState('account', newAccount);
  // Update display
  updateDashboard();
}
async function cancelTransaction() {
  const dialog = document.getElementById('transactionDialog');
  dialog.classList.remove('show');
}
function logout() {
  updateState('account', null);
  navigate('/login');
}
// ---------------------------------------------------------------------------
// Utils
// ---------------------------------------------------------------------------
function updateElement(id, textOrNode) {
  const element = document.getElementById(id);
  element.textContent = ''; // Removes all children
  element.append(textOrNode);
}
// ---------------------------------------------------------------------------
// Init
// ---------------------------------------------------------------------------
function init() {
  // Restore state
  const savedState = localStorage.getItem(storageKey);
  if (savedState) {
    updateState('account', JSON.parse(savedState));
  }
  // Update route for browser back/next buttons
  window.onpopstate = () => updateRoute();
  updateRoute();
}
init();