Rebranded for Thunderbird Portal (THBD): numbering prefix EL->TH, all "ELMI"/"ЭЛМИ" UI strings genericized, ELMI's real contract templates and company legal/bank details removed (not applicable to this company), fresh VAPID keypair and encryption key, fresh single-migration schema history. Same architecture as elmi-portal: Web Push notifications, unread-chat app badge, cookie-session auth. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gh2UXUQUVBWroWEnn1FLFG
61 lines
2.9 KiB
TypeScript
61 lines
2.9 KiB
TypeScript
import { redirect } from "next/navigation";
|
|
import Link from "next/link";
|
|
import { Truck } from "lucide-react";
|
|
import { getCurrentSession } from "@/lib/auth/session";
|
|
import { LogoutButton } from "./logout-button";
|
|
import { NavShell } from "./nav-shell";
|
|
import { ThemeToggle } from "./theme-toggle";
|
|
import { NotificationToggle } from "./notification-toggle";
|
|
import { EventToasts } from "./event-toasts";
|
|
import { PresenceHeartbeat } from "./presence-heartbeat";
|
|
import { Avatar } from "./avatar";
|
|
|
|
export default async function AdminLayout({ children }: { children: React.ReactNode }) {
|
|
const session = await getCurrentSession();
|
|
if (!session) {
|
|
redirect("/login");
|
|
}
|
|
|
|
const role = session.user.role as "admin" | "manager" | "accountant";
|
|
|
|
return (
|
|
// Fixed to the real visible viewport height (ViewportHeightFix keeps
|
|
// --app-vh in sync with window.visualViewport, which — unlike 100vh —
|
|
// actually shrinks under the on-screen keyboard) so header/nav stay
|
|
// pinned and only <main> scrolls, the standard "app shell" layout
|
|
// instead of the whole page scrolling under a header that reflows.
|
|
<div className="flex flex-col overflow-hidden bg-bg" style={{ height: "var(--app-vh)" }}>
|
|
<header className="shrink-0 border-b border-border bg-surface pt-[env(safe-area-inset-top)]">
|
|
<div className="mx-auto flex max-w-[1800px] items-center gap-3 px-4 py-2.5 sm:gap-6 sm:px-6 sm:py-3">
|
|
<Link href="/inquiries" className="flex shrink-0 items-center gap-2 font-display text-[15px] font-bold tracking-tight">
|
|
<div className="flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-accent text-white">
|
|
<Truck size={15} strokeWidth={2.5} />
|
|
</div>
|
|
<span className="hidden sm:inline">Thunderbird Portal</span>
|
|
</Link>
|
|
<div className="flex flex-1 justify-center overflow-x-auto">
|
|
<NavShell role={role} />
|
|
</div>
|
|
<div className="ml-auto flex shrink-0 items-center gap-2 sm:ml-0 sm:gap-3">
|
|
<NotificationToggle />
|
|
<ThemeToggle />
|
|
<Link href="/profile" className="flex items-center gap-2 rounded-md px-1.5 py-1 hover:bg-surface-hover">
|
|
<Avatar userId={session.user.id} name={session.user.name} hasAvatar={Boolean(session.user.avatarPath)} size={26} />
|
|
<span className="hidden text-sm text-text-muted sm:inline">{session.user.name}</span>
|
|
</Link>
|
|
<LogoutButton />
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
{/* pb: clears the fixed mobile bottom-tab bar (~52px + safe-area) so it never covers the last bit of scrolled content */}
|
|
<main className="app-shell-main mx-auto w-full max-w-[1800px] flex-1 overflow-y-auto px-4 py-6 pb-[calc(4.25rem+env(safe-area-inset-bottom))] sm:px-6 lg:pb-6">
|
|
{children}
|
|
</main>
|
|
|
|
<EventToasts currentUserId={session.user.id} />
|
|
<PresenceHeartbeat />
|
|
</div>
|
|
);
|
|
}
|