import { useSyncExternalStore, type AnchorHTMLAttributes, type MouseEvent, type ReactNode } from "react";
export type RouteMatch =
| { name: "overview" }
| { name: "help" }
| { name: "nonprofits" }
| { name: "catalog" }
| { name: "kit"; id: string }
| { name: "contribute" }
| { name: "tracks" }
| { name: "fabric" }
| { name: "about" }
| { name: "notFound"; path: string };
const STATIC_ROUTES: Record = {
"/": { name: "overview" },
"/help": { name: "help" },
"/nonprofits": { name: "nonprofits" },
"/catalog": { name: "catalog" },
"/contribute": { name: "contribute" },
"/tracks": { name: "tracks" },
"/fabric": { name: "fabric" },
"/about": { name: "about" },
};
export function matchPath(rawPath: string): RouteMatch {
const path = rawPath.replace(/\/+$/, "") || "/";
const fixed = STATIC_ROUTES[path];
if (fixed) return fixed;
const kitMatch = /^\/catalog\/([^/]+)$/.exec(path);
const rawId = kitMatch?.[1];
if (kitMatch && rawId) {
return { name: "kit", id: decodeURIComponent(rawId) };
}
return { name: "notFound", path };
}
function subscribe(onChange: () => void): () => void {
window.addEventListener("popstate", onChange);
return () => window.removeEventListener("popstate", onChange);
}
export function usePath(): string {
return useSyncExternalStore(subscribe, () => window.location.pathname);
}
export function navigate(to: string): void {
window.history.pushState(null, "", to);
window.dispatchEvent(new PopStateEvent("popstate"));
window.scrollTo({ top: 0 });
}
interface AppLinkProps extends Omit, "href"> {
to: string;
children: ReactNode;
}
export function AppLink({ to, children, onClick, ...rest }: AppLinkProps) {
function handleClick(event: MouseEvent) {
onClick?.(event);
const modified = event.metaKey || event.ctrlKey || event.shiftKey || event.altKey;
if (event.defaultPrevented || modified || event.button !== 0) return;
event.preventDefault();
navigate(to);
}
return (
{children}
);
}