feat(site-scope): load the allowed site list in the dashboard layout
This commit is contained in:
parent
c88ebb1715
commit
63eb8ed931
2 changed files with 54 additions and 5 deletions
|
|
@ -1,13 +1,25 @@
|
|||
import { Sidebar } from "@/components/layout/sidebar";
|
||||
import { AuthGate } from "@/components/auth-gate";
|
||||
import { SiteScopeProvider } from "@/components/layout/site-scope-provider";
|
||||
import { getSiteScope, scopeFallbackValue } from "@/lib/site-scope";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function DashboardLayout({ children }: { children: React.ReactNode }) {
|
||||
// No searchParams here — layouts cannot read them and do not re-render
|
||||
// on navigation. This call supplies `allowed` (stable for the session)
|
||||
// and the cookie-resolved default; the switcher reads the live value
|
||||
// from the URL itself.
|
||||
const scope = await getSiteScope();
|
||||
|
||||
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<AuthGate>
|
||||
<div className="flex min-h-screen w-full bg-background">
|
||||
<Sidebar />
|
||||
<div className="min-w-0 flex-1">{children}</div>
|
||||
</div>
|
||||
<SiteScopeProvider allowed={scope.allowed} fallbackValue={scopeFallbackValue(scope)}>
|
||||
<div className="flex min-h-screen w-full bg-background">
|
||||
<Sidebar />
|
||||
<div className="min-w-0 flex-1">{children}</div>
|
||||
</div>
|
||||
</SiteScopeProvider>
|
||||
</AuthGate>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
37
src/components/layout/site-scope-provider.tsx
Normal file
37
src/components/layout/site-scope-provider.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import type { ScopeSite } from "@/lib/site-scope";
|
||||
|
||||
interface SiteScopeContextValue {
|
||||
/** Every site the user may see. Loaded once by the dashboard layout. */
|
||||
allowed: ScopeSite[];
|
||||
/**
|
||||
* What to show when the URL carries no ?site= — the cookie-resolved
|
||||
* default. Deliberately NOT the active scope: active always comes
|
||||
* from useSearchParams() so the control's state is the URL.
|
||||
*/
|
||||
fallbackValue: string;
|
||||
}
|
||||
|
||||
const SiteScopeContext = React.createContext<SiteScopeContextValue | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
export function SiteScopeProvider({
|
||||
allowed,
|
||||
fallbackValue,
|
||||
children,
|
||||
}: SiteScopeContextValue & { children: React.ReactNode }) {
|
||||
const value = React.useMemo(
|
||||
() => ({ allowed, fallbackValue }),
|
||||
[allowed, fallbackValue]
|
||||
);
|
||||
return <SiteScopeContext.Provider value={value}>{children}</SiteScopeContext.Provider>;
|
||||
}
|
||||
|
||||
export function useSiteScope() {
|
||||
const ctx = React.useContext(SiteScopeContext);
|
||||
if (!ctx) throw new Error("useSiteScope must be used inside SiteScopeProvider");
|
||||
return ctx;
|
||||
}
|
||||
Loading…
Reference in a new issue