// The dashboard shell — and the AUTH GATE. This nested layout wraps every // page under `/dashboard`, and its `loader` runs ONCE for all of them, // server-side, BEFORE the page renders. That makes it the right seam to // validate the session + preload shared data in one place. // // Throwing RedirectError short-circuits the whole subtree: on SSR it emits a // 303 + Location; on a client navigation it `navigate(.., { replace:true })` // so Back doesn't bounce onto the redirecting page. The pages under here are // `renderMode: 'ssr'` precisely so this runs per request — a `static` page // can't redirect per-visitor, and the build rejects a RedirectError there. import type { ReactNode } from 'react' import { RedirectError, useLoaderData, useLocation, type LoaderFn } from '@voltro/web' import { T, useLocale, useT } from '@voltro/i18n' import { LocaleSwitcher } from '@voltro/ui-shadcn' import { APP_NAME } from '../../config' import { isSignedIn } from '../../lib/auth' const LOCALES = [ { code: 'en', label: 'English' }, { code: 'de', label: 'Deutsch' }, ] interface DashboardData { readonly user: { readonly name: string; readonly plan: string } } export const loader: LoaderFn = async ({ headers }) => { if (!isSignedIn(headers)) { // No session → bounce to login, remembering where they were headed. throw new RedirectError('/login?from=/dashboard') } // Shared data, loaded once for every page beneath this layout and read // via useLoaderData() right here in the layout. return { user: { name: 'Demo User', plan: 'Pro' } } } export default function DashboardLayout({ children }: { readonly children: ReactNode }): ReactNode { const { user } = useLoaderData() const pathname = useLocation() const locale = useLocale() const langLabel = useT('lang.label') const navItem = (href: string, label: ReactNode): ReactNode => ( {label} ) return (
{user.name}
{children}
) }