Level 1 Projects
1. ATM Bank Terminal Simulator
This guide is tailored for students who want to build an ATM terminal-like application using React and Vite frameworks, based on the files and designs you've shared.
Step 1: Initialize the Project
1. Create a new React + Vite project:
Copy npm create vite@latest million-dollar-bank -- --template react
cd million-dollar-bank
npm install
2. Install dependencies (if you use additional ones like React Router or icons):
Step 2: Set Up Project Structure
Structure your folders as follows:
Copy /src
/components
Header.jsx
Footer.jsx
Message.jsx
App.jsx
main.jsx
/style.css
/index.html
Add the provided App.jsx
and style.css
into your /src
and root respectively.
App.jsx style.css
Copy // App.jsx
import React from "react"
import Message from "./components/Message"
import Header from "./components/Header"
import Footer from "./components/Footer"
export default function App() {
const passCode = "1001"
const [userInput, setUserInput] = React.useState({
charOne: "",
charTwo: "",
charThree: "",
charFour: "",
})
const [verified, setVerified] = React.useState(undefined)
function handleChange(event) {
setUserInput(prev => ({...prev, [event.target.name]: event.target.value }))
}
function handleSubmit(event) {
event.preventDefault()
const combinedInput = Object.values(userInput).join("")
setVerified(combinedInput === passCode)
}
return (
<div className="wrapper">
<Header />
<form onSubmit={handleSubmit}>
<div>
<input
required
type="password"
name="charOne"
maxLength="1"
onChange={handleChange}
/>
<input
required
type="password"
name="charTwo"
maxLength="1"
onChange={handleChange}
/>
<input
required
type="password"
name="charThree"
maxLength="1"
onChange={handleChange}
/>
<input
required
type="password"
name="charFour"
maxLength="1"
onChange={handleChange}
/>
</div>
<button disabled={verified}>Submit</button>
<Message status={verified} />
</form>
<Footer />
</div>
)
}
Copy * {
box-sizing: border-box;
}
body {
margin: 0;
}
.wrapper {
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: url(./images/bady-abbas-c4XoMGxfsVU-unsplash.jpg);
background-size: cover;
background-position: center;
}
header,
footer {
position: absolute;
width: 100%;
background: #474056;
height: 70px;
color: #f7ece1;
display: flex;
justify-content: center;
align-items: center;
z-index: 5;
}
header {
top: 0;
}
footer {
bottom: 0;
}
h1 {
margin: 0;
font-size: 20px;
text-align: center;
color: #f9f8f8;
letter-spacing: 1px;
}
span {
color: #f9f8f8;
font-weight: 700;
}
form {
width: 350px;
height: 350px;
display: flex;
flex-direction: column;
align-items: center;
border-radius: 15px;
background: #111111bc;
transition: 0.4s ease-in-out;
position: relative;
}
.message-container {
display: flex;
flex-direction: column;
}
.message {
position: relative;
text-align: center;
color: #222222;
background: ghostwhite;
top: 3em;
padding: 6px;
border-radius: 5px;
width: 300px;
font-weight: bold;
}
.check {
position: relative;
align-self: center;
top: 24px;
width: 130px;
margin: 0;
}
input {
position: relative;
width: 2.5em;
height: 2.5em;
margin: 1em;
border-radius: 5px;
border: none;
outline: none;
background-color: rgb(235, 235, 235);
box-shadow: inset 3px 3px 6px #d1d1d1, inset -3px -3px 6px #ffffff;
top: 0.6em;
padding-left: 15px;
transition: 0.4s ease-in-out;
}
input:hover {
box-shadow: inset 0px 0px 0px #d1d1d1, inset 0px 0px 0px #ffffff;
background-color: lightgrey;
}
input:focus {
box-shadow: inset 0px 0px 0px #d1d1d1, inset 0px 0px 0px #ffffff;
background-color: lightgrey;
}
button {
font-size: 17px;
padding: 0.5em 2em;
border: transparent;
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.4);
background-image: linear-gradient(to bottom, #3891a6, #1d93b7, #0094c8, #1393d9, #3c91e6);
margin-top: 20px;
border-radius: 4px;
}
button:hover:not(:disabled) {
background: rgb(2, 0, 36);
background: linear-gradient(to top, rgba(30, 144, 255, 1) 0%, rgba(0, 212, 255, 1) 100%);
cursor: pointer;
}
button:active:not(:disabled) {
transform: scale(0.96)
}
button:focus:not(:focus-visible) {
outline: 0;
}
button:disabled {
pointer-events: none;
opacity: 0.3;
}
@media (max-height: 500px) {
header, footer {
display: none;
}
}
Step 3: Build Components
Copy // Header.jsx
export default function Header() {
return <header><h1>KOIDA Financial Services</h1></header>;
}
Copy // Footer.jsx
export default function Footer() {
return <footer><h1>Current Balance: $1,000,000</h1></footer>;
}
Copy // Message.jsx
export default function Message({ status }) {
if (status === undefined) return null;
return (
<div className="message-container">
<p className="message">
{status ? 'Access Granted. Welcome!' : 'Access Denied. Try Again.'}
</p>
</div>
);
}
Step 4: Complete App.jsx Logic
Your App.jsx
file is already well-implemented based on the challenge!
✅ State management for user inputs.
✅ handleChange()
function updates state dynamically.
✅ handleSubmit()
prevents page reload, checks input against passCode
, and sets verification state.
✅ DRY (Don't Repeat Yourself) principle is followed by using common handlers.
Step 5: Apply Styling
Use the provided style.css
. Add it to your main.jsx
:
Copy // main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
import './style.css';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
Step 6: Add Background Image
Place the background image in /public/images/
folder, and ensure the path in style.css
matches:
Copy // Some code
background: url(./images/bady-abbas-c4XoMGxfsVU-unsplash.jpg);
Step 7: Test the App
Run the development server:
Output
Check if:
✅ You can enter the 4-digit code.
✅ The correct code (1001
) grants access.
✅ Any other code denies access.
✅ Styling matches the provided screenshot.
Step 8 (Optional): Improvements 🚀
Auto-focus to the next input box after typing.
Add animations or transitions on success/fail.
Add sound effects for success/error.
Save "verified" state in localStorage.
Let's expand the app with two exciting features: PIN entry animations and auto-tab functionality !
Step 9: Add PIN Entry Animations
Update the CSS for Input Animations
In your style.css
, add:
Copy @keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
input:focus {
animation: pulse 0.3s ease-in-out;
}
This gives a nice pulse effect when typing PIN numbers.
Add Shake Animation for Incorrect PIN
In style.css
, add:
Copy @keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
.shake {
animation: shake 0.3s;
}
Apply Class Conditionally in App.jsx
Update your <form>
tag in App.jsx
to conditionally apply the shake animation:
Copy // App.jsx snippet
<form onSubmit={handleSubmit} className={verified === false ? 'shake' : ''}>
Step 10: Add Auto-Tab Functionality
Enhance handleChange()
function in App.jsx:
Copy // App.jsx snippet
function handleChange(event) {
const { name, value, nextSibling } = event.target;
setUserInput(prev => ({ ...prev, [name]: value }));
if (value && nextSibling && nextSibling.tagName === 'INPUT') {
nextSibling.focus();
}
}
Step 11: Test New Features 🚀
Run your app and test:
✅ Inputs pulse on focus.
✅ Form shakes when the wrong PIN is entered.
✅ Cursor auto-moves to the next input.
Modify the Footer component so as to display the balance when correct pin is entered. If PIN is entered incorrectly, warning red shield icon is displayed. If PIN is correctly verified, green shieled icon is displayed.
Install React Icons Library (optional but recommended):
Copy npm install react-icons
Update Footer.jsx Component:
Copy // Updated Footer.jsx
import { FaShieldAlt } from 'react-icons/fa';
import { MdWarning } from 'react-icons/md';
export default function Footer({ status }) {
return (
<footer>
{status === undefined && <h1>Please enter your PIN</h1>}
{status === true && (
<h1>
Current Balance: $1,000,000 {' '}
<FaShieldAlt style={{ color: 'limegreen' }} />
</h1>
)}
{status === false && (
<h1>
Access Denied {' '}
<MdWarning style={{ color: 'red' }} />
</h1>
)}
</footer>
);
}
Pass Status Prop from App.jsx:
In App.jsx
, update your Footer component usage:
Copy // App.jsx snippet
<Footer status={verified} />
✅ Enter correct PIN → Balance with green shield icon.
✅ Enter incorrect PIN → Warning with red icon.
✅ Before input → Prompt message "Please enter your PIN".
Output: