"use client"; import Link from "next/link"; import { ChevronLeft, ChevronRight, LogOut, NotebookPen } from "lucide-react"; import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"; import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { useSideNav } from "@/components/nav/side-nav-provider"; import { SideNavItem } from "@/components/nav/side-nav-item"; import { ProjectsNavSection } from "@/components/nav/projects-nav-section"; import { NAV_ITEMS, ADMIN_NAV_ITEM } from "@/components/nav/nav-items"; import { ThemeToggle } from "@/components/theme/theme-toggle"; import { logout } from "@/lib/actions/auth"; import { Role } from "@/lib/generated/prisma/enums"; /** * The signed-in user's circle at the bottom of the nav: their photo when * they've set one, otherwise the letter fallback -- and a link to the * Profile page either way. */ function UserAvatar({ userName, userEmail, avatar, sizeClass, }: { userName: string | null; userEmail: string; avatar: string | null; sizeClass: string; }) { if (avatar) { return ( // eslint-disable-next-line @next/next/no-img-element -- data-URL avatars, no image-optimization pipeline in this self-hosted app {userName ); } const letter = (userName?.trim()[0] ?? userEmail[0] ?? "?").slice(0, 1); return ( {letter} ); } /** * The actual nav content, shared by the desktop aside and the mobile * hamburger drawer so the two never drift apart. `collapsed` is always * false in the drawer (a 288px-wide sheet has no room for icon-only mode). */ function SideNavContent({ collapsed, userEmail, userName, avatar, role, themeMenu, }: { collapsed: boolean; userEmail: string; userName: string | null; avatar: string | null; role: Role; // Where the theme picker menu opens from its trigger -- right of the // trigger in the desktop sidebar, below it in the mobile drawer (a // right-opening menu would run off the phone's right edge there). themeMenu: { side: "right" | "bottom"; align: "start" | "end" }; }) { const adminItem = role === Role.ADMIN ? [ADMIN_NAV_ITEM] : []; // The user's display label: their name when set, otherwise the email // (same as before profiles existed). const userLabel = userName || userEmail; return ( <>
{!collapsed && ( Organize )}
{collapsed ? (
} /> Log out ({userEmail})
) : (
)}
{collapsed ? ( } > {userLabel} ) : ( {userLabel} )}
); } /** * Mobile (< md) hamburger drawer: the full side nav in a left sheet. * Tapping any link closes it; the backdrop and Escape close it too. */ function MobileNavDrawer({ userEmail, userName, avatar, role, }: { userEmail: string; userName: string | null; avatar: string | null; role: Role; }) { const { mobileOpen, setMobileOpen } = useSideNav(); return ( { // Close on navigation -- links (nav items, projects) are the only // things that should dismiss it, not e.g. a theme switch. if ((e.target as HTMLElement).closest("a")) setMobileOpen(false); }} className={cn( "fixed inset-y-0 left-0 z-50 flex w-72 max-w-[85vw] flex-col overflow-y-auto bg-sidebar text-sidebar-foreground shadow-xl duration-200 md:hidden", "data-open:animate-in data-open:fade-in-0 data-open:slide-in-from-left", "data-closed:animate-out data-closed:fade-out-0 data-closed:slide-out-to-left" )} > Menu ); } export function SideNav({ userEmail, userName, avatar, role, }: { userEmail: string; userName: string | null; avatar: string | null; role: Role; }) { const { collapsed, toggle } = useSideNav(); return ( <> ); }