Files
DevRunbook-Public/apps/web/src/app/_authenticated/authenticated-app-layout.tsx
T
DevRunbook release export cfd2804e27
Managed validation / full (push) Successful in 3m18s
Publish DevRunbook source
2026-09-03 04:09:17 +02:00

109 lines
3.4 KiB
TypeScript

import type { ActorContext } from '@devrunbook/application'
import { cookies, headers } from 'next/headers'
import { redirect } from 'next/navigation'
import type { ReactNode } from 'react'
import type { CommandPaletteItem } from '../../components/command-palette/command-palette'
import { AppShell } from '../../components/shell/app-shell'
import { isThemePreference } from '../../components/theme/theme-model'
import {
detectLocale,
isPresentationMode,
} from '../../components/presentation/presentation-model'
import { getAuth } from '../../auth/auth'
import {
AuthenticatedWorkspaceContextError,
resolveAuthenticatedWorkspaceContext,
} from '../../server/authenticated-workspace-context'
import { DrizzleWorkspaceSelectionLookup } from '@devrunbook/db'
import {
authenticatedCommands,
buildAuthenticatedShellPresentation,
buildLoginHref,
commandsForWorkspaceRole,
localizeAuthenticatedCommands,
} from './authenticated-app-presentation'
export interface AuthenticatedAppLayoutProps {
readonly children: ReactNode
readonly requestPath: string
readonly loginReturnTo: string
readonly activeNavigationId: string
readonly commands?: readonly CommandPaletteItem[]
readonly loadCommands?: (
actor: ActorContext,
) => Promise<readonly CommandPaletteItem[]>
}
export async function AuthenticatedAppLayout({
children,
requestPath,
loginReturnTo,
activeNavigationId,
commands = authenticatedCommands,
loadCommands,
}: AuthenticatedAppLayoutProps) {
let actor: ActorContext
const requestHeaders = await headers()
try {
actor = await resolveAuthenticatedWorkspaceContext(
new Request(new URL(requestPath, 'http://devrunbook.local'), {
headers: requestHeaders,
}),
)
} catch (error) {
if (
error instanceof AuthenticatedWorkspaceContextError &&
error.code === 'authentication_required'
) {
redirect(buildLoginHref(loginReturnTo))
}
throw error
}
const cookieStore = await cookies()
const themeCookie = cookieStore.get('devrunbook_theme')?.value
const initialTheme = isThemePreference(themeCookie) ? themeCookie : 'system'
const modeCookie = cookieStore.get('devrunbook_mode')?.value
const mode = isPresentationMode(modeCookie) ? modeCookie : 'simple'
const locale = detectLocale(
cookieStore.get('devrunbook_locale')?.value,
requestHeaders.get('accept-language'),
)
const session = await getAuth().api.getSession({ headers: requestHeaders })
const workspaces =
await new DrizzleWorkspaceSelectionLookup().listAuthorizedWorkspaces(
actor.userId,
)
const presentation = buildAuthenticatedShellPresentation({
...actor,
mode,
locale,
workspaces: workspaces.map(({ id, name, type }) => ({ id, name, type })),
...(session?.user.name ? { displayName: session.user.name } : {}),
...(session?.user.email ? { email: session.user.email } : {}),
})
const resolvedCommands = localizeAuthenticatedCommands(
commandsForWorkspaceRole(
loadCommands ? await loadCommands(actor) : commands,
actor.workspaceRole,
),
locale,
)
return (
<AppShell
activeNavigationId={activeNavigationId}
navigation={presentation.navigation}
workspaces={presentation.workspaces}
activeWorkspaceId={presentation.activeWorkspaceId}
actor={presentation.actor}
initialTheme={initialTheme}
locale={locale}
commands={resolvedCommands}
>
{children}
</AppShell>
)
}