import { useEffect, useState } from 'react'; import { useLockState } from './useWalletData'; // The unlock window, as a second-by-second countdown. // // The sidecar's `secondsRemaining` is a snapshot taken when the request was served, and re-polling once a // second to animate a clock would be absurd. So the poll re-syncs the truth every ten seconds and this // hook interpolates between syncs from React Query's `dataUpdatedAt`, which is the wall time the snapshot // arrived. Drift is bounded by the poll interval and always errs towards showing LESS time than remains — // a countdown that overstates the window is the one that surprises you mid-signature. // // When it reaches zero the UI flips to locked immediately rather than waiting for the next poll to agree. // Nothing else is invalidated when that happens: every read in this app works fine against a locked // wallet, and blowing the caches away would make an expiring timer look like a connection failure. export type LockCountdown = { /** Null while the first lock-state request is in flight, or when no wallet is selected. */ hasSeed: boolean | null; unlocked: boolean; secondsRemaining: number; isLoading: boolean; }; export function useLockCountdown(walletId: number | null): LockCountdown { const { lock, dataUpdatedAt, isLoading } = useLockState(walletId); const [now, setNow] = useState(() => Date.now()); const ticking = lock?.unlocked === true; useEffect(() => { if (!ticking) return; const timer = setInterval(() => setNow(Date.now()), 1_000); return () => clearInterval(timer); }, [ticking]); if (!lock) return { hasSeed: null, unlocked: false, secondsRemaining: 0, isLoading }; const elapsed = dataUpdatedAt ? Math.floor((now - dataUpdatedAt) / 1000) : 0; const secondsRemaining = Math.max(0, lock.secondsRemaining - Math.max(0, elapsed)); return { hasSeed: lock.hasSeed, unlocked: lock.unlocked && secondsRemaining > 0, secondsRemaining, isLoading, }; }