"use client" import { useCallback } from "react" import Link from "next/link" import { usePathname, useRouter } from "next/navigation" import { ChevronRight, Circle, LogOutIcon, UserIcon } from "lucide-react" import type { NavGroup, NavItem } from "@/base/types/navigation" import type { UserInfo } from "@/base/types/navigation" import { useAuthStore } from "@/shared/stores/auth-store" import { cn } from "@/shared/lib/utils" import { Avatar, AvatarFallback, AvatarImage } from "@/shared/components/ui/avatar" import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@/shared/components/ui/collapsible" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/shared/components/ui/dropdown-menu" import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupLabel, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarRail, useSidebar, } from "@/shared/components/ui/sidebar" type AppSidebarProps = React.ComponentProps & { navGroups: NavGroup[] logo?: React.ReactNode user?: UserInfo } export function AppSidebar({ navGroups, logo, user, ...props }: AppSidebarProps) { const { state, isMobile } = useSidebar() const isCollapsed = state === "collapsed" && !isMobile const { logout } = useAuthStore((s) => s) const router = useRouter() const handleLogout = useCallback(async () => { await logout() router.push("/login") }, [logout, router]) return ( {logo && ( {logo} )} {navGroups.map((group, groupIndex) => ( {group.label && ( {group.label} )} {group.items.map((item) => item.items && item.items.length > 0 ? ( ) : ( ) )} ))} {user?.avatar && } {user?.initials ?? user?.name?.charAt(0).toUpperCase()} {!isCollapsed && (
{user?.name} {user?.email && ( {user.email} )}
)}
{user?.name} {user?.email && ( {user.email} )}
Profile Logout
) } function isPathActive(pathname: string, href: string): boolean { if (href === '/') return pathname === '/' return pathname === href || pathname.startsWith(href + '/') } function isItemActive(pathname: string, item: { href: string; matchPath?: string; isActive?: boolean }): boolean { if (item.isActive !== undefined) return item.isActive if (item.matchPath) return isPathActive(pathname, item.matchPath) return isPathActive(pathname, item.href) } function SimpleNavItem({ item, isCollapsed }: { item: NavItem; isCollapsed: boolean }) { const pathname = usePathname() const isActive = isItemActive(pathname, item) return ( {item.icon} { !isCollapsed && {item.title} } ) } function CollapsibleNavItem({ item, isCollapsed }: { item: NavItem; isCollapsed: boolean }) { const pathname = usePathname() const isChildActive = item.items?.some((sub) => isItemActive(pathname, sub)) const isActive = item.isActive ?? (isItemActive(pathname, item) || isChildActive === true) // Collapsed sidebar → flyout dropdown with sub-items if (isCollapsed) { return ( {item.icon} { !isCollapsed && {item.title} } {item.title} {item.items?.map((sub) => { const isSubActive = isItemActive(pathname, sub) return ( {sub.icon ? ( svg]:size-4", isSubActive ? "text-primary" : "text-muted-foreground/70")}> {sub.icon} ) : ( )} {sub.title} ) })} ) } // Expanded sidebar → collapsible/accordion sub-menu return ( {item.icon} {item.title} {item.items?.map((sub) => { const isSubActive = isItemActive(pathname, sub) return ( {sub.icon ? ( svg]:size-4", isSubActive ? "text-primary" : "text-muted-foreground/70 group-hover/menu-sub-item:text-primary")}> {sub.icon} ) : ( )} {sub.title} ) })} ) }