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:
ogrechkoandClaude Sonnet 5 committed 2026-08-31 16:33:30 +00:00
commit 579528edf7
197 files changed
+25022

No files matched your search

+60
View File
@@ -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>
);
}