File size: 1,985 Bytes
a17d02d
 
 
 
 
 
 
 
 
 
be8595f
 
a17d02d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import { useCallback, useEffect, useRef, useState } from 'react'
import toast from 'react-hot-toast'
import clsx from 'clsx'
import { formatTime, TIMER_WARNINGS } from '../../utils/testEngineUtils'

export default function TimerDisplay({
  endTime,
  onExpire,
  announceWarnings = true,
  className = 'flex items-center gap-1.5 px-3 py-1 rounded font-mono font-bold text-sm border',
  lowClassName = 'bg-destructive/20 border-destructive/50 text-destructive timer-critical',
  normalClassName = 'bg-muted theme-border text-foreground',
}) {
  const getRemaining = useCallback(
    () => Number.isFinite(endTime) ? Math.max(0, endTime - Date.now()) : null,
    [endTime]
  )
  const [remaining, setRemaining] = useState(getRemaining)
  const expiredRef = useRef(false)
  const warnedRef = useRef(new Set())

  useEffect(() => {
    if (!Number.isFinite(endTime)) {
      setRemaining(null)
      return
    }

    expiredRef.current = false
    warnedRef.current = new Set()

    const timer = setInterval(() => {
      const left = Math.max(0, endTime - Date.now())

      setRemaining(left)

      const minsLeft = Math.ceil(left / 60000)
      if (announceWarnings && TIMER_WARNINGS.includes(minsLeft) && !warnedRef.current.has(minsLeft) && left > 1000) {
        warnedRef.current.add(minsLeft)
        if (minsLeft === 5 || minsLeft === 1) {
          toast(minsLeft === 1 ? '1 minute remaining!' : `${minsLeft} minutes remaining`, { duration: 4000 })
        }
      }

      if (left <= 0 && !expiredRef.current) {
        expiredRef.current = true
        clearInterval(timer)
        onExpire()
      }
    }, 1000)

    const left = Math.max(0, endTime - Date.now())
    setRemaining(left)

    return () => clearInterval(timer)
  }, [endTime, onExpire, announceWarnings])

  const isLow = remaining !== null && remaining < 5 * 60 * 1000

  return (
    <div className={clsx(className, isLow ? lowClassName : normalClassName)}>
      {formatTime(remaining)}
    </div>
  )
}