All checks were successful
ci/woodpecker/push/web Pipeline was successful
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
108 lines
3.4 KiB
TypeScript
108 lines
3.4 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect } from "react";
|
||
import { useRouter } from "next/navigation";
|
||
import { useAuth } from "@/lib/auth/auth-context";
|
||
import { IS_MOCK_AUTH_MODE } from "@/lib/config";
|
||
import { AppHeader } from "@/components/layout/AppHeader";
|
||
import { AppSidebar } from "@/components/layout/AppSidebar";
|
||
import { SidebarProvider, useSidebar } from "@/components/layout/SidebarContext";
|
||
import { ChatOverlay } from "@/components/chat";
|
||
import { MosaicSpinner } from "@/components/ui/MosaicSpinner";
|
||
import type { ReactNode } from "react";
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Constants
|
||
// ---------------------------------------------------------------------------
|
||
|
||
const SIDEBAR_EXPANDED_WIDTH = "240px";
|
||
const SIDEBAR_COLLAPSED_WIDTH = "60px";
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Inner shell — must be a child of SidebarProvider to use useSidebar
|
||
// ---------------------------------------------------------------------------
|
||
|
||
interface AppShellProps {
|
||
children: ReactNode;
|
||
}
|
||
|
||
function AppShell({ children }: AppShellProps): React.JSX.Element {
|
||
const { collapsed, isMobile } = useSidebar();
|
||
|
||
// On tablet (md–lg), hide sidebar from the grid when the sidebar is collapsed.
|
||
// On mobile, the sidebar is fixed-position so the grid is always single-column.
|
||
const sidebarHidden = !isMobile && collapsed;
|
||
|
||
return (
|
||
<div
|
||
className="app-shell"
|
||
data-sidebar-hidden={sidebarHidden ? "true" : undefined}
|
||
style={
|
||
{
|
||
"--sidebar-w": collapsed ? SIDEBAR_COLLAPSED_WIDTH : SIDEBAR_EXPANDED_WIDTH,
|
||
transition: "grid-template-columns 0.2s var(--ease, ease)",
|
||
} as React.CSSProperties
|
||
}
|
||
>
|
||
{/* Full-width header — grid-column: 1 / -1 via .app-header CSS class */}
|
||
<AppHeader />
|
||
|
||
{/* Sidebar — left column, row 2, via .app-sidebar CSS class */}
|
||
<AppSidebar />
|
||
|
||
{/* Main content — right column, row 2, via .app-main CSS class */}
|
||
<main className="app-main" id="main-content">
|
||
{IS_MOCK_AUTH_MODE && (
|
||
<div
|
||
className="border-b px-4 py-2 text-xs font-medium flex-shrink-0"
|
||
style={{
|
||
borderColor: "var(--ms-amber-500)",
|
||
background: "rgba(245, 158, 11, 0.08)",
|
||
color: "var(--ms-amber-400)",
|
||
}}
|
||
data-testid="mock-auth-banner"
|
||
>
|
||
Mock Auth Mode (Local Only): Real authentication is bypassed for frontend development.
|
||
</div>
|
||
)}
|
||
<div className="flex-1 overflow-y-auto p-5">{children}</div>
|
||
</main>
|
||
|
||
{!IS_MOCK_AUTH_MODE && <ChatOverlay />}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Authenticated layout — handles auth guard + provides sidebar context
|
||
// ---------------------------------------------------------------------------
|
||
|
||
export default function AuthenticatedLayout({
|
||
children,
|
||
}: {
|
||
children: ReactNode;
|
||
}): React.JSX.Element | null {
|
||
const router = useRouter();
|
||
const { isAuthenticated, isLoading } = useAuth();
|
||
|
||
useEffect(() => {
|
||
if (!isLoading && !isAuthenticated) {
|
||
router.push("/login");
|
||
}
|
||
}, [isAuthenticated, isLoading, router]);
|
||
|
||
if (isLoading) {
|
||
return <MosaicSpinner size={48} fullPage />;
|
||
}
|
||
|
||
if (!isAuthenticated) {
|
||
return null;
|
||
}
|
||
|
||
return (
|
||
<SidebarProvider>
|
||
<AppShell>{children}</AppShell>
|
||
</SidebarProvider>
|
||
);
|
||
}
|