"use client"; import { useEffect, useState, useTransition } from "react"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { MenuIcon, LogOutIcon, Loader2Icon } from "lucide-react"; import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; import { logoutAction } from "@/actions/auth"; import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger, } from "@/components/ui/sheet"; import { NAV_ITEMS, navItemsForRole, pickActiveNavKey, type NavItem, type NavRole, } from "@/components/nav-config"; // --------------------------------------------------------------------------- // Mobile header (sm:hidden) // // Single-row layout: // ┌──┐ ┌────┐ // │cm│ Page title │menu│ // └──┘ └────┘ // // The brand mark on the left links home. The page title (derived from // the current nav route) gives the user a "you are here" cue without // waiting for the page content to render. The menu button on the right // opens a Sheet with the full nav list and the theme toggle. // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- // Sign-out button used by both the desktop sidebar footer and the mobile // drawer footer. Server-action under the hood: clears the session // cookie and redirects to /login. Disabled while in flight so a // double-click doesn't fire two redirects. // --------------------------------------------------------------------------- function SignOutButton({ username }: { username: string | null }) { const [pending, start] = useTransition(); return (
{username && (

Signed in as {username}

)}
); } function MobileHeader({ items, username, }: { items: NavItem[]; username: string | null; }) { const pathname = usePathname(); const activeKey = pickActiveNavKey(items, pathname); const [open, setOpen] = useState(false); // Close the drawer when the route changes (i.e. the user picked a nav // item). Without this, navigating leaves the sheet open over the new // page until the user dismisses it manually. useEffect(() => { setOpen(false); }, [pathname]); // Use the full list (not the role-filtered one) for the title lookup // so the page title still shows up correctly when a 'user' role hits // a route they wouldn't normally see in the nav (e.g. arrives via a // direct link), even though they can't navigate there from the menu. const currentItem = NAV_ITEMS.find(({ href }) => href === "/" ? pathname === "/" : pathname.startsWith(href), ); const title = currentItem?.label ?? "WhatsApp Bot"; return (
cm {title} cm WhatsApp Bot Primary navigation menu
); } // --------------------------------------------------------------------------- // Sidebar (desktop only — hidden below sm) // --------------------------------------------------------------------------- function Sidebar({ items, username, }: { items: NavItem[]; username: string | null; }) { const pathname = usePathname(); const activeKey = pickActiveNavKey(items, pathname); return ( ); } // --------------------------------------------------------------------------- // Bare header for unauthenticated routes (/login). No sidebar, no mobile // menu, no nav — just the centered brand mark + name. The user explicitly // asked for nothing else here so the sign-in screen feels like a separate // surface from the authenticated app. // --------------------------------------------------------------------------- function BareHeader() { return (
cm WhatsApp Bot
); } // --------------------------------------------------------------------------- // AppShell — the outer container // --------------------------------------------------------------------------- interface AppShellProps { children: React.ReactNode; /** Role of the signed-in user, or null when unauthenticated. */ role: NavRole | null; /** Username of the signed-in user, surfaced in the footer + sign-out hint. */ username: string | null; } export function AppShell({ children, role, username }: AppShellProps) { const pathname = usePathname(); const isAuthRoute = pathname === "/login"; if (isAuthRoute) { return ( <>
{children}
); } // Treat unauthenticated render of a protected route (shouldn't happen // because middleware redirects, but defense-in-depth) as 'user': hides // the admin-only entries. const items = navItemsForRole(role ?? "user"); return ( <> {/* Desktop sidebar */} {/* Mobile header (single row: brand · title · menu) */} {/* Main content Mobile: push down for the h-14 header (56px) plus a small gap so page titles don't kiss the bottom edge of the nav. Desktop: push right for the sidebar (sm:pl-56), no top offset. */}
{children}
); }