import * as React from "react" import { Outlet, useLocation } from "react-router-dom" import { AppSidebar, SidebarShell, SecondaryPanelProvider, SecondaryPanel, } from "@/components/sidebar" import { isExamLockPath } from "@/lib/exam-lock-shell" import { SIDEBAR_STATE_COOKIE_NAME, sidebarDefaultOpenFromCookie, } from "@/lib/sidebar-state-cookie" import { ThemeProvider } from "@/components/theme-provider" import { TooltipProvider } from "@/components/ui/tooltip" import { ProductProviderRoot } from "@/components/product-provider-root" import { ProductRouteSync, useProductDashboardHref, useProductOrganizationSettingsHref, } from "@/contexts/product-route-sync" import { ProductSwitchOverlay } from "@/components/product-switch-overlay" import { ThemeColorSync } from "@/components/theme-color-sync" import { DashboardViewProvider } from "@/contexts/dashboard-view-context" import { ChartVariantProvider } from "@/contexts/chart-variant-context" import { AskLeoProvider, AskLeoSidebar, useAskLeo, } from "@/components/ask-leo-sidebar" import { KeyMetricsAskLeoBridge } from "@/components/key-metrics-ask-leo-bridge" import { SystemBannerProvider } from "@/contexts/system-banner-context" import { SystemBannerSlot } from "@/components/system-banner-slot" import { CommandMenu } from "@/components/command-menu" import { CommandMenuProvider } from "@/contexts/command-menu-context" import { buildCommandMenuConfig, type CommandMenuConfig } from "@/lib/command-menu-config" import { COMMAND_MENU_SEARCH_DATA_GROUPS } from "@/lib/command-menu-search-data" /** * Vite-side root layout — replaces both `app/layout.tsx` (Next root) and * `app/(app)/layout.tsx` (Next signed-in app shell). * * Cookie reading: Next reads `cookies()` server-side. In Vite/SPA we read * `document.cookie` synchronously on first render — the API is identical * (default `true` if absent, otherwise the persisted state). No * hydration mismatch because there is no server render to mismatch * against. * * Provider order is preserved verbatim from the Next layout — these * contexts are tightly coupled to DS components (KeyMetrics ↔ Ask Leo, * SecondaryPanel ↔ Sidebar) and reorder would break runtime behaviour. * * The `` in the centre is where each route's element renders. */ function readCookie(name: string): string | undefined { if (typeof document === "undefined") return undefined const match = document.cookie .split("; ") .find(c => c.startsWith(`${name}=`)) return match ? decodeURIComponent(match.split("=")[1]) : undefined } function patchOrganizationSettingsHref( config: CommandMenuConfig, organizationSettingsHref: string, ): CommandMenuConfig { return { ...config, groups: config.groups.map(group => ({ ...group, items: group.items?.map(item => item.id === "nav-settings-organization" ? { ...item, href: organizationSettingsHref } : item, ), })), } } function AppShellLayout({ sidebarDefaultOpen }: { sidebarDefaultOpen: boolean }) { const { pathname } = useLocation() const { open: askLeoOpen } = useAskLeo() const examLock = isExamLockPath(pathname) return ( {!examLock ? : null} {!examLock ? : null} {examLock ? (
) : (
)}
) } function AppShell({ sidebarDefaultOpen }: { sidebarDefaultOpen: boolean }) { const dashboardHref = useProductDashboardHref() const libraryHref = dashboardHref.replace(/\/dashboard$/, "/library") const organizationSettingsHref = useProductOrganizationSettingsHref() const commandMenuConfig = React.useMemo( () => patchOrganizationSettingsHref( patchCommandMenuHrefs( buildCommandMenuConfig({ dataGroups: COMMAND_MENU_SEARCH_DATA_GROUPS }), { "nav-dashboard": dashboardHref, "nav-library": libraryHref, "nav-library-all": `${libraryHref}/all`, }, ), organizationSettingsHref, ), [dashboardHref, libraryHref, organizationSettingsHref], ) return ( <> ) } function patchCommandMenuHrefs( config: CommandMenuConfig, hrefs: Record, ): CommandMenuConfig { return { ...config, groups: config.groups.map(group => ({ ...group, items: group.items?.map(item => item.id in hrefs ? { ...item, href: hrefs[item.id] } : item, ), })), } } export function App() { const sidebarDefaultOpen = sidebarDefaultOpenFromCookie( readCookie(SIDEBAR_STATE_COOKIE_NAME), ) return (
) }