Skip to content
Closed
78 changes: 77 additions & 1 deletion Sprint-3/alarmclock/alarmclock.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,73 @@
function setAlarm() {}
// Constants for time conversion
const SECONDS_PER_MINUTE = 60;
const MILLISECONDS_PER_SECOND = 1000;

let intervalId;

/**
* Formats time in seconds to MM:SS format
*/
function formatTimeDisplay(totalSeconds) {
const minutes = Math.floor(totalSeconds / SECONDS_PER_MINUTE);
const seconds = totalSeconds % SECONDS_PER_MINUTE;

return (
"Time Remaining: " +
String(minutes).padStart(2, "0") +
":" +
String(seconds).padStart(2, "0")
);
}

function setAlarm() {
const input = document.getElementById("alarmSet");
const heading = document.getElementById("timeRemaining");
const errorMsg = document.getElementById("alarmError");

// Reset alarm sound + flashing background before new countdown
pauseAlarm();

let raw = input.value.trim();

// Clear previous error
errorMsg.textContent = "";

// STRONG VALIDATION: must be digits only
if (!/^\d+$/.test(raw)) {
heading.innerText = "Time Remaining: 00:00";
errorMsg.textContent =
"Invalid input. Please enter a whole number of seconds (e.g., 10, 30, 120). Decimals and text are not allowed.";
return;
}

let totalSeconds = Number(raw);

// Prevent extremely large or zero values
if (totalSeconds === 0 || totalSeconds > 86400) {
Comment on lines +35 to +46
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could consider combining these two checks into:

 let totalSeconds = Number(raw);
 if (!Number.isInteger(totalSeconds) || totalSeconds <= 0 || totalSeconds > 86400) {
    errorMsg.textContent = "Please enter an integer between 1 and 86400";
    return;
 }

heading.innerText = "Time Remaining: 00:00";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same time-update code shows up in multiple places, which is a good signal that it could be factored into a utility function.

errorMsg.textContent =
"Please enter a value between 1 and 86,400 seconds. Examples: 10, 45, 300.";
return;
}

// Reset any existing countdown
clearInterval(intervalId);
Comment on lines +53 to +54
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could consider placing all the "resetting" code together (code on lines 54 and 28).


heading.innerText = formatTimeDisplay(totalSeconds);

intervalId = setInterval(() => {
totalSeconds--;

if (totalSeconds <= 0) {
heading.innerText = formatTimeDisplay(0);
clearInterval(intervalId);
playAlarm();
return;
}

heading.innerText = formatTimeDisplay(totalSeconds);
}, MILLISECONDS_PER_SECOND);
}

// DO NOT EDIT BELOW HERE

Expand All @@ -12,14 +81,21 @@ function setup() {
document.getElementById("stop").addEventListener("click", () => {
pauseAlarm();
});

// Allow Enter key to trigger alarm
document.getElementById("alarmSet").addEventListener("keyup", (e) => {
if (e.key === "Enter") setAlarm();
});
}

function playAlarm() {
audio.play();
document.body.classList.add("alarm-active");
}

function pauseAlarm() {
audio.pause();
document.body.classList.remove("alarm-active");
}

window.onload = setup;
41 changes: 33 additions & 8 deletions Sprint-3/alarmclock/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,42 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="style.css" />
<title>Title here</title>
<title>Alarm clock app</title>
</head>

<body>
<div class="centre">
<h1 id="timeRemaining">Time Remaining: 00:00</h1>
<label for="alarmSet">Set time to:</label>
<input id="alarmSet" type="number" />
<main class="centre">
<h1 id="timeRemaining" aria-live="polite" role="timer">
Time Remaining: 00:00
</h1>
Comment on lines +12 to +14
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could consider express code on lines 12-14 as:

      <div id="timeRemaining" aria-live="polite" role="timer">
        Time Remaining: <span id="theTime">00:00</span>
      </div>

Note: Use AI to find out why if needed.


<label for="alarmSet">Set alarm time (seconds):</label>

<input
id="alarmSet"
type="number"
min="1"
step="1"
inputmode="numeric"
aria-describedby="alarmError"
/>

<!-- Visible and screen-reader friendly error message -->
<p id="alarmError" class="error-message" aria-live="assertive"></p>

<button
id="set"
type="button"
aria-label="Set alarm using the entered number of seconds"
>
Set Alarm
</button>

<button id="stop" type="button" aria-label="Stop the alarm sound">
Stop Alarm
</button>
</main>

<button id="set" type="button">Set Alarm</button>
<button id="stop" type="button">Stop Alarm</button>
</div>
<script src="alarmclock.js"></script>
</body>
</html>
32 changes: 31 additions & 1 deletion Sprint-3/alarmclock/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
position: fixed;
top: 50%;
left: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
text-align: center;
}

#alarmSet {
Expand All @@ -13,3 +13,33 @@
h1 {
text-align: center;
}

/* Visible, helpful error message */
.error-message {
color: #b00020;
font-size: 0.9rem;
margin-top: -10px;
margin-bottom: 20px;
min-height: 1.2rem; /* Prevent layout shift */
}

/* Accessible focus outline */
button:focus,
input:focus {
outline: 3px solid #005fcc;
outline-offset: 3px;
}

/* Flashing background when alarm is active */
.alarm-active {
animation: flash 1s infinite alternate;
}

@keyframes flash {
from {
background-color: #fff;
}
to {
background-color: #ffcccc;
}
}
2 changes: 1 addition & 1 deletion Sprint-3/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/user-event": "^14.6.1",
"jest": "^30.0.4",
"jest": "^30.2.0",
"jest-environment-jsdom": "^30.0.4"
}
}