feat(web): implement full sidebar and header components (MS15-FE-003, MS15-FE-004)
All checks were successful
ci/woodpecker/push/web Pipeline was successful

Sidebar: collapsible with icon-only mode, 4 nav groups (Overview, Workspace,
Operations, System), SVG icons matching reference, badges, user card footer
with avatar/initials, SidebarContext for collapse state.

Header: search bar, system status indicator, terminal toggle, notification
bell with badge, user avatar dropdown with Profile/Settings/Sign Out,
breadcrumb navigation. Removes standalone LogoutButton.

Layout updated to wrap with SidebarProvider, dynamic --sidebar-w on collapse.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-22 14:48:46 -06:00
parent 709348f07c
commit 04f99183f9
4 changed files with 1273 additions and 175 deletions

View File

@@ -6,34 +6,39 @@ 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";
export default function AuthenticatedLayout({
children,
}: {
// ---------------------------------------------------------------------------
// 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;
}): 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;
}
function AppShell({ children }: AppShellProps): React.JSX.Element {
const { collapsed } = useSidebar();
return (
<div className="app-shell">
<div
className="app-shell"
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 />
@@ -62,3 +67,36 @@ export default function AuthenticatedLayout({
</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>
);
}