Skip to content

This PR is the solution to the 'js basics' #4 #93

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions bank-solution/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"liveServer.settings.port": 5501
}
253 changes: 253 additions & 0 deletions bank-solution/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
//Create a variable which stores the logged-in user's details, with initial value as none indicating no user is logged in.
//We need a private space to store all the user details who have created their accounts. These details are stored and used to retrieve the account details.

let state = Object.freeze({
account: null
});
const api = '//localhost:5000/api/accounts/';
const storageKey = 'savedAccount';

//We need to create an array which would hold all the required ids and titles of each template which would help in navigation.

const routes = {
'/login': {
templateId: 'login' ,
title: 'Login Page'
},
'/dashboard': {
templateId: 'dashboard',
title: 'Dashboard',
init: refresh
},
'/credits': {
templateId: 'credits',
title: 'credits'
},
};




//templateId: The ID of the HTML template to load.
//title: The title of the page (used to update document.title).
//init: A function which executes when the route is loaded.

async function register() {
const registerForm = document.getElementById('registerForm'); //retrieves data from the register form and stores it in this variable.
const formData = new FormData(registerForm);
const data = Object.fromEntries(formData);
const jsonData = JSON.stringify(data); //Converts data into an object and then converts in the form of JSON string.
const result = await createAccount(jsonData); //stores the registered user details

if(result.error){
return updateElement('registerError', result.error); //displays the error message and does the respective styling.
}

console.log('Account created!', result);
updateState('account',result); // stores it into the variable we created for user details if no error is returned.
navigate('/dashboard'); //calls the navigate function and navigates to the dashboard page.
}


async function add(){
const transactionForm = document.getElementById('transactionForm');
if (!transactionForm) {
console.log("Transaction form not found!");
return;
}
const addDetails = new FormData(transactionForm);
const transactionDetails = Object.fromEntries(addDetails);
console.log("Transaction Details:", transactionDetails);
// Create the row
const transactionRow = createTransactionRow(transactionDetails);
// Find the transactions table
const transactionsTable = document.getElementById('transactions'); // Ensure this is the tbody, not the table
if (!transactionsTable) {
console.log("Transactions table not found!");
return;
}
// Append the new row to the table
transactionsTable.appendChild(transactionRow);
console.log("Attempting to save transaction to API...");

try {
const response = await fetch(`${api}${encodeURIComponent(state.account.user)}/transactions`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(transactionDetails),
});
if (!response.ok) throw new Error(await response.text());
console.log("Transaction successfully saved to API.");
} catch (error) {
console.error("Error saving transaction:", error);
}

// Reset the form after adding
transactionForm.reset();
}

async function createAccount(account) {
try {
const response = await fetch(api , {
method: 'POST',
headers: { 'Content-Type': 'application/json' }, //sends a POST request to the API to create an account.
body: account
});
return await response.json(); //converts the response to JSON.
} catch (error) {
return { error: error.message || 'Unknown error' }; //catch is used to display an error message if any error is generated.
}
}

function logout() {
updateState('account', null);
localStorage.removeItem(storageKey); //removes the items stored in the storageKey.
navigate('/login'); //redirects to the login page once logged out
}

async function login() {
const loginForm = document.getElementById('loginForm'); //retrieves data from the login form and stores it in this variable.
const user = loginForm.user.value; //the name type is used to get the username to the variable user.
const data = await getAccount(user); //calls the getAccount function to fetch the user details on logging in.
if(data.error){
return updateElement('loginError',data.error); //displays an error message when any error is found.
}

updateState('account',data); //stores the user details in the account variable.
navigate('/dashboard'); //navigates to the dashboard page on logging in.
}

// We create a function used to get the user details when already registered in the accounts api using async/await function.

async function getAccount(user) {
try {
const response = await fetch(api + encodeURIComponent(user));
return await response.json();
} catch (error) {
return { error: error.message || 'Unknown error' }; //try and catch function to catch any error and display the error if there.
}
}


//createTransactionRow generates a separate row for each transaction.

function createTransactionRow(transaction) {
const template = document.getElementById('transaction'); //calls the transaction template.
const transactionRow = template.content.cloneNode(true); //clones the transaction template.
const tr = transactionRow.querySelector('tr');
tr.children[0].textContent = transaction.date ;
tr.children[1].textContent = transaction.object;
tr.children[2].textContent = transaction.amount; //updates the row with transaction details.
return transactionRow; //returns the modified row.
}


function updateRoute() {
const path = window.location.pathname; //gets the current url path.
const route = routes[path]; //searches for the given template in the routes array.

if (!route) {
return navigate('/dashboard'); //if there is no route, then redirects to the login page.(basically used as a default)
}

const template = document.getElementById(route.templateId); //searches for the template in our html file.
const view = template.content.cloneNode(true); //clones the template.
const app = document.getElementById('app');
document.title = route.title; //sets the document title according to the template that is currently active.
app.innerHTML = ''; //sets the title to LOading... if any error in navigating between templates.
app.appendChild(view);

if (typeof route.init === 'function') {
route.init(); //calls an initialization function if one is defined.
}

attachEventListeners();
}

function attachEventListeners() {
document.querySelector(".logoutButton")?.addEventListener("click", logout);
document.getElementById('addTransactions')?.addEventListener('click', () => {
document.querySelector("[data-modal]")?.showModal();
});
document.getElementById("ok")?.addEventListener('click', add);
}


function updateElement(id, content){ //used to change the text of the respective element by specifying the parameters(id and text)
const element = document.getElementById(id);
element.textContent = '';
element.append(content); //We replace the textContent mathod with the append() method as it allows to attach either text or DOM Nodes to a parent element, which is perfect for all our use cases.
}

function navigate(path) {
window.history.pushState({}, path, path); //updates the browser history.
updateRoute();
} //used for navigation purposes.

function updateDashboard() {
const account = state.account;
if (!account) {
return logout();
}

updateElement('description', account.description);
updateElement('balance', account.balance.toFixed(2));
updateElement('currency', account.currency); //updates the given elements content.

const transactionsRows = document.createDocumentFragment();
for (const transaction of account.transactions) {
const transactionRow = createTransactionRow(transaction);
transactionsRows.appendChild(transactionRow);
}
updateElement('transactions', transactionsRows); //creates and appends the transaction rows.
}

//we create a function which would update any details directly on the dashboard page itself without the need to reload and login again.

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 updateState(property, newData) {
state = Object.freeze({
...state,
[property]: newData
});

if (newData && newData.user) {
localStorage.setItem(storageKey, newData.user);
} else {
localStorage.removeItem(storageKey); // Ensure old data is removed
}
}

async function init() {
const savedUser = localStorage.getItem(storageKey);
if (savedUser) {
const info = await getAccount(savedUser);
updateState('account', info); //calls the updateState function to get the entire user details after getting the username.
}

// Our previous initialization code
window.onpopstate = () => updateRoute(); //Ensures navigation works on clicking the back and forward buttons.
updateRoute(); //Loads the correct page when the app starts.
}



init();
105 changes: 105 additions & 0 deletions bank-solution/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title></title>
<script src="app.js" defer></script>
<link rel="stylesheet" href="styles.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Sigmar&display=swap" rel="stylesheet">
</head>
<body>
<div id="app">Loading.....</div>
<template id="login">
<div class="heading">
<h1>Bank App</h1>
</div>
<div class="forms">
<section>
<h2>Login</h2>
<form id="loginForm" action="javascript: login()">
<label for="username">Username</label><br>
<input id="username" name="user" type="text"><br>
<div id="loginError" role="alert" class="error"></div>
<button>Login</button>
</form>
<hr color="grey">
<h2>Register</h2>
<form id="registerForm" action="javascript:register()">
<label for="user">Username (required)</label><br>
<input id="user" name="user" type="text" maxlength="25" required><br>
<label for="currency">Currency (required)</label><br>
<input id="currency" name="currency" type="text" value="$" required><br>
<label for="description">Description</label><br>
<input id="description" name="description" type="text" maxlength="100"><br>
<label for="balance">Current balance</label><br>
<input id="balance" name="balance" type="number" value="0" maxlength="5"><br>
<div id="registerError" role="alert" class="error"></div>
<button>Register</button>
</form>
</section>
</div>
</template>
<template id="dashboard">
<header>
<div class="heading2">
<h1>Bank App</h1>
<button class="logoutButton">Logout</button>
</div>

<!--<br><a href="/credits" onclick="onLinkClick(event)">Credits</a>-->
</header>
<div id="balanceField">
<section>
Balance: <span id="balance"></span><span id="currency"></span>
</section>
</div>
<section>
<div id="heading3">
<div>
<h2 id="description"></h2>
</div>
<div>
<button id="addTransactions">Add Transactions</button>
</div>
</div>
<table id="table" border="6">
<thead>
<tr>
<th>Date</th>
<th>Object</th>
<th>Amount</th>
</tr>
</thead>
<tbody id="transactions"></tbody>
</table>
</section>
</template>
<dialog data-modal>
<div>
<h1 class="heading4">ADD TRANSACTION</h1>
</div>
<div class="dialog">
<form action="javascript: add()" id="transactionForm" method="dialog">
<label for="date">DATE</label><br>
<input type="date" name="date" id="datetr"><br>
<label for="object">OBJECT</label><br>
<input type="text" name="object" id="objtr"><br>
<label for="amount">AMOUNT (USE NEGATIVE VALUE FOR DEBIT)</label><br>
<input type="number" name="amount" id="amounttr">
<button id="cancel" class="dialogButton">CANCEL</button>
<button id="ok" class="dialogButton" type="submit">OK</button>
</form>
</div>
</dialog>
<template id="transaction">
<tr>
<td></td>
<td></td>
<td></td>
</tr>
</template>
</body>
</html>
Loading