Included more rebust client side validation

pull/1513/head
Roshan Patel 2 months ago
parent 2f458c2267
commit 396850dbb9

@ -11,34 +11,34 @@ const accountsKey = 'accounts'; // New key for all accounts
// ---------------------------------------------------------------------------
const routes = {
'/dashboard': { title: 'My Account', templateId: 'dashboard', init: refresh },
'/login': { title: 'Login', templateId: 'login' }
  '/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();
  window.history.pushState({}, path, window.location.origin + path);
  updateRoute();
}
function updateRoute() {
const path = window.location.pathname;
const route = routes[path];
  const path = window.location.pathname;
  const route = routes[path];
if (!route) {
return navigate('/dashboard');
}
  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);
  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();
}
  if (typeof route.init === 'function') {
    route.init();
  }
document.title = route.title;
  document.title = route.title;
}
// ---------------------------------------------------------------------------
@ -46,71 +46,71 @@ function updateRoute() {
// ---------------------------------------------------------------------------
function getAccounts() {
return JSON.parse(localStorage.getItem(accountsKey) || '[]');
  return JSON.parse(localStorage.getItem(accountsKey) || '[]');
}
function saveAccounts(accounts) {
localStorage.setItem(accountsKey, JSON.stringify(accounts));
  localStorage.setItem(accountsKey, JSON.stringify(accounts));
}
function findAccount(user) {
const accounts = getAccounts();
return accounts.find(acc => acc.user === user) || null;
  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);
});
  // 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);
});
  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);
});
  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);
  });
}
// ---------------------------------------------------------------------------
@ -118,47 +118,87 @@ async function createTransaction(user, transactionJson) {
// ---------------------------------------------------------------------------
let state = Object.freeze({
account: null
  account: null
});
function updateState(property, newData) {
state = Object.freeze({
...state,
[property]: newData
});
localStorage.setItem(storageKey, JSON.stringify(state.account));
  state = Object.freeze({
    ...state,
    [property]: newData
  });
  localStorage.setItem(storageKey, JSON.stringify(state.account));
}
// ---------------------------------------------------------------------------
// Login/register
// ---------------------------------------------------------------------------
async function login() {
const loginForm = document.getElementById('loginForm')
const user = loginForm.user.value;
const data = await getAccount(user);
// 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.error) {
return updateElement('loginError', data.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');
  updateState('account', data);
  navigate('/dashboard');
}
async function register() {
const registerForm = document.getElementById('registerForm');
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);
  const registerForm = document.getElementById('registerForm');
// Run client-side validation first
if (!validateRegister(registerForm)) {
return;
}
updateState('account', result);
navigate('/dashboard');
  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');
}
// ---------------------------------------------------------------------------
@ -166,99 +206,99 @@ async function register() {
// ---------------------------------------------------------------------------
async function updateAccountData() {
const account = state.account;
if (!account) {
return logout();
}
  const account = state.account;
  if (!account) {
    return logout();
  }
const data = await getAccount(account.user);
if (data.error) {
return logout();
}
  const data = await getAccount(account.user);
  if (data.error) {
    return logout();
  }
updateState('account', data);
  updateState('account', data);
}
async function refresh() {
await updateAccountData();
updateDashboard();
  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);
// Update transactions
const transactionsRows = document.createDocumentFragment();
for (const transaction of account.transactions) {
const transactionRow = createTransactionRow(transaction);
transactionsRows.appendChild(transactionRow);
}
updateElement('transactions', transactionsRows);
  const account = state.account;
  if (!account) {
    return logout();
  }
  updateElement('description', account.description);
  updateElement('balance', account.balance.toFixed(2));
  updateElement('balance-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;
  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');
  const dialog = document.getElementById('transactionDialog');
  dialog.classList.add('show');
// Reset form
const transactionForm = document.getElementById('transactionForm');
transactionForm.reset();
  // Reset form
  const transactionForm = document.getElementById('transactionForm');
  transactionForm.reset();
// Set date to today
transactionForm.date.valueAsDate = new Date();
  // Set date to today
  transactionForm.date.valueAsDate = new Date();
}
async function confirmTransaction() {
const dialog = document.getElementById('transactionDialog');
dialog.classList.remove('show');
  const dialog = document.getElementById('transactionDialog');
  dialog.classList.remove('show');
const transactionForm = document.getElementById('transactionForm');
  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);
  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);
}
  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 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();
  // Update display
  updateDashboard();
}
async function cancelTransaction() {
const dialog = document.getElementById('transactionDialog');
dialog.classList.remove('show');
  const dialog = document.getElementById('transactionDialog');
  dialog.classList.remove('show');
}
function logout() {
updateState('account', null);
navigate('/login');
  updateState('account', null);
  navigate('/login');
}
// ---------------------------------------------------------------------------
@ -266,9 +306,9 @@ function logout() {
// ---------------------------------------------------------------------------
function updateElement(id, textOrNode) {
const element = document.getElementById(id);
element.textContent = ''; // Removes all children
element.append(textOrNode);
  const element = document.getElementById(id);
  element.textContent = ''; // Removes all children
  element.append(textOrNode);
}
// ---------------------------------------------------------------------------
@ -276,15 +316,15 @@ function updateElement(id, textOrNode) {
// ---------------------------------------------------------------------------
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();
  // 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();

Loading…
Cancel
Save