From d3b5ada7c8a7eb4a8b5aa9d8f8d635cbc57dd929 Mon Sep 17 00:00:00 2001 From: PinJinx Date: Mon, 18 Nov 2024 23:14:31 +0530 Subject: [PATCH 1/3] task3 --- 3-typing-game/Solution.md | 442 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 442 insertions(+) create mode 100644 3-typing-game/Solution.md diff --git a/3-typing-game/Solution.md b/3-typing-game/Solution.md new file mode 100644 index 0000000..34f910e --- /dev/null +++ b/3-typing-game/Solution.md @@ -0,0 +1,442 @@ +```3-typing-game + +Challenge: +Add more functionality + +- Disable the `input` event listener on completion, and re-enable it when the button is clicked +- Disable the textbox when the player completes the quote +- Display a modal dialog box with the success message +- Store high scores using + + + + +Soulution: + +Part One of the challenge was to disable input event listener I did this function by, +typed valueElement is the input field +typedValueElement.removeEventListener('input'); + +Part two was to disable textbox i did this using +typedValueElement.disabled = true; + +Part four is to create a high score I did this using, + const elapsedTime = new Date().getTime() - startTime; + const HighScore = localStorage.getItem("hs"); + if(HighScore != null){ + message= `CONGRATULATIONS! You finished in ${elapsedTime / 1000} seconds.Current highscore is ${HighScore}`; + } + if(HighScore>elapsedTime / 1000 || HighScore == null){ + message = `CONGRATULATIONS! You finished with a new Highscore of ${elapsedTime / 1000} seconds.`; + localStorage.setItem("hs",elapsedTime/1000); + } +Part three was done by a setting a divs style hidden and visible in which both highscore and congrats message is displayed + +Here is the entire code: + +HTML: + + + + Typing game + + + +

Typing game!

+

Practice your typing skills with a quote from Sherlock Holmes. Click **start** to begin!

+

+

+
+ + +
+ + + + + + + + +CSS: +.highlight { + background-color: yellow; + } + + .error { + background-color: lightcoral; + border: red; + } + .sep{ + margin-top: 20px; + margin-bottom: 30px; + width: 500px; + border: 1px solid black; + } + .box { + width: 500px; + background-color: lightgreen; + height: 170px; + border: 1px solid black; + padding: 10px; + text-align: center; + margin: 10px; + } + .x{ + margin-left: 480px; + } + Java: + const quotes = [ + + 'When you have eliminated the impossible, whatever remains, however improbable, must be the truth.', + 'There is nothing more deceptive than an obvious fact.', + 'I ought to know by this time that when a fact appears to be opposed to a long train of deductions it invariably proves to be capable of bearing some other interpretation.', + 'I never make exceptions. An exception disproves the rule.', + 'What one man can invent another can discover.', + 'Nothing clears up a case so much as stating it to another person.', + 'Education never ends, Watson. It is a series of lessons, with the greatest for the last.', +]; +// store the list of words and the index of the word the player is currently typing +let words = []; +let wordIndex = 0; +// the starting time +let startTime = Date.now(); +// page elements +const quoteElement = document.getElementById('quote'); +const messageElement = document.getElementById('message'); +const typedValueElement = document.getElementById('typed-value'); +const WinBox=document.getElementById('winbox'); +const close = document.getElementById("close"); + +let k=false; + + +close.addEventListener('click',() => { + WinBox.style.visibility = 'hidden'; +}) + +document.getElementById('start').addEventListener('click', () => { + // get a quote + const quoteIndex = Math.floor(Math.random() * quotes.length); + const quote = quotes[quoteIndex]; + // Put the quote into an array of words + words = quote.split(' '); + // reset the word index for tracking + wordIndex = 0; + WinBox.style.visibility = 'hidden'; + // UI updates + // Create an array of span elements so we can set a class + const spanWords = words.map(function(word) { return `${word} `}); + // Convert into string and set as innerHTML on quote display + quoteElement.innerHTML = spanWords.join(''); + // Highlight the first word + quoteElement.childNodes[0].className = 'highlight'; + // Clear any prior messages + messageElement.innerText = ''; + typedValueElement.disabled = false; + // Clear the textbox + typedValueElement.value = ''; + // set focus + typedValueElement.focus(); + // set the event handler + // Start the timer + startTime = new Date().getTime(); + }); + + + // at the end of script.js +typedValueElement.addEventListener('input', () => { + // Get the current word + const currentWord = words[wordIndex]; + // get the current value + const typedValue = typedValueElement.value; + + if (typedValue === currentWord && wordIndex === words.length - 1) { + // end of sentence + // Display success + const elapsedTime = new Date().getTime() - startTime; + // Setup the textbox + const HighScore = localStorage.getItem("hs"); + let message = ``; + if(HighScore != null){ + message= `CONGRATULATIONS! You finished in ${elapsedTime / 1000} seconds.Current highscore is ${HighScore}`; + } + if(HighScore>elapsedTime / 1000 || HighScore == null){ + message = `CONGRATULATIONS! You finished with a new Highscore of ${elapsedTime / 1000} seconds.`; + localStorage.setItem("hs",elapsedTime/1000); + } + messageElement.innerText = message; + WinBox.style.visibility = 'visible'; + //Challenges + typedValueElement.disabled = true; + typedValueElement.removeEventListener('input'); + + + + } else if (typedValue.endsWith(' ') && typedValue.trim() === currentWord) { + // end of word + // clear the typedValueElement for the new word + typedValueElement.value = ''; + // move to the next word + wordIndex++; + // reset the class name for all elements in quote + for (const wordElement of quoteElement.childNodes) { + wordElement.className = ''; + } + // highlight the new word + quoteElement.childNodes[wordIndex].className = 'highlight'; + } else if (currentWord.startsWith(typedValue)) { + // currently correct + // highlight the next word + typedValueElement.className = ''; + } else { + // error state + typedValueElement.className = 'error'; + } + }); + + + + + + Assignment: + + For the Assignment I made a game where your objective is to find the hidden number based on the clues provided for your previous guess. + There are three types of clues: + Red digits indicate the number of digits in your guess that are in the correct position and have the correct value. + Yellow digits show how many digits are present in the hidden number but are in the wrong position + Green digits represent the number of digits in your guess that are repeated in the hidden number. + Use these clues to narrow down guesses and discover the hidden number!Sc + +ScreenShots: +![image](https://github.com/user-attachments/assets/0118b3eb-1446-4e61-87d8-a9d96e898ada) +![image](https://github.com/user-attachments/assets/7f57c03e-3cb1-484b-a967-0a0ef01508d0) + + +Code: +HTML + + + + Guess the Text + + + + + + +
    + +

    -

    +
    + +

    -

    +
    + +

    -

    +
    + +

    -

    +
    +
+
+

How to Play:

+

How to Play: + In this game, your objective is to find the hidden number based on the clues provided for your previous guess.
+ There are three types of clues:
+ Red digits indicate the number of digits in your guess that are in the correct position and have the correct value.
+ Yellow digits show how many digits are present in the hidden number but are in the wrong position.
+ Green digits represent the number of digits in your guess that are repeated in the hidden number.
+ Use these clues to narrow down your guesses and discover the hidden number!
+

+
+

Your Guess:

+ + +

Previous Guesses:

+
    + +
+ + + + + +JAVA: +const Num1 = document.getElementById('Num1'); +const Num2 = document.getElementById('Num2'); +const Num3 = document.getElementById('Num3'); +const Num4 = document.getElementById('Num4'); +const Prev_Guess = document.getElementById('ps'); +const Prev_Guess_Header = document.getElementById('psh'); +const inputbox = document.getElementById('guess'); +const RestartButton = document.getElementById('rs'); + +let GuessNum = 0; + +let GuessAmt = 0; + + + +function UpdateNum(k){ + Num1.innerText=k[0]; + Num2.innerText=k[1]; + Num3.innerText=k[2]; + Num4.innerText=k[3]; +} + +RestartButton.addEventListener('click',()=>{ + RestartButton.visibility=''; + GuessNum=getRandomInt(1000,9999); + GuessAmt=0; + inputbox.value=''; + UpdateNum('----'); + while (Prev_Guess.firstChild) { + Prev_Guess.removeChild(Prev_Guess.lastChild); + } + Prev_Guess_Header.innerText = "Previous Guesses:"; + Prev_Guess.style.visibility='visible'; +}) + + +inputbox.addEventListener("input",()=>{ + let val=inputbox.value; + + + + if(val.length > 3){ + GuessAmt++; + //Do PrevGuess + inputbox.value=""; + let ele =document.createElement("ls"); + let ele2=document.createElement("ol"); + ele2.className = "Numbox"; + h1=document.createElement("h1"); + h2=document.createElement("h1"); + h3=document.createElement("h1"); + h4=document.createElement("h1"); + ele2.appendChild(h1); + ele2.appendChild(h2); + ele2.appendChild(h3); + ele2.appendChild(h4); + + h2.className = "red"; + h3.className="yellow"; + h4.className="green"; + + ele.appendChild(ele2) + Prev_Guess.appendChild(ele); + + //Set val of h1 + GuessNum=GuessNum.toString(); + let y=0; + let r=0; + let m=Num1.innerText+Num2.innerText+Num3.innerText+Num4.innerText; + let k='' + //Count a number repeating + let multi_occurence='' + let ml=0 + console.log(GuessNum); + for(let i=0;i1){ + ml++; + } + } + } + + } + } + console.log(k); + UpdateNum(k); + + if(k === GuessNum){ + Prev_Guess.style.visibility = 'hidden'; + + let hs = localStorage.getItem('hs'); + RestartButton.style.visibility = 'visible'; + if(hs === null || hs > GuessAmt){ + Prev_Guess_Header.innerText = `You Won with ${GuessAmt} guesses.That is a new HIGHSCORE!`; + localStorage.setItem('hs',GuessAmt); + } + else{ + Prev_Guess_Header.innerText = `You Won with ${GuessAmt} guesses.The High Score is ${hs}`; + } + } + //Setup Text + h1.innerText=val; + h2.innerText = r; + h3.innerText= y; + h4.innerText=ml; + + } +}) + + + + + +function count(str,count_term){ + let c =0; + for(let j=0;j Date: Mon, 25 Nov 2024 22:02:23 +0530 Subject: [PATCH 2/3] bank-project --- 3-typing-game/Assignment/index.html | 46 +++++ 3-typing-game/Assignment/script.js | 139 +++++++++++++ 3-typing-game/Assignment/style.css | 28 +++ 3-typing-game/index.html | 29 +++ 3-typing-game/script.js | 108 ++++++++++ 3-typing-game/style.css | 27 +++ 4-bank-project/bank-project-solution/app.js | 189 ++++++++++++++++++ .../bank-project-solution/index.html | 131 ++++++++++++ 4-bank-project/bank-project-solution/logo.png | Bin 0 -> 29786 bytes .../bank-project-solution/style.css | 106 ++++++++++ 4-bank-project/index.html | 11 + 11 files changed, 814 insertions(+) create mode 100644 3-typing-game/Assignment/index.html create mode 100644 3-typing-game/Assignment/script.js create mode 100644 3-typing-game/Assignment/style.css create mode 100644 3-typing-game/index.html create mode 100644 3-typing-game/script.js create mode 100644 3-typing-game/style.css create mode 100644 4-bank-project/bank-project-solution/app.js create mode 100644 4-bank-project/bank-project-solution/index.html create mode 100644 4-bank-project/bank-project-solution/logo.png create mode 100644 4-bank-project/bank-project-solution/style.css create mode 100644 4-bank-project/index.html diff --git a/3-typing-game/Assignment/index.html b/3-typing-game/Assignment/index.html new file mode 100644 index 0000000..9439ad2 --- /dev/null +++ b/3-typing-game/Assignment/index.html @@ -0,0 +1,46 @@ + + + + Guess the Text + + + + + + +
    + +

    -

    +
    + +

    -

    +
    + +

    -

    +
    + +

    -

    +
    +
+
+

How to Play:

+

How to Play: + In this game, your objective is to find the hidden number based on the clues provided for your previous guess.
+ There are three types of clues:
+ Red digits indicate the number of digits in your guess that are in the correct position and have the correct value.
+ Yellow digits show how many digits are present in the hidden number but are in the wrong position.
+ Green digits represent the number of digits in your guess that are repeated in the hidden number.
+ Use these clues to narrow down your guesses and discover the hidden number!
+

+
+

Your Guess:

+ + +

Previous Guesses:

+
    + +
+ + + + \ No newline at end of file diff --git a/3-typing-game/Assignment/script.js b/3-typing-game/Assignment/script.js new file mode 100644 index 0000000..cd376b0 --- /dev/null +++ b/3-typing-game/Assignment/script.js @@ -0,0 +1,139 @@ +const Num1 = document.getElementById('Num1'); +const Num2 = document.getElementById('Num2'); +const Num3 = document.getElementById('Num3'); +const Num4 = document.getElementById('Num4'); +const Prev_Guess = document.getElementById('ps'); +const Prev_Guess_Header = document.getElementById('psh'); +const inputbox = document.getElementById('guess'); +const RestartButton = document.getElementById('rs'); + +let GuessNum = 0; + +let GuessAmt = 0; + + + +function UpdateNum(k){ + Num1.innerText=k[0]; + Num2.innerText=k[1]; + Num3.innerText=k[2]; + Num4.innerText=k[3]; +} + +RestartButton.addEventListener('click',()=>{ + RestartButton.visibility=''; + GuessNum=getRandomInt(1000,9999); + GuessAmt=0; + inputbox.value=''; + UpdateNum('----'); + while (Prev_Guess.firstChild) { + Prev_Guess.removeChild(Prev_Guess.lastChild); + } + Prev_Guess_Header.innerText = "Previous Guesses:"; + Prev_Guess.style.visibility='visible'; +}) + + +inputbox.addEventListener("input",()=>{ + let val=inputbox.value; + + + + if(val.length > 3){ + GuessAmt++; + //Do PrevGuess + inputbox.value=""; + let ele =document.createElement("ls"); + let ele2=document.createElement("ol"); + ele2.className = "Numbox"; + h1=document.createElement("h1"); + h2=document.createElement("h1"); + h3=document.createElement("h1"); + h4=document.createElement("h1"); + ele2.appendChild(h1); + ele2.appendChild(h2); + ele2.appendChild(h3); + ele2.appendChild(h4); + + h2.className = "red"; + h3.className="yellow"; + h4.className="green"; + + ele.appendChild(ele2) + Prev_Guess.appendChild(ele); + + //Set val of h1 + GuessNum=GuessNum.toString(); + let y=0; + let r=0; + let m=Num1.innerText+Num2.innerText+Num3.innerText+Num4.innerText; + let k='' + //Count a number repeating + let multi_occurence='' + let ml=0 + console.log(GuessNum); + for(let i=0;i1){ + ml++; + } + } + } + + } + } + console.log(k); + UpdateNum(k); + + if(k === GuessNum){ + Prev_Guess.style.visibility = 'hidden'; + + let hs = localStorage.getItem('hs'); + RestartButton.style.visibility = 'visible'; + if(hs === null || hs > GuessAmt){ + Prev_Guess_Header.innerText = `You Won with ${GuessAmt} guesses.That is a new HIGHSCORE!`; + localStorage.setItem('hs',GuessAmt); + } + else{ + Prev_Guess_Header.innerText = `You Won with ${GuessAmt} guesses.The High Score is ${hs}`; + } + } + //Setup Text + h1.innerText=val; + h2.innerText = r; + h3.innerText= y; + h4.innerText=ml; + + } +}) + + + + + +function count(str,count_term){ + let c =0; + for(let j=0;j + + + Typing game + + + + + +

Typing game!

+

Practice your typing skills with a quote from Sherlock Holmes. Click **start** to begin!

+

+

+
+ + +
+ + + + + + + \ No newline at end of file diff --git a/3-typing-game/script.js b/3-typing-game/script.js new file mode 100644 index 0000000..1b79c54 --- /dev/null +++ b/3-typing-game/script.js @@ -0,0 +1,108 @@ +const quotes = [ + + 'When you have eliminated the impossible, whatever remains, however improbable, must be the truth.', + 'There is nothing more deceptive than an obvious fact.', + 'I ought to know by this time that when a fact appears to be opposed to a long train of deductions it invariably proves to be capable of bearing some other interpretation.', + 'I never make exceptions. An exception disproves the rule.', + 'What one man can invent another can discover.', + 'Nothing clears up a case so much as stating it to another person.', + 'Education never ends, Watson. It is a series of lessons, with the greatest for the last.', +]; +// store the list of words and the index of the word the player is currently typing +let words = []; +let wordIndex = 0; +// the starting time +let startTime = Date.now(); +// page elements +const quoteElement = document.getElementById('quote'); +const messageElement = document.getElementById('message'); +const typedValueElement = document.getElementById('typed-value'); +const WinBox=document.getElementById('winbox'); +const close = document.getElementById("close"); + +let k=false; + + +close.addEventListener('click',() => { + WinBox.style.visibility = 'hidden'; +}) + +document.getElementById('start').addEventListener('click', () => { + // get a quote + const quoteIndex = Math.floor(Math.random() * quotes.length); + const quote = quotes[quoteIndex]; + // Put the quote into an array of words + words = quote.split(' '); + // reset the word index for tracking + wordIndex = 0; + WinBox.style.visibility = 'hidden'; + // UI updates + // Create an array of span elements so we can set a class + const spanWords = words.map(function(word) { return `${word} `}); + // Convert into string and set as innerHTML on quote display + quoteElement.innerHTML = spanWords.join(''); + // Highlight the first word + quoteElement.childNodes[0].className = 'highlight'; + // Clear any prior messages + messageElement.innerText = ''; + typedValueElement.disabled = false; + // Clear the textbox + typedValueElement.value = ''; + // set focus + typedValueElement.focus(); + // set the event handler + // Start the timer + startTime = new Date().getTime(); + }); + + + // at the end of script.js +typedValueElement.addEventListener('input', () => { + // Get the current word + const currentWord = words[wordIndex]; + // get the current value + const typedValue = typedValueElement.value; + + if (typedValue === currentWord && wordIndex === words.length - 1) { + // end of sentence + // Display success + const elapsedTime = new Date().getTime() - startTime; + // Setup the textbox + const HighScore = localStorage.getItem("hs"); + let message = ``; + if(HighScore != null){ + message= `CONGRATULATIONS! You finished in ${elapsedTime / 1000} seconds.Current highscore is ${HighScore}`; + } + if(HighScore>elapsedTime / 1000 || HighScore == null){ + message = `CONGRATULATIONS! You finished with a new Highscore of ${elapsedTime / 1000} seconds.`; + localStorage.setItem("hs",elapsedTime/1000); + } + messageElement.innerText = message; + WinBox.style.visibility = 'visible'; + //Challenges + typedValueElement.disabled = true; + typedValueElement.removeEventListener('input'); + + + + } else if (typedValue.endsWith(' ') && typedValue.trim() === currentWord) { + // end of word + // clear the typedValueElement for the new word + typedValueElement.value = ''; + // move to the next word + wordIndex++; + // reset the class name for all elements in quote + for (const wordElement of quoteElement.childNodes) { + wordElement.className = ''; + } + // highlight the new word + quoteElement.childNodes[wordIndex].className = 'highlight'; + } else if (currentWord.startsWith(typedValue)) { + // currently correct + // highlight the next word + typedValueElement.className = ''; + } else { + // error state + typedValueElement.className = 'error'; + } + }); \ No newline at end of file diff --git a/3-typing-game/style.css b/3-typing-game/style.css new file mode 100644 index 0000000..c69b562 --- /dev/null +++ b/3-typing-game/style.css @@ -0,0 +1,27 @@ +.highlight { + background-color: yellow; + } + + .error { + background-color: lightcoral; + border: red; + } + .sep{ + margin-top: 20px; + margin-bottom: 30px; + width: 500px; + border: 1px solid black; + } + .box { + width: 500px; + background-color: lightgreen; + height: 170px; + border: 1px solid black; + padding: 10px; + text-align: center; + margin: 10px; + } + .x{ + margin-left: 480px; + } + \ No newline at end of file diff --git a/4-bank-project/bank-project-solution/app.js b/4-bank-project/bank-project-solution/app.js new file mode 100644 index 0000000..ccfc3b6 --- /dev/null +++ b/4-bank-project/bank-project-solution/app.js @@ -0,0 +1,189 @@ +// ----------------------------- Routes ----------------------------- +const routes = { + '/login': { templateId: 'login', title: 'Login Page' }, + '/dashboard': { templateId: 'dashboard', title: 'Dashboard', init: refresh }, + '/credits': { templateId: 'credits', title: 'Credits' }, +}; + +// ----------------------------- Global Variables ----------------------------- +let user = ""; +const storageKey = 'savedAccount'; + +let state = Object.freeze({ + account: null // Stores the current user's account data +}); + +// ----------------------------- State Management ----------------------------- + +function updateState(property, newData) { + state = Object.freeze({ + ...state, + [property]: newData + }); + user = state.account.user; // Update the global `user` variable + localStorage.setItem(storageKey, state.account.user); // Save to LocalStorage +} + +// ----------------------------- Route Management ----------------------------- +function updateRoute() { + const path = window.location.pathname; // Get the current URL path + const route = routes[path]; + + if (!route) { + return navigate('/dashboard'); // Redirect to dashboard if route not found + } + + document.getElementById('title').innerText = route.title; // Update the page title + + // Call route's initialization function if defined + if (typeof route.init === 'function') { + route.init(); + } + + // Load the appropriate template into the app container + const template = document.getElementById(route.templateId); + const view = template.content.cloneNode(true); + const app = document.getElementById('app'); + app.innerHTML = ''; // Clear previous content + app.appendChild(view); + + if (typeof route.init === 'function') { + route.init(); // Ensure init function is called again + } + + dashboardLoaded = true; // Update dashboard state (if used) +} + +// ----------------------------- Transactions ----------------------------- + +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; // Set date + tr.children[1].textContent = transaction.object; // Set object + tr.children[2].textContent = transaction.amount.toFixed(2); // Set amount + return transactionRow; +} + + +async function createTransaction(account) { + try { + const response = await fetch('http://localhost:5000/api/accounts/' + user + '/transactions', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: account + }); + return await response.json(); + } catch (error) { + return { error: error.message || 'Unknown error' }; + } +} + + +function AddNewTransaction(event) { + event.preventDefault(); + const registerForm = document.getElementById('popup-box'); + const formData = new FormData(registerForm); // Collect form data + const jsonData = JSON.stringify(Object.fromEntries(formData)); // Convert to JSON + createTransaction(jsonData); // Send transaction to the server + navigate('/dashboard'); // Redirect to dashboard +} + +// ----------------------------- Popup Handling ----------------------------- +//Toggles the visibility of the popup box. + +function TogglePopup() { + const popup = document.getElementById("popup-box"); + if (popup.style.visibility === "hidden" || !popup.style.visibility) { + popup.style.visibility = "visible"; // Show popup + } else { + popup.style.visibility = "hidden"; // Hide popup + } +} + +// ----------------------------- Authentication ----------------------------- + +//Logs the user out and redirects to the login page. + +function logout() { + updateState('account', null); // Clear account data + navigate('/login'); +} + + +//Logs the user in by verifying credentials + +async function login() { + const loginForm = document.getElementById('loginForm'); + const user = loginForm.user.value; + const data = await getAccount(user); // Fetch account data + + if (data.error) { + return updateElement('loginError', data.error); + } + + updateState('account', data); // Update state with account data + navigate('/dashboard'); // Redirect to dashboard +} + + +async function getAccount(user) { + try { + const response = await fetch('//localhost:5000/api/accounts/' + encodeURIComponent(user)); + return await response.json(); + } catch (error) { + return { error: error.message || 'Unknown error' }; + } +} + +// ----------------------------- Dashboard Updates ----------------------------- +function updateDashboard() { + const account = state.account; + if (!account) { + return logout(); // Logout if no account data + } + + // Update UI elements with account details + updateElement('description', account.description); + updateElement('balance', account.balance.toFixed(2)); + updateElement('currency', account.currency); + + // Builds transactions table + const transactionsRows = document.createDocumentFragment(); + for (const transaction of account.transactions) { + const transactionRow = createTransactionRow(transaction); + transactionsRows.appendChild(transactionRow); + } + updateElement('transactions', transactionsRows); +} + + +//Refreshes the account data and updates the dashboard. + +async function refresh() { + await updateAccountData(); + updateDashboard(); +} + +// ----------------------------- Utility Functions ----------------------------- +function updateElement(id, textOrNode) { + const element = document.getElementById(id); + element.textContent = ''; + element.append(textOrNode); +} + +// ----------------------------- Initialization ----------------------------- +async function init() { + const savedAccount = localStorage.getItem(storageKey); + const data = await getAccount(savedAccount); + if (data.error) { + return logout(); + } + updateState('account', data); + + // Setup navigation handling + window.onpopstate = () => updateRoute(); + updateRoute(); +} +init(); diff --git a/4-bank-project/bank-project-solution/index.html b/4-bank-project/bank-project-solution/index.html new file mode 100644 index 0000000..dfb4300 --- /dev/null +++ b/4-bank-project/bank-project-solution/index.html @@ -0,0 +1,131 @@ + + + + + + + + Bank App + + + + + + + +
Loading...
+ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/4-bank-project/bank-project-solution/logo.png b/4-bank-project/bank-project-solution/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..38e1d03bc27ea5fc0666ccedd29354268f688772 GIT binary patch literal 29786 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 literal 0 HcmV?d00001 diff --git a/4-bank-project/bank-project-solution/style.css b/4-bank-project/bank-project-solution/style.css new file mode 100644 index 0000000..d546a37 --- /dev/null +++ b/4-bank-project/bank-project-solution/style.css @@ -0,0 +1,106 @@ +html { + height: 100%; +} + +body { + height: 100%; + margin: 0; + background-repeat: no-repeat; + background-attachment: fixed; + background-image: linear-gradient(to bottom, rgb(255, 255, 255), rgb(196, 196, 196)); +} + +.modal{ + background-color: white; +} + + +#app{ + height: 100%; + width: 100%; + display: flex; /* Enables flexbox on body */ + justify-content: center; /* Centers horizontally */ + align-items: center; /* Centers vertically */ + text-align: center; +} + +#H1 { + color: white; +} + +.inputLogin { + align-self: center; + width: 400px; + height: 25px; +} + +#LoginMenu{ + background-repeat: no-repeat; + background-attachment: fixed; + background-image: linear-gradient(to bottom, rgb(0, 204, 255), rgb(4, 168, 173)); + width: 30%; + min-height: 80%; + border-radius: 3%; +} + + +#DashboardMenu { + background-repeat: no-repeat; + background-attachment: fixed; + background-image: linear-gradient(to bottom, rgb(0, 204, 255), rgb(4, 168, 173)); + width: 90%; + min-height: 90%; + padding: 20px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + display: flex; + flex-direction: column; + justify-content: space-between; + color: white; +} + +header { + display: flex; + justify-content: space-between; + align-items: center; + color: white; +} + +header a { + text-decoration: none; + color: white; + margin: 0 10px; + font-weight: bold; +} + +section { + background: rgba(255, 255, 255, 0.8); + padding: 15px; + color: black; +} + +section h2 { + margin-top: 0; +} + +table { + width: 100%; + border-collapse: collapse; + margin-top: 10px; +} + +table th, table td { + padding: 10px; + border: 1px solid #ccc; + text-align: left; +} + +table th { + background-color: rgba(0, 204, 255, 0.7); + color: white; +} + + +#registerForm { + display: flex; + flex-direction: column; +} diff --git a/4-bank-project/index.html b/4-bank-project/index.html new file mode 100644 index 0000000..5ade482 --- /dev/null +++ b/4-bank-project/index.html @@ -0,0 +1,11 @@ + + + + + + Bank App + + + + + From 3a0570e3dbb6f68e19a204239bc36c32e2be1aff Mon Sep 17 00:00:00 2001 From: PinJinx Date: Fri, 29 Nov 2024 15:53:01 +0530 Subject: [PATCH 3/3] Part 4 --- 4-bank-project/Solution.md | 147 ++++++++++++++++++++ 4-bank-project/bank-project-solution/app.js | 2 +- 2 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 4-bank-project/Solution.md diff --git a/4-bank-project/Solution.md b/4-bank-project/Solution.md new file mode 100644 index 0000000..fabbabe --- /dev/null +++ b/4-bank-project/Solution.md @@ -0,0 +1,147 @@ +``` +This is the Documentation for Bank Project + + +1-template-route +Challenge:Add a new template and route for a third page that shows the credits for this app. + +This Challenge was pretty straight forward and simple i created the following template for the credits + +Then I added a button to the dashboard to display this template + + + +Assignment: +The routes declaration contains currently only the template ID to use. But when displaying a new page, a bit more is needed sometimes. Let's improve our routing implementation with two additional features: +1.Give titles to each template and update the window title with this new title when the template changes. +2.Add an option to run some code after the template changes. +We want to print 'Dashboard is shown' in the developer console every time the dashboard page is displayed. + +For Giving the titles to each page i stored the titles in routes as +const routes = { + '/login': { templateId: 'login', title: 'Login Page' }, + '/dashboard': { templateId: 'dashboard', title: 'Dashboard', init: refresh }, + '/credits': { templateId: 'credits', title: 'Credits' }, +}; +Then I set the title elements text to this using the following piece of code in updateroute() +document.getElementById('title').innerText = route.title; + +2-forms +Challenge: +Show an error message in the HTML if the user already exists. + +Assignment: +Create a new styles.css file and add a link to it in your current index.html file. In the CSS file you just created add some styling to make the Login and Dashboard page looks nice and tidy. Try to create a color theme to give your app its own branding. + +For this task i have styled both the dashboard and the login page with css and partially with html I have attached both these files in solution folder of this PR + + +3-data +Challenge: +Work together to make the dashboard page look like a real banking app. If you already styled your app, try to use media queries to create a responsive design working nicely on both desktop and mobile devices. + +For this task i have styled the tables inside dashboard where it shows transaction and made nice looking for both pc and mobile with css and partially with html I have attached both these files in solution folder of this PR + + +Assignment: +As your codebase grows, it's important to refactor your code frequently to keep it readable and maintainable over time. Add comments and refactor your app.js to improve the code quality: + + Extract constants, like the server API base URL + Factorize similar code: for example you can create a sendRequest() function to regroup the code used in both createAccount() and getAccount() + Reorganize the code to make it easier to read, and add comments + +For this assignment I have refactored the code and added comments for easier reading and all over organized the entire code. The code file attached in solution folder has been done in the same way. + + +4-state-management +Challenge: +Now that we reload the account data every time the dashboard is loaded, do you think we still need to persist all the account data? +Try working together to change what is saved and loaded from local Storage to only include what is absolutely required for the app to work. + + +For this task I changed the code to only store the name of currently logged in user by updating the following: + +function updateState(property, newData) { + state = Object.freeze({ + ...state, + [property]: newData + }); + user = state.account.user; // Update the global `user` variable + localStorage.setItem(storageKey, state.account.user); // Save to LocalStorage +} + + +Then when the tab is reloaded or opened I set the user this variable using the following: + +async function init() { + const savedAccount = localStorage.getItem(storageKey); + const data = await getAccount(savedAccount); + if (data.error) { + return logout(); + } + updateState('account', data); + + // Setup navigation handling + window.onpopstate = () => updateRoute(); + updateRoute(); +} + + +Assignment: +Our bank app is still missing one important feature: the possibility to enter new transactions. Using everything that you've learnt in the four previous lessons, implement an "Add transaction" dialog: + + Add an "Add transaction" button in the dashboard page + Either create a new page with an HTML template, or use JavaScript to show/hide the dialog HTML without leaving the dashboard page (you can use hidden property for that, or CSS classes) + Make sure you handle keyboard and screen reader accessibility for the dialog + Implement an HTML form to receive input data + Create JSON data from the form data and send it to the API + Update the dashboard page with the new data + + + +Soln: +I have added a trasaction box for new transaction inside of bank app I have also styled this box to fit in with reset of the page here is the code: + +Code: +async function createTransaction(account) { + try { + const response = await fetch('http://localhost:5000/api/accounts/' + user + '/transactions', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: account + }); + return await response.json(); + } catch (error) { + return { error: error.message || 'Unknown error' }; + } +} + + +function AddNewTransaction(event) { + event.preventDefault(); + const registerForm = document.getElementById('popup-box'); + const formData = new FormData(registerForm); // Collect form data + const jsonData = JSON.stringify(Object.fromEntries(formData)); // Convert to JSON + createTransaction(jsonData); // Send transaction to the server + navigate('/dashboard'); // Redirect to dashboard +} + +// ----------------------------- Popup Handling ----------------------------- +//Toggles the visibility of the popup box that is the new transaction box. + +function TogglePopup() { + const popup = document.getElementById("popup-box"); + if (popup.style.visibility === "hidden" || !popup.style.visibility) { + popup.style.visibility = "visible"; // Show popup + } else { + popup.style.visibility = "hidden"; // Hide popup + } +}``` diff --git a/4-bank-project/bank-project-solution/app.js b/4-bank-project/bank-project-solution/app.js index ccfc3b6..838bcba 100644 --- a/4-bank-project/bank-project-solution/app.js +++ b/4-bank-project/bank-project-solution/app.js @@ -91,7 +91,7 @@ function AddNewTransaction(event) { } // ----------------------------- Popup Handling ----------------------------- -//Toggles the visibility of the popup box. +//Toggles the visibility of the popup box that is the new transaction box. function TogglePopup() { const popup = document.getElementById("popup-box");