All files / web/src/hooks useGameBreakTimer.ts

87.85% Statements 94/107
100% Branches 15/15
50% Functions 1/2
87.85% Lines 94/107

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 1081x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 5x 4x 4x 4x 16x 16x 16x 1x 1x 1x 1x 1x 16x 16x 16x 1x 1x 1x 1x 1x 1x 1x 16x 16x 16x 16x 12x 12x 4x 4x                           4x 4x 4x 4x 4x 2x 2x 4x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x  
'use client'
 
import { useCallback, useEffect, useRef, useState } from 'react'
 
export interface UseGameBreakTimerOptions {
  maxDurationMinutes: number
  onTimeout: () => void
  enabled?: boolean
}
 
export interface UseGameBreakTimerResult {
  startTime: number | null
  elapsedMs: number
  remainingMs: number
  remainingMinutes: number
  remainingSeconds: number
  percentRemaining: number
  isActive: boolean
  start: () => void
  stop: () => void
  reset: () => void
}
 
export function useGameBreakTimer({
  maxDurationMinutes,
  onTimeout,
  enabled = true,
}: UseGameBreakTimerOptions): UseGameBreakTimerResult {
  const [startTime, setStartTime] = useState<number | null>(null)
  const [elapsedMs, setElapsedMs] = useState(0)
  const animationFrameRef = useRef<number | null>(null)
  const hasTimedOutRef = useRef(false)
 
  const maxDurationMs = maxDurationMinutes * 60 * 1000
 
  const start = useCallback(() => {
    if (!enabled) return
    hasTimedOutRef.current = false
    setStartTime(Date.now())
    setElapsedMs(0)
  }, [enabled])
 
  const stop = useCallback(() => {
    setStartTime(null)
    if (animationFrameRef.current) {
      cancelAnimationFrame(animationFrameRef.current)
      animationFrameRef.current = null
    }
  }, [])
 
  const reset = useCallback(() => {
    hasTimedOutRef.current = false
    setStartTime(null)
    setElapsedMs(0)
    if (animationFrameRef.current) {
      cancelAnimationFrame(animationFrameRef.current)
      animationFrameRef.current = null
    }
  }, [])
 
  useEffect(() => {
    if (!startTime || !enabled) {
      return
    }
 
    const updateTimer = () => {
      const now = Date.now()
      const elapsed = now - startTime
      setElapsedMs(elapsed)

      if (elapsed >= maxDurationMs) {
        if (!hasTimedOutRef.current) {
          hasTimedOutRef.current = true
          onTimeout()
        }
      } else {
        animationFrameRef.current = requestAnimationFrame(updateTimer)
      }
    }
 
    animationFrameRef.current = requestAnimationFrame(updateTimer)
 
    return () => {
      if (animationFrameRef.current) {
        cancelAnimationFrame(animationFrameRef.current)
      }
    }
  }, [startTime, maxDurationMs, onTimeout, enabled])
 
  const remainingMs = Math.max(0, maxDurationMs - elapsedMs)
  const remainingMinutes = Math.floor(remainingMs / 60000)
  const remainingSeconds = Math.floor((remainingMs % 60000) / 1000)
  const percentRemaining = maxDurationMs > 0 ? (remainingMs / maxDurationMs) * 100 : 100
 
  return {
    startTime,
    elapsedMs,
    remainingMs,
    remainingMinutes,
    remainingSeconds,
    percentRemaining,
    isActive: startTime !== null && !hasTimedOutRef.current,
    start,
    stop,
    reset,
  }
}