Organize/app/(app)/layout.tsx

65 lines
2.9 KiB
TypeScript

import { redirect } from "next/navigation";
import { auth } from "@/auth";
import { prisma } from "@/lib/db";
import type { ThemeName } from "@/lib/themes";
import type { ProjectDTO } from "@/types/project";
import { SideNavProvider } from "@/components/nav/side-nav-provider";
import { SideNav } from "@/components/nav/side-nav";
import { MobileTopBar } from "@/components/nav/mobile-top-bar";
import { ProjectsProvider } from "@/components/projects/projects-context";
import { ScheduledPanelProvider } from "@/components/scheduled/scheduled-panel-provider";
import { ScheduledPanel } from "@/components/scheduled/scheduled-panel";
import { BoardViewProvider } from "@/components/board/board-view-provider";
import { HoldOnCompleteProvider } from "@/components/hold-on-complete";
export default async function AppLayout({ children }: { children: React.ReactNode }) {
const session = await auth();
// Defense in depth: proxy.ts already redirects unauthenticated requests,
// but every protected data boundary should check for itself too.
if (!session?.user) redirect("/login");
// Just the list for the sidebar/`/projects` page -- each project's own
// board (categories/groups/todos) is fetched separately by its own page.
// `theme` is stored as free text; the set of valid names is enforced
// client-side (ProjectThemeSchema) so a plain assertion is safe here.
const projects: ProjectDTO[] = (
await prisma.project.findMany({
where: { ownerId: session.user.id },
orderBy: { createdAt: "asc" },
select: { id: true, title: true, theme: true },
})
).map((p) => ({
id: p.id,
title: p.title,
theme: p.theme as ThemeName | null,
}));
return (
<SideNavProvider>
<ScheduledPanelProvider>
<ProjectsProvider initialProjects={projects}>
<BoardViewProvider>
<HoldOnCompleteProvider>
{/* Responsive shell:
- md and up: the original three-column desktop layout, with
the same min-width + horizontal-scroll fallback as before.
- below md (phones): a true stacked app layout -- top bar,
full-height board, and the Scheduled dock (rendered inside
ScheduledPanel) take the full column with no horizontal
scrolling; h-dvh tracks the mobile browser's dynamic
toolbar instead of the stale 100vh. */}
<div className="flex h-dvh flex-col md:h-screen md:min-w-[860px] md:flex-row md:overflow-x-auto">
<MobileTopBar />
<SideNav userEmail={session.user.email ?? ""} role={session.user.role} />
<main className="min-h-0 flex-1 overflow-auto">{children}</main>
<ScheduledPanel />
</div>
</HoldOnCompleteProvider>
</BoardViewProvider>
</ProjectsProvider>
</ScheduledPanelProvider>
</SideNavProvider>
);
}