diff --git a/bank-solution/.vscode/settings.json b/bank-solution/.vscode/settings.json new file mode 100644 index 0000000..6f3a291 --- /dev/null +++ b/bank-solution/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "liveServer.settings.port": 5501 +} \ No newline at end of file diff --git a/bank-solution/app.js b/bank-solution/app.js new file mode 100644 index 0000000..3b55b68 --- /dev/null +++ b/bank-solution/app.js @@ -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(); \ No newline at end of file diff --git a/bank-solution/index.html b/bank-solution/index.html new file mode 100644 index 0000000..83620cb --- /dev/null +++ b/bank-solution/index.html @@ -0,0 +1,105 @@ + + + + + + + + + + + + + +
Loading.....
+ + + +
+

ADD TRANSACTION

+
+
+
+
+
+
+
+
+ + + +
+
+
+ + + \ No newline at end of file diff --git a/bank-solution/styles.css b/bank-solution/styles.css new file mode 100644 index 0000000..731027a --- /dev/null +++ b/bank-solution/styles.css @@ -0,0 +1,295 @@ +.heading{ + text-align: center; + font-size: 25px; + color: rgb(253, 237, 10); + font-family: 'Sigmar',sans-serif; + font-weight: bolder; + text-shadow: 6px 6px crimson; +} +.link{ + color: white; + text-decoration: none; +} +.error{ + color: red; + text-align: left; + font-size: 20px; + padding-left: 14%; + margin-bottom: 5px; +} +.forms{ + display: inline-block; + width: 40%; + height: 65%; + margin-left: 30%; + font-size: 25px; + text-align:center ; + border: 2px solid gray; + border-radius: 40px; + background-color: rgb(231, 228, 228); + font-weight: bolder; + color: rgb(6, 160, 250); +} +h2{ + font-size: 35px; + color: rgb(226, 2, 2); + text-transform: uppercase; + text-decoration: double; +} +input{ + margin-bottom: 12px; + height: 40px; + width: 70%; + border-radius: 60px; + background-color: rgb(247, 246, 246); + font-size: 20px; +} +button{ + margin-bottom: 18px; + height: 50px; + width: 70%; + background-color: red; + border-radius: 60px; + font-weight: bold; + font-size: 30px; + text-transform: uppercase; + color: rgb(247, 247, 247); +} +body{ + background-color: rgb(8, 239, 247); +} +.heading2{ + text-align: center; + font-size: 25px; + color: rgb(253, 237, 10); + font-family: 'Sigmar',sans-serif; + font-weight: bolder; + text-shadow: 6px 6px crimson; + display: flex; + justify-content: space-between; +} +#heading3 { + display: flex; + flex-direction: row; + justify-content: space-between; +} +.logoutButton{ + margin-top: 50px; + width: 250px; + height: 80px; + border-radius: 10px; + text-decoration: none; +} +#addTransactions{ + margin-top: 20px; + width: 350px; + height: 70px; + border-radius: 10px; + text-decoration: none; + background-color: rgb(3, 69, 131); +} +#balanceField{ + margin-top: 30px; + color: rgb(248, 164, 7); + display: flex; + flex-flow: column; + height: 60px; + padding: 10px 0px; + text-shadow: 2px 2px rgb(61, 1, 16); + width: 100%; + font-size: 60px; + font-weight: bolder; + text-align: center; + background-color: rgb(145, 245, 233); +} +#table { + margin-left: 30%; + width: 40%; + margin-top: 10px; + color: rgb(1, 55, 116); + font-size: 30px; + border-color: rgb(0, 6, 8); + border-collapse: collapse; +} +#table th{ + border-width: 5px; +} +#table td{ + text-align: center; + margin-top: 5px; + height: 60px; + border-width: 5px; +} +dialog{ + border-radius: 30px; + height: 450px; + width: 650px; + font-size: 20px; + background-color: rgb(253, 250, 250); +} +.dialog label{ + margin: 10px 50px; + font-size: 15px; + color: rgba(34, 33, 33, 0.822); + font-weight: bolder; +} +.dialog input{ + margin: 10px 35px; + width: 560px; + font-size: 18px; + height: 45px; + border-radius: 15px; + font-weight: bolder; +} +.heading4{ + margin-left: 20%; + color: red; + font-size: 35px; +} +#cancel{ + margin-left: 44%; +} + +#ok{ + margin-left: 20px; +} +.dialogButton{ + margin-top: 15px; + height: 50px; + width: 150px; + background-color: rgba(248, 4, 24); + border-radius: 20px; + color: white; + font-size: 25px; +} + +@media screen and (max-width: 1504px) { + .forms { + width: 50%; + margin-left: 25%; + } + + input, button { + width: 70%; + } + + #table { + width: 60%; + margin-left: 20%; + } + + #addTransactions { + width: 300px; + font-size: 25px; + } +} + +/* For mobile devices */ +@media screen and (max-width: 968px) { + .forms { + width: 70%; + margin-left: 15%; + font-size: 20px; + } + + h2 { + font-size: 28px; + } + + input, button { + width: 70%; + font-size: 18px; + } + + button { + height: 45px; + font-size: 24px; + } + + .heading, .heading2 { + font-size: 20px; + text-shadow: 4px 4px crimson; + } + + #balanceField { + font-size: 40px; + height: 60px; + } + + #table { + width: 80%; + margin-left: 10%; + font-size: 25px; + } + + #table td { + height: 50px; + } + + .logoutButton{ + margin-top: 50px; + width: 150px; + height: 50px; + border-radius: 10px; + text-decoration: none; + } + + #addTransactions{ + margin-top: 20px; + width: 250px; + height: 70px; + border-radius: 10px; + text-decoration: none; + font-size: 20px; + } +} + +/* For very small screens */ +@media screen and (max-width: 580px) { + .forms { + width: 70%; + margin-left: 15%; + font-size: 18px; + } + + h2 { + font-size: 24px; + } + + input, button { + width: 75%; + font-size: 16px; + } + + button { + height: 40px; + font-size: 20px; + } + + .heading, .heading2 { + font-size: 18px; + text-shadow: 3px 3px crimson; + } + + #balanceField { + font-size: 30px; + height: 50px; + } + + #table { + width: 90%; + margin-left: 5%; + font-size: 20px; + } + + #table td { + height: 40px; + } + + #addTransactions{ + font-size: 15px; + width: 150px; + height: 50px; + } + +} diff --git a/solution.md b/solution.md new file mode 100644 index 0000000..bd6c6c9 --- /dev/null +++ b/solution.md @@ -0,0 +1,438 @@ +# 4-Bank-Project + +## 1-template-route + +### Assignment + +The routes object defines available routes in the application which establishes a connection between the url and your templates.Currently our routes declaration contains only the template ID to use. Modify your routes object to include a title for your template and later, we will include our command which gives a title to the document depending on which template is opened. After this, we can observe a change in document title as and when a template is opened. + +```javascript +const routes = { + '/login': { + templateId: 'login' , + title: 'Login Page' + }, + '/dashboard': { + templateId: 'dashboard', + title: 'Dashboard' + }, + '/credits': { + templateId: 'credits', + title: 'credits' + }, +}; +``` +Now add this code in your updateRoute function to update the title of the browser and print the message in the console everytime a template is opened. + +For example, The message 'Dashboard is shown' in the console every time the dashboard page is opened. + +```javascript +document.title = route.title; +console.log(`${route.title} is shown`); +``` +### Challenge + +I just had to repeat and apply whatever I had learnt till now. First, I added the following code to my html file just below the dashboard template to create another template called credits. + +```html + +``` +After creating the template, my next step was to add the template to my routes object, which established a connection between my url and credits template. I also added a link in the header element of my dashboard template to navigate between the templates. + +```html +Credits +``` + +## 3. data + +### Challenge + +```css +@media screen and (max-width: 1504px) { + .forms { + width: 50%; + margin-left: 25%; + } + + input, button { + width: 70%; + } + + #table { + width: 60%; + margin-left: 20%; + } +} + +/* For mobile devices */ +@media screen and (max-width: 968px) { + .forms { + width: 70%; + margin-left: 15%; + font-size: 20px; + } + + h2 { + font-size: 28px; + } + + input, button { + width: 70%; + font-size: 18px; + } + + button { + height: 45px; + font-size: 24px; + } + + .heading, .heading2 { + font-size: 20px; + text-shadow: 4px 4px crimson; + } + + #balanceField { + font-size: 40px; + height: 60px; + } + + #table { + width: 80%; + margin-left: 10%; + font-size: 25px; + } + + #table td { + height: 50px; + } + + .logoutButton{ + margin-top: 50px; + width: 150px; + height: 50px; + border-radius: 10px; + text-decoration: none; + } + + .logoutButton a{ + font-size: 25px; + text-decoration: none; + color: white; + text-shadow: 2px 2px rgb(253, 249, 11); + } +} + +/* For very small screens */ +@media screen and (max-width: 580px) { + .forms { + width: 70%; + margin-left: 15%; + font-size: 18px; + } + + h2 { + font-size: 24px; + } + + input, button { + width: 75%; + font-size: 16px; + } + + button { + height: 40px; + font-size: 20px; + } + + .heading, .heading2 { + font-size: 18px; + text-shadow: 3px 3px crimson; + } + + #balanceField { + font-size: 30px; + height: 50px; + } + + #table { + width: 90%; + margin-left: 5%; + font-size: 20px; + } + + #table td { + height: 40px; + } +} +``` + +### Assignment + +```javascript +//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 account = null; +const api = '//localhost:5000/api/accounts/'; + +//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: updateDashboard + }, + '/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); + 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 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. + } +} + +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. + } + + 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.toFixed(2); //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('/login'); //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. + } + } + + +function updateElement(id, textorNode){ //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(textorNode); //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() { + if (!account) { + return navigate('/login'); + } + + 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. +} + +window.onpopstate = () => updateRoute(); //Ensures navigation works on clicking the back and forward buttons. +updateRoute(); //Loads the correct page when the app starts. +``` + +## 4. state-management + +### Challenge + +Update the updateState() function to contain the following code, so that the storageKey stores the username only, rather than storing the entire account detials. + +```javascript +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 + } +``` +Subsequently, we also need to make changes to our init() function to get our account details later by using getAccount() function. + +```javascript +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. +} +``` +### Assignment + +In order to add a feature which enables us to add the transactions to our account and displays the balance the next time we login, we need to first create a dialog which opens up where we enter the transaction details. + +So first , I added the (add transaction) button which on clicking will open our dialog. I added the above code to my index.html file just below our div element which is containing our description heading. + +```html +
+ +
+``` +I then added some styling to my button such that the final output looks like this. + + +I then added the dialog tag in my index.html file which shows up only when the button is clicked.I first created a separate html file to style my dialog and then integrated everything into my actual file, making it easy for me to work on one thing. +I added the following code which creates a data modal which shows up only when the add transaction button is clicked. + +```html + +
+

ADD TRANSACTION

+
+
+
+
+
+
+
+
+ + + +
+
+
+``` +I had added a form in my dialog so that on adding the transaction details and submitting my form , it would be a lot more easier to save it to the api later. ( basically following the same thing that we follow for register) + +I added all the event listeners in my project in attachEventListeners function due to some error I faced in my later stages (which I will specify at a later stage) + +```javascript +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(); +} +``` + +