TorbatYar/frontend/components/RouteProgress.tsx
Mortezakoohjani 86b37c1de9 Speed up accounting sidebar navigation with shared session cache and prefetch.
Stop refetching /me on every page mount, warm shared accounting queries, prefetch nav routes, and keep the shell visible during soft SPA transitions.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-26 18:31:24 +03:30

61 lines
1.8 KiB
TypeScript

"use client";
import { Suspense, useEffect, useState } from "react";
import { usePathname, useSearchParams } from "next/navigation";
function RouteProgressInner() {
const pathname = usePathname();
const searchParams = useSearchParams();
const [active, setActive] = useState(false);
useEffect(() => {
const onClick = (e: MouseEvent) => {
if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
return;
}
const el = (e.target as HTMLElement | null)?.closest?.("a[href]");
if (!el) return;
const href = el.getAttribute("href");
if (!href || href.startsWith("http") || href.startsWith("mailto:") || href.startsWith("#")) {
return;
}
if (!href.startsWith("/")) return;
const nextPath = href.split("?")[0];
if (nextPath === window.location.pathname && href === `${window.location.pathname}${window.location.search}`) {
return;
}
setActive(true);
};
document.addEventListener("click", onClick, true);
return () => document.removeEventListener("click", onClick, true);
}, []);
useEffect(() => {
if (!active) return;
const t = window.setTimeout(() => setActive(false), 350);
return () => window.clearTimeout(t);
}, [pathname, searchParams, active]);
if (!active) return null;
return (
<div className="pointer-events-none fixed inset-x-0 top-0 z-[200] h-[3px] overflow-hidden" aria-hidden>
<div
className="h-full w-full bg-primary"
style={{
animation: "ty-route-progress 0.85s ease-in-out infinite",
transformOrigin: "right center",
}}
/>
</div>
);
}
export function RouteProgress() {
return (
<Suspense fallback={null}>
<RouteProgressInner />
</Suspense>
);
}