"use client"; import { createContext, useContext, useMemo, type ReactNode } from "react"; import { usePathname, useSearchParams } from "next/navigation"; import { useRefRegistryVersion } from "./use-ref-registry-version"; import { useResolvedProjectRoute } from "./project-route-context"; import { projectIdFromPath, projectSegmentFromPath } from "./project-id-from-path"; export interface AppTabRouteSnapshot { pathname: string; search: string; /** The raw `/projects/[ref]` segment; `useAppTabRoute` expands it to an id. */ projectSegment: string | null; } const ActiveContext = createContext(true); const ActivationContext = createContext(0); export const useAppTabActivation = () => useContext(ActivationContext); export const useAppTabActive = () => useContext(ActiveContext); const Context = createContext(null); export function AppTabRouteProvider({ route, children, active = true, activationSequence = 0 }: { route: AppTabRouteSnapshot; children: ReactNode; active?: boolean; activationSequence?: number }) { return {children}; } function LiveRoute({ children }: { children: ReactNode }) { const pathname = usePathname(); const search = useSearchParams().toString(); const route = useMemo(() => ({ pathname, search, projectSegment: projectSegmentFromPath(pathname) }), [pathname, search]); return {children}; } /** Standalone route entries read Next; retained entries never subscribe to it. */ export function AppTabRouteBoundary({ children }: { children: ReactNode }) { const retained = useContext(Context); return retained ? children : {children}; } /** * Render a project page once its project id is known. A retained view reads * its route outside the project layout, so a short code this browser has not * learned yet (a project created elsewhere, while the project list loads) * expands to null for a moment; the page waits behind its skeleton instead of * rendering without a project, which crashed into the error screen. */ export function ProjectRouteReady({ fallback, children }: { fallback: ReactNode; children: ReactNode }) { return useAppTabRoute().projectId ? children : fallback; } /** Hidden views retain their own location while the window navigates elsewhere. */ export function useAppTabRoute() { const route = useContext(Context); if (!route) throw new Error("useAppTabRoute requires AppTabRouteBoundary"); const query = route.search; const searchParams = useMemo(() => new URLSearchParams(query), [query]); const pathname = route.pathname; // The URL carries a short code; it expands once the project list is loaded. const registryVersion = useRefRegistryVersion(); // eslint-disable-next-line react-hooks/exhaustive-deps -- The registry version is the input that changes the expansion. const expanded = useMemo(() => projectIdFromPath(pathname), [pathname, registryVersion]); // The project layout resolved the segment server-side: known from the very // first render, before the project list arrives. const resolved = useResolvedProjectRoute(route.projectSegment); const projectId = expanded ?? resolved; return { pathname, searchParams, projectId }; }