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 (
)
}