"use client"; import { usePathname } from "next/navigation"; import { useCallback, useEffect, useRef, useState } from "react"; const TOP_BAR_HEIGHT = 3; const MAX_PERCENT = 90; const COMPLETE_PERCENT = 100; const TICK_MS = 50; const INCREMENT = 2; const COMPLETE_DURATION = 200; export function TopProgressBar() { const pathname = usePathname(); const [visible, setVisible] = useState(false); const [percent, setPercent] = useState(0); const timerRef = useRef | null>(null); const completeTimeoutRef = useRef | null>(null); const prevPathnameRef = useRef(pathname); const clearTimers = useCallback(() => { if (timerRef.current) { clearInterval(timerRef.current); timerRef.current = null; } if (completeTimeoutRef.current) { clearTimeout(completeTimeoutRef.current); completeTimeoutRef.current = null; } }, []); const startProgress = useCallback(() => { clearTimers(); setVisible(true); setPercent(0); timerRef.current = setInterval(() => { setPercent((p) => { if (p >= MAX_PERCENT) { if (timerRef.current) { clearInterval(timerRef.current); timerRef.current = null; } return MAX_PERCENT; } return Math.min(p + INCREMENT, MAX_PERCENT); }); }, TICK_MS); }, [clearTimers]); const completeProgress = useCallback(() => { clearTimers(); setPercent(COMPLETE_PERCENT); completeTimeoutRef.current = setTimeout(() => { setVisible(false); setPercent(0); completeTimeoutRef.current = null; }, COMPLETE_DURATION); }, [clearTimers]); // Start bar when user clicks an internal link useEffect(() => { const handleClick = (e: MouseEvent) => { const target = (e.target as Element).closest("a"); if (!target?.href) return; try { const url = new URL(target.href); if (url.origin === window.location.origin && url.pathname !== pathname) { startProgress(); } } catch { // ignore invalid URLs } }; document.addEventListener("click", handleClick, true); return () => document.removeEventListener("click", handleClick, true); }, [pathname, startProgress]); // Complete bar when pathname changes (route loaded) useEffect(() => { if (prevPathnameRef.current !== pathname) { prevPathnameRef.current = pathname; if (visible) completeProgress(); } }, [pathname, visible, completeProgress]); useEffect(() => { return clearTimers; }, [clearTimers]); if (!visible) return null; return (
); }