<div id="app"></div>#container {
  width: 100%;
  margin: auto;
  text-align: center;
  margin: 20px;
}
input {
  min-width: 40px;
}
/*
 * https://frontendeval.com/questions/countdown-timer
 *
 * Create a countdown timer that notifies the user
 */
const { useEffect, useState } = React;
const App = () => {
  const [hours, setHours] = useState("");
  const [minutes, setMinutes] = useState("");
  const [seconds, setSeconds] = useState("");
  const [timerRunning, setTimerRunning] = useState(false);
  const [timerPaused, setTimerPaused] = useState(false);
  const [canSendNotif, setCanSendNotif] = useState(false);
  const [completionMessage, setCompletionMessage] = useState("Time's up!");
  const [showCompletionMessageInput, setShowCompletionMessageInput] = useState(
    false
  );
  // these are necessary to maintain precision when accounting for pauses
  const [timerMsValue, setTimerMsValue] = useState(null);
  const [timerStartTime, setTimerStartTime] = useState(null);
  const [msUntilNextRender, setMsUntilNextRender] = useState(0);
  // for testing improved precision
  // const [timeFirstStart, setTimeFirstStart] = useState(0)
  // const [pauseStart, setPauseStart] = useState(0)
  // const [pausedTime, setPausedTime] = useState(0)
  const resetTimer = () => {
    setTimerRunning(false);
    setTimerPaused(false);
    setHours("");
    setMinutes("");
    setSeconds("");
    setTimerMsValue(null);
    setTimerStartTime(null);
    // for testing improved precision
    // setTimeFirstStart(0)
    // setPauseStart(0)
    // setPausedTime(0)
  };
  const decreaseByOneSecond = () => {
    if (hours && minutes === 0 && seconds === 0) {
      const newHours = hours - 1;
      setHours(newHours);
      const newMinutes = 59;
      setMinutes(newMinutes);
      const newSeconds = 59;
      setSeconds(newSeconds);
    } else if (minutes && seconds === 0) {
      const newMinutes = minutes - 1;
      setMinutes(newMinutes);
      const newSeconds = 59;
      setSeconds(newSeconds);
    } else {
      const newSeconds = seconds - 1;
      setSeconds(newSeconds);
    }
  };
  useEffect(() => {
    if ("Notification" in window) {
      // console.log("permission status: ", Notification.permission);
      // always "denied", even if I explicitly to allow in chrome site settings
      if (Notification.permission === "granted") {
        setCanSendNotif(true);
      } else {
        Notification.requestPermission().then((permission) => {
          if (permission === "granted") {
            setCanSendNotif(true);
          }
        });
      }
    }
  }, [timerRunning]);
  useEffect(() => {
    if (hours !== "" && minutes !== "" && seconds !== "") {
      const displayHours = hours > 9 ? hours : `0${hours}`;
      const displayMins = minutes > 9 ? minutes : `0${minutes}`;
      const displaySecs = seconds > 9 ? seconds : `0${seconds}`;
      document.title = `${displayHours}:${displayMins}:${displaySecs}`;
    } else {
      document.title = "Countdown Timer";
    }
    if (hours === 0 && minutes === 0 && seconds === 0) {
      if (canSendNotif) {
        const notification = new Notification(completionMessage);
      } else {
        alert(completionMessage);
      }
      // const totalTimeElapsed = Date.now() - timeFirstStart
      // console.log("totalTimeElapsed: ", totalTimeElapsed)
      // console.log("totalPausedTime: ", pausedTime)
      // console.log("timeElapsed minus Paused: ", totalTimeElapsed - pausedTime)
      // Alert the user with a native notification using the Notification API
      // https://developer.mozilla.org/en-US/docs/Web/API/notification
      resetTimer();
    } else {
      if (timerRunning) {
        // timer is taking up to 999ms longer depending on when you click pause
        let intervalId;
        // console.log("msUntilNextRender: ", msUntilNextRender)
        if (msUntilNextRender) {
          intervalId = setInterval(
            () => decreaseByOneSecond(),
            msUntilNextRender
          );
          setMsUntilNextRender(0);
        } else {
          intervalId = setInterval(() => decreaseByOneSecond(), 1000);
        }
        return () => clearInterval(intervalId);
      }
    }
  }, [timerRunning, seconds]);
  const handleSubmit = (e) => {
    e.preventDefault();
    const form = e.target;
    const formData = new FormData(form);
    let formJson = Object.fromEntries(formData.entries());
    for (const property in formJson) {
      formJson[property] = +formJson[property] ?? 0;
    }
    // first time start is pressed
    if (!timerPaused) {
      setHours(formJson.hours);
      setMinutes(formJson.minutes);
      setSeconds(formJson.seconds);
      const totalMs =
        ((formJson.hours * 60 + formJson.minutes) * 60 + formJson.seconds) *
        1000;
      setTimerMsValue(totalMs);
      // for testing improved precision
      // const timeStartMs = Date.now()
      // console.log("firstStartTime: ", timeStartMs)
      // setTimeFirstStart(timeStartMs)
    }
    if (timerPaused) {
      setTimerPaused(false);
      // for testing improved precision
      // const newPausePeriod = Date.now() - pauseStart
      // const totalPausedTime = pausedTime + newPausePeriod
      // setPausedTime(totalPausedTime)
    }
    const startTime = Date.now();
    // console.log("startTime: ", startTime)
    setTimerStartTime(startTime);
    setTimerRunning(true);
  };
  const handleClickPause = () => {
    setTimerPaused(true);
    setTimerRunning(false);
    // more precise - keeping track of elapsed 1-999ms between last render and clicking pause
    // example: press pause 700ms after rendering 11seconds
    // 10seconds and 300ms when paused so timeRemaining = 10,300
    // timerRemaining % 1000 = msElapsed that wasn't being accounted for
    const msRemaining = timerMsValue - (Date.now() - timerStartTime);
    setTimerMsValue(msRemaining);
    // console.log("seconds: ", seconds)
    // console.log("msRemaining: ", msRemaining)
    const msRemainingThisSecond = msRemaining % 1000;
    setMsUntilNextRender(msRemainingThisSecond);
    // for testing improved precision
    // const pauseStartTime = Date.now()
    // setPauseStart(pauseStartTime)
  };
  const handleClickReset = () => {
    resetTimer();
  };
  const handleSaveMessageSubmit = (e) => {
    e.preventDefault();
    const form = e.target;
    const formData = new FormData(form);
    const formJson = Object.fromEntries(formData.entries());
    const newMessage = formJson.completionMessage;
    setCompletionMessage(newMessage);
  };
  const handleClickCancel = () => {
    setShowCompletionMessageInput(false);
  };
  return (
    <div id="container">
      <h1>Countdown timer</h1>
      <div style={{ display: "flex", justifyContent: "center" }}>
        <form onSubmit={handleSubmit}>
          <input
            id="hours"
            name="hours"
            type="number"
            min="0"
            max="99"
            placeholder="HH"
            aria-label="Hours"
            value={hours === "" || hours > 9 ? hours : `0${hours}`}
            onChange={(e) => setHours(e.target.value)}
          />{" "}
          :{" "}
          <input
            id="minutes"
            name="minutes"
            type="number"
            min="0"
            max="60"
            placeholder="MM"
            aria-label="Minutes"
            value={minutes === "" || minutes > 9 ? minutes : `0${minutes}`}
            onChange={(e) => setMinutes(e.target.value)}
          />{" "}
          :{" "}
          <input
            id="seconds"
            name="seconds"
            type="number"
            min="0"
            max="60"
            placeholder="SS"
            aria-label="Seconds"
            value={seconds === "" || seconds > 9 ? seconds : `0${seconds}`}
            onChange={(e) => setSeconds(e.target.value)}
          />{" "}
          {!timerRunning && !timerPaused && <button>Start</button>}
          {!timerRunning && timerPaused && <button>Continue</button>}
        </form>
        {timerRunning && (
          <button type="button" onClick={handleClickPause}>
            Pause
          </button>
        )}
        {(timerRunning || timerPaused) && (
          <button type="button" onClick={handleClickReset}>
            Reset
          </button>
        )}
      </div>
      <div style={{ marginTop: "10px" }}>
        Completion Message: {completionMessage}
      </div>
      <div style={{ marginTop: "10px" }}>
        {!showCompletionMessageInput ? (
          <button onClick={() => setShowCompletionMessageInput(true)}>
            Customize Completion Message
          </button>
        ) : (
          <form onSubmit={handleSaveMessageSubmit}>
            <label>New Completion Message </label>
            <input
              type="text"
              name="completionMessage"
              placeholder={completionMessage}
            />{" "}
            <button>Save</button>{" "}
            <button type="button" onClick={handleClickCancel}>
              Cancel
            </button>
          </form>
        )}
      </div>
    </div>
  );
};
ReactDOM.render(<App />, document.getElementById("app"));
// const MILLISECONDS_PER_SECOND = 1000
// const SECONDS_PER_MINUTE = 60
// const MILLISECONDS_PER_MINUTE = MILLISECONDS_PER_SECOND * SECONDS_PER_MINUTE
// const MINUTES_PER_HOUR = 60
// const MILLISECONDS_PER_HOUR = MILLISECONDS_PER_MINUTE * MINUTES_PER_HOUR
//   // getDisplayTimeFromMs(3700000)
//   // 1 hour, 1 minute, 40 seconds
//   const getDisplayTimeFromMs = (ms) => {
//   // 3,600,000 MS in 1 HOUR
//   // assume 3,700,000 => 1 hour, 100,000 MS
//   // 100,000 / 60,000 = 1 minute, 40,000 MS
//   // 40,000 / 1,000 = 40 seconds
//   const hours = Math.floor(ms / MILLISECONDS_PER_HOUR)
//   let remaining = ms % MILLISECONDS_PER_HOUR
//   const minutes = Math.floor(remaining / MILLISECONDS_PER_MINUTE)
//   remaining = ms % MILLISECONDS_PER_MINUTE
//   const seconds = Math.floor(remaining / MILLISECONDS_PER_SECOND)
//   return { hours, minutes, seconds }
// }
This Pen doesn't use any external CSS resources.