60 lines
2.2 KiB
TypeScript
60 lines
2.2 KiB
TypeScript
import { notFound, redirect } from "next/navigation";
|
|
|
|
import { auth } from "@/auth";
|
|
import { prisma } from "@/lib/db";
|
|
import { projectAccessFilter } from "@/lib/access";
|
|
import { getBoard } from "@/lib/board";
|
|
import { getAiSettingsView } from "@/lib/ai-settings";
|
|
import { themeSwitchScript, type ThemeName } from "@/lib/themes";
|
|
import { KanbanBoard } from "@/components/board/kanban-board";
|
|
import { ProjectThemeScope } from "@/components/theme/project-theme-scope";
|
|
|
|
export default async function ProjectPage({
|
|
params,
|
|
}: {
|
|
params: Promise<{ projectId: string }>;
|
|
}) {
|
|
const session = await auth();
|
|
if (!session?.user) redirect("/login");
|
|
const { projectId } = await params;
|
|
|
|
const project = await prisma.project.findFirst({
|
|
where: { id: projectId, ...projectAccessFilter(session.user.id) },
|
|
select: { id: true, title: true, theme: true },
|
|
});
|
|
// Same response whether the project doesn't exist or just isn't this
|
|
// user's -- no need to distinguish "not found" from "not yours".
|
|
if (!project) notFound();
|
|
|
|
const [board, aiSettings] = await Promise.all([
|
|
getBoard({ projectId: project.id }),
|
|
getAiSettingsView(),
|
|
]);
|
|
const aiConfigured = !!(aiSettings.apiUrl && aiSettings.model);
|
|
|
|
// A project's assigned theme (null = "Default Theme", i.e. the user's
|
|
// global theme-menu choice) applies while this page is open and lifts on
|
|
// navigation -- ProjectThemeScope owns the client side. The inline script
|
|
// mirrors it on <html> before first paint on a hard load, so the first
|
|
// frame is already themed (and leaves the data-project-theme marker the
|
|
// ThemeProvider reads at hydration). `theme` is free text in the DB; the
|
|
// set of valid names is enforced by ProjectThemeSchema on write, so the
|
|
// assertion is safe.
|
|
const scopedTheme: ThemeName | null = project.theme as ThemeName | null;
|
|
|
|
return (
|
|
<>
|
|
{scopedTheme && (
|
|
<script dangerouslySetInnerHTML={{ __html: themeSwitchScript(scopedTheme) }} />
|
|
)}
|
|
<ProjectThemeScope projectId={project.id} />
|
|
<KanbanBoard
|
|
initialCategories={board}
|
|
projectId={project.id}
|
|
title={project.title}
|
|
aiConfigured={aiConfigured}
|
|
/>
|
|
</>
|
|
);
|
|
}
|