import { useEffect, useState } from 'react'; import { useHttpService } from '../../services'; import { readLifecycle } from '../SandboxMode/sandboxLifecycle'; const MINUTE = 60_000; const HOUR = 60 * MINUTE; const DAY = 24 * HOUR; const plural = (n: number, unit: string) => `${n} ${unit}${n === 1 ? '' : 's'}`; /** "Resets in 2 days, 4 hours", or nothing once the instant has passed. */ export function resetCountdownLabel(scheduledAt: string, now: number): string | null { const left = Date.parse(scheduledAt) - now; if (!Number.isFinite(left) || left <= 0) return null; const days = Math.floor(left / DAY); const hours = Math.floor((left % DAY) / HOUR); if (days > 0) return `Resets in ${plural(days, 'day')}${hours > 0 ? `, ${plural(hours, 'hour')}` : ''}`; if (hours > 0) return `Resets in ${plural(hours, 'hour')}`; return 'Resets in less than an hour'; } /** * The countdown to the sandbox's next scheduled recreation, only when the server has one recorded. Without * one, or if the read fails, there is nothing to count down to and nothing is shown. */ export function useSandboxResetCountdown(enabled: boolean): string | null { const { get } = useHttpService(); const [scheduledAt, setScheduledAt] = useState(null); const [now, setNow] = useState(() => Date.now()); useEffect(() => { if (!enabled) return; let live = true; readLifecycle((url) => get(url)) .then((lifecycle) => { if (live) setScheduledAt(lifecycle.recreation?.scheduledAt ?? null); }) .catch(() => { if (live) setScheduledAt(null); }); return () => { live = false; }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [enabled]); useEffect(() => { if (!scheduledAt) return; const timer = setInterval(() => setNow(Date.now()), MINUTE); return () => clearInterval(timer); }, [scheduledAt]); return scheduledAt ? resetCountdownLabel(scheduledAt, now) : null; }