curriculum/challenges/english/blocks/lab-one-time-password-generator/67c562286b29447da020d407.md
In this lab, you will generate a 6-digit OTP (One-Time Password) and display it to the user. The OTP will expire after 5 seconds, and a new OTP will be generated when the user clicks the "Generate New OTP" button.
Objective: Fulfill the user stories below and get all the tests to pass to complete the lab.
User Stories:
You should use the useEffect hook to manage the countdown timer.
Your OTPGenerator component should return a div element with the class name container.
The div having the class container should include the following elements:
h1 element with the ID otp-title and text "OTP Generator".h2 element with the ID otp-display that either displays the message "Click 'Generate OTP' to get a code" or shows the generated OTP if one is available.p element with the ID otp-timer and aria-live attribute set to a valid value that:
"Expires in: X seconds" after the button is clicked, where X represents the remaining time before the OTP expires."OTP expired. Click the button to generate a new OTP." once the countdown reaches 0.button element with the ID generate-otp-button labeled "Generate OTP". When clicked, it should generate a new OTP and start a 5-second countdown."Generate OTP" button must be disabled while the countdown is active.You should ensure the countdown timer stops automatically once it reaches 0 seconds to prevent unnecessary updates.
The generated OTP should be 6 digits long.
let clock = __FakeTimers.install();
clock.uninstall();
You should not remove the existing const { useState, useEffect, useRef } = React; assignment from the code.
assert.match(code, /{\s*(useState\s*,\s*useEffect\s*,\s*useRef|useState\s*,\s*useRef\s*,\s*useEffect|useEffect\s*,\s*useState\s*,\s*useRef|useEffect\s*,\s*useRef\s*,\s*useState|useRef\s*,\s*useState\s*,\s*useEffect|useRef\s*,\s*useEffect\s*,\s*useState|useState|useEffect|useRef)\s*}\s*=\s*React;/);
You should render a div with the class .container.
const containerDiv = document.querySelector('.container');
assert.exists(containerDiv);
The .container should contain an h1 element with the ID otp-title the text "OTP Generator".
const h1 = document.querySelector(".container h1#otp-title");
assert.exists(h1);
assert.equal(h1.textContent, "OTP Generator");
The .container should contain an h2 element with the ID otp-display that displays the OTP when generated.
const h2 = document.querySelector(".container h2#otp-display");
assert.exists(h2);
Initially, the h2 inside .container should display the message "Click 'Generate OTP' to get a code".
assert.strictEqual(document.querySelector('.container h2#otp-display').textContent.trim(), "Click 'Generate OTP' to get a code");
The .container element should contain a p element with the ID otp-timer.
const container = document.querySelector('.container');
assert.exists(container, "The container element should exist");
const pElements = container.querySelectorAll('p');
assert.strictEqual(pElements.length, 1);
assert.strictEqual(pElements[0].id, "otp-timer", "The p element should have the id otp-timer");
The p element should have an aria-live attribute set to assertive or polite.
const pElement = document.querySelector('.container p');
assert.exists(pElement?.ariaLive);
assert.oneOf(pElement?.ariaLive, ["assertive", "polite"]);
Initially, the p element should be empty.
const pElement = document.querySelector('.container p');
assert.strictEqual(pElement?.innerText, "", "The `p` element should be empty");
The .container should contain a button element with the ID generate-otp-button and text "Generate OTP".
const button = document.querySelector('.container button#generate-otp-button').textContent;
assert.exists(button);
assert.strictEqual(button, 'Generate OTP');
When the button is clicked, it should display a new OTP in the h2 element with id otp-display.
try {
// allow the page to render
clock.tick(1)
const button = document.querySelector(".container button#generate-otp-button");
const h2 = document.querySelector(".container h2#otp-display");
button.click();
clock.tick(300)
assert.match(h2.textContent?.trim(), /[0-9]/, "OTP should be a number");
} finally {
// we need to wait for the countdown to finish for the next test to work otherwise the button will be disabled
await clock.runAllAsync();
}
The generated OTP should be 6 digits long.
try {
const button = document.querySelector(".container button#generate-otp-button");
button.click();
clock.tick(1);
const h2 = document.querySelector(".container h2#otp-display");
assert.match(h2.textContent.trim(), /^[0-9]{6}$/, "OTP should be a 6-digit number");
} finally {
// we need to wait for the countdown to finish for the next test to work otherwise the button will be disabled
await clock.runAllAsync();
}
When the button is clicked, the p element with id of otp-timer should show a 5-second countdown.
try {
// allow the page to render
clock.tick(1)
const button = document.querySelector(".container button#generate-otp-button");
const h2 = document.querySelector(".container h2#otp-display");
button.click();
clock.tick(300)
const p = document.querySelector(".container p#otp-timer");
assert.match(p.textContent, /Expires in: 5 seconds/, "Countdown should start at 5 seconds");
} finally {
// we need to wait for the countdown to finish for the next test to work otherwise the button will be disabled
await clock.runAllAsync();
}
The message in the p element with id otp-timer should update every second to show the remaining time.
const button = document.querySelector(".container button#generate-otp-button");
button.click();
clock.tick(1)
let p = document.querySelector(".container p#otp-timer");
assert.match(p.textContent, /Expires in: 5 seconds/);
await clock.tickAsync(1000)
p = document.querySelector(".container p#otp-timer");
assert.match(p.textContent, /Expires in: 4 seconds/, "Countdown should update to 4 seconds after 1 second");
The "Generate OTP" button should be disabled while the countdown timer is running.
try {
const button = document.querySelector(".container button#generate-otp-button");
button.click();
clock.tick(1);
assert.isTrue(button.disabled);
} finally {
await clock.runAllAsync();
}
The "Generate OTP" button should be enabled once the countdown timer reaches 0.
const button = document.querySelector(".container button#generate-otp-button");
button.click();
await clock.tickAsync(6000);
assert.isFalse(button.disabled);
When the countdown timer reaches 0, you should display the message OTP expired. Click the button to generate a new OTP..
const button = document.querySelector(".container button#generate-otp-button");
button.click();
await clock.tickAsync(6000);
const p = document.querySelector(".container p#otp-timer");
assert.equal(p.textContent.trim(), "OTP expired. Click the button to generate a new OTP.");
You should export the OTPGenerator component.
assert.isFunction(window.index.OTPGenerator);
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>OTP Generator</title>
<link rel="stylesheet" href="styles.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.development.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.development.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/7.26.5/babel.min.js"></script>
<script
data-plugins="transform-modules-umd"
type="text/babel"
src="index.jsx"
></script>
</head>
<body>
<div id="root"></div>
<script
data-plugins="transform-modules-umd"
type="text/babel"
data-presets="react"
data-type="module"
>
import { OTPGenerator } from './index.jsx';
ReactDOM.createRoot(document.getElementById('root')).render(<OTPGenerator />);
</script>
</body>
</html>
const { useState, useEffect, useRef } = React;
export const OTPGenerator = () => {};
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>OTP Generator</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.development.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.development.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/7.26.5/babel.min.js"></script>
<script
data-plugins="transform-modules-umd"
type="text/babel"
src="index.jsx"
></script>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<div id="root"></div>
<script
data-plugins="transform-modules-umd"
type="text/babel"
data-presets="react"
data-type="module"
>
import { OTPGenerator } from './index.jsx';
ReactDOM.createRoot(document.getElementById('root')).render(<OTPGenerator />);
</script>
</body>
</html>
body {
font-family: 'Castoro Titling', cursive;
}
.container {
text-align: center;
margin-top: 50px;
}
h1 {
font-size: 30px;
color: #333;
}
h2 {
font-size: 25px;
color: #555;
}
p {
font-size: 16px;
color: #888;
}
button {
padding: 10px 20px;
margin-top: 20px;
font-size: 16px;
border: none;
background-color: #383f59;
color: white;
border-radius: 5px;
}
button:disabled {
background-color:#57585c;
cursor: not-allowed;
}
button:hover {
background-color: #444;
}
const { useState, useEffect, useRef } = React;
export const OTPGenerator = () => {
const intervalId = useRef(null);
const [otp, setOtp] = useState(null);
const [secondsLeft, setSecondsLeft] = useState(5);
const [isCounting, setIsCounting] = useState(false);
const [hasGeneratedOtp, setHasGeneratedOtp] = useState(false);
const generateOTP = () => Math.floor(100000 + Math.random() * 900000);
const handleGenerateOtp = () => {
setOtp(generateOTP());
setSecondsLeft(5);
setIsCounting(true);
setHasGeneratedOtp(true);
intervalId.current = setInterval(() => {
setSecondsLeft(prevSeconds => prevSeconds - 1);
}, 1000);
};
useEffect(() => {
if (secondsLeft === 0) {
setIsCounting(false);
clearInterval(intervalId.current);
}
}, [secondsLeft]);
useEffect(
() => () => {
clearInterval(intervalId.current);
},
[]
);
return (
<div className='container'>
<h1 id='otp-title'>OTP Generator</h1>
<h2 id='otp-display'>
{otp ? otp : "Click 'Generate OTP' to get a code"}
</h2>
<p id='otp-timer' aria-live='assertive'>
{isCounting
? `Expires in: ${secondsLeft} seconds`
: hasGeneratedOtp &&
"OTP expired. Click the button to generate a new OTP."}
</p>
<button
id='generate-otp-button'
onClick={handleGenerateOtp}
disabled={isCounting}
>
Generate OTP
</button>
</div>
);
};