Created
September 11, 2025 14:44
-
-
Save lucasmotta/9727b1e6d674d32400b5f9b86d049bc9 to your computer and use it in GitHub Desktop.
Revisions
-
lucasmotta created this gist
Sep 11, 2025 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,198 @@ import * as React from 'react'; export interface UseCountdownOptions { key: string; duration: number; autoStart?: boolean; onComplete?: () => void; } export const useCountdown = ({ key, duration, autoStart = false, onComplete, }: UseCountdownOptions) => { const [timeLeft, setTimeLeft] = React.useState<number>(0); const [completed, setCompleted] = React.useState<boolean>(false); const [running, setRunning] = React.useState<boolean>(false); const intervalRef = React.useRef<NodeJS.Timeout | null>(null); const storageKey = React.useMemo(() => `countdown_${key}`, [key]); const safeClearInterval = React.useCallback(() => { if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } }, []); const saveToStorage = React.useCallback( (endTime: number, isActive: boolean, isCompleted = false) => { if (typeof window !== 'undefined') { localStorage.setItem( storageKey, JSON.stringify({ endTime, isActive, duration, completed: isCompleted, }), ); } }, [storageKey, duration], ); const loadFromStorage = React.useCallback(() => { if (typeof window === 'undefined') { return null; } try { const stored = localStorage.getItem(storageKey); if (!stored) { return null; } const data = JSON.parse(stored); if (data.duration !== duration) { localStorage.removeItem(storageKey); return null; } return data; } catch { localStorage.removeItem(storageKey); return null; } }, [storageKey, duration]); const startCountdown = React.useCallback( (fromTime?: number) => { safeClearInterval(); const now = Date.now(); const endTime = fromTime || now + duration * 1000; const remaining = Math.max(0, endTime - now); if (remaining <= 0) { setTimeLeft(0); setCompleted(true); setRunning(false); saveToStorage(endTime, false, true); onComplete?.(); return; } setTimeLeft(Math.ceil(remaining / 1000)); setCompleted(false); setRunning(true); saveToStorage(endTime, true); intervalRef.current = setInterval(() => { const currentRemaining = Math.max(0, endTime - Date.now()); const currentSeconds = Math.ceil(currentRemaining / 1000); setTimeLeft(currentSeconds); if (currentRemaining <= 0) { setCompleted(true); setRunning(false); safeClearInterval(); saveToStorage(endTime, false, true); onComplete?.(); } }, 1000); }, [safeClearInterval, duration, onComplete, saveToStorage, storageKey], ); const start = React.useCallback(() => { startCountdown(); }, [startCountdown]); const continueTimer = React.useCallback(() => { const stored = loadFromStorage(); if (stored && stored.isActive) { startCountdown(stored.endTime); } else { startCountdown(); } }, [loadFromStorage, startCountdown]); const reset = React.useCallback( (shouldStart = false) => { safeClearInterval(); setTimeLeft(duration); setCompleted(false); setRunning(false); localStorage.removeItem(storageKey); if (shouldStart) { startCountdown(); } }, [safeClearInterval, duration, startCountdown, storageKey], ); React.useEffect(() => { const stored = loadFromStorage(); if (stored) { if (stored.completed) { setTimeLeft(0); setCompleted(true); setRunning(false); return; } if (stored.isActive) { const now = Date.now(); const remaining = Math.max(0, stored.endTime - now); if (remaining > 0) { startCountdown(stored.endTime); } else { setTimeLeft(0); setCompleted(true); setRunning(false); saveToStorage(stored.endTime, false, true); onComplete?.(); } return; } } setTimeLeft(duration); if (autoStart) { startCountdown(); } }, [ autoStart, duration, loadFromStorage, onComplete, saveToStorage, startCountdown, storageKey, ]); React.useEffect( () => () => { safeClearInterval(); }, [safeClearInterval], ); return { timeLeft, completed, running, start, continue: continueTimer, reset, }; };