109 lines
3.4 KiB
TypeScript
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>
|
|
)
|
|
}
|