Four low-risk adds before the Zod 4 / drizzle-zod headliner: - @total-typescript/ts-reset: tightens TS stdlib types globally (JSON.parse → unknown, fetch().json() → unknown, .filter(Boolean) narrows, Set literals respect typed Set targets). Caught 179 latent type errors; fixed all production sites (8 files) and added `any` cast escape hatch in test files (ESLint exemption scoped to tests/). - web-vitals + /api/v1/internal/vitals endpoint + WebVitalsReporter client component: establishes Core Web Vitals baseline (LCP/INP/CLS/ FCP/TTFB) via navigator.sendBeacon. Required before optimisation work. - @hookform/devtools + FormDevtool wrapper: dev-only RHF state inspector, lazy-loaded via next/dynamic so the chunk is excluded from prod bundles entirely. - @tanstack/query-broadcast-client-experimental: cross-tab cache sync via BroadcastChannel — wired in query-provider.tsx, 1-liner. Audit doc updated with sections 35 + 36 (PDF stack overhaul + comprehensive second-pass package sweep) covering ~20 package adoption candidates and 4-5 deprecation candidates. Verified: tsc clean, vitest 1293/1293 pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
78 lines
3.1 KiB
TypeScript
78 lines
3.1 KiB
TypeScript
import { redirect } from 'next/navigation';
|
|
import { headers } from 'next/headers';
|
|
import { eq } from 'drizzle-orm';
|
|
|
|
import { auth } from '@/lib/auth';
|
|
import { db } from '@/lib/db';
|
|
import { ports as portsTable } from '@/lib/db/schema/ports';
|
|
import { userPortRoles, userProfiles } from '@/lib/db/schema/users';
|
|
import { QueryProvider } from '@/providers/query-provider';
|
|
import { SocketProvider } from '@/providers/socket-provider';
|
|
import { PortProvider } from '@/providers/port-provider';
|
|
import { PermissionsProvider } from '@/providers/permissions-provider';
|
|
import { Sidebar } from '@/components/layout/sidebar';
|
|
import { Topbar } from '@/components/layout/topbar';
|
|
import { MobileLayout } from '@/components/layout/mobile/mobile-layout';
|
|
import { RealtimeToasts } from '@/components/shared/realtime-toasts';
|
|
import { WebVitalsReporter } from '@/components/shared/web-vitals-reporter';
|
|
|
|
export default async function DashboardLayout({ children }: { children: React.ReactNode }) {
|
|
const session = await auth.api.getSession({ headers: await headers() });
|
|
if (!session?.user) redirect('/login');
|
|
|
|
// Super admins have implicit access to every port; everyone else only sees
|
|
// ports they have an explicit user_port_roles row for.
|
|
const profile = await db.query.userProfiles.findFirst({
|
|
where: eq(userProfiles.userId, session.user.id),
|
|
});
|
|
|
|
const portRoles = await db.query.userPortRoles.findMany({
|
|
where: eq(userPortRoles.userId, session.user.id),
|
|
with: { port: true, role: true },
|
|
});
|
|
|
|
const ports = profile?.isSuperAdmin
|
|
? await db.query.ports.findMany({ orderBy: portsTable.name })
|
|
: portRoles.map((pr) => pr.port);
|
|
|
|
return (
|
|
<QueryProvider>
|
|
<PortProvider ports={ports} defaultPortId={ports[0]?.id ?? null}>
|
|
<PermissionsProvider>
|
|
<SocketProvider>
|
|
<RealtimeToasts />
|
|
<WebVitalsReporter />
|
|
{/* Desktop shell - hidden by CSS on mobile */}
|
|
<div data-shell="desktop" className="flex h-screen overflow-hidden bg-background">
|
|
<Sidebar
|
|
portRoles={portRoles}
|
|
isSuperAdmin={profile?.isSuperAdmin ?? false}
|
|
user={{
|
|
name: profile?.displayName ?? session.user.name ?? session.user.email,
|
|
email: session.user.email,
|
|
}}
|
|
ports={ports}
|
|
/>
|
|
<div className="flex-1 flex flex-col overflow-hidden min-w-0">
|
|
<Topbar
|
|
ports={ports}
|
|
user={{
|
|
name: profile?.displayName ?? session.user.name ?? session.user.email,
|
|
email: session.user.email,
|
|
}}
|
|
/>
|
|
<main className="flex-1 overflow-y-auto bg-background px-6 pt-3 pb-6">
|
|
{children}
|
|
</main>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Mobile shell - hidden by CSS on desktop */}
|
|
<MobileLayout>{children}</MobileLayout>
|
|
</SocketProvider>
|
|
</PermissionsProvider>
|
|
</PortProvider>
|
|
</QueryProvider>
|
|
);
|
|
}
|