Yachts list page rendered each row's Current Owner via OwnerLink, which
fired its own /api/v1/clients/{id} or /companies/{id} fetch — N+1 round-
trips per page load (12+ for the harbor-royale fixture). Worse, until
those fetches resolved each cell showed "Client c68da7..." style raw IDs.
Fix: listYachts now resolves the polymorphic currentOwnerName in two
batched in-array queries after the page query (mirrors the listClients
yachtCount/companyCount pattern), and OwnerLink accepts an optional
preloadedName prop that suppresses the per-row fetch when supplied.
Topbar: show real user name + avatar initial from session/profile, and
expand the My-Account dropdown header to include the user's email.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
66 lines
2.5 KiB
TypeScript
66 lines
2.5 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';
|
|
|
|
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>
|
|
<div 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,
|
|
}}
|
|
/>
|
|
<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 p-6">{children}</main>
|
|
</div>
|
|
</div>
|
|
</SocketProvider>
|
|
</PermissionsProvider>
|
|
</PortProvider>
|
|
</QueryProvider>
|
|
);
|
|
}
|