Initial commit: thbd-portal, duplicated from elmi-portal
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
This commit is contained in:
commit
579528edf7
197 files changed
+25022
No files matched your search
@@ -0,0 +1,60 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user