updates
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
"use client"
|
||||
|
||||
import { useCallback } from "react"
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { ChevronRight, Circle } from "lucide-react"
|
||||
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,
|
||||
@@ -22,6 +26,7 @@ import {
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarGroup,
|
||||
SidebarGroupLabel,
|
||||
SidebarHeader,
|
||||
@@ -38,11 +43,19 @@ import {
|
||||
type AppSidebarProps = React.ComponentProps<typeof Sidebar> & {
|
||||
navGroups: NavGroup[]
|
||||
logo?: React.ReactNode
|
||||
user?: UserInfo
|
||||
}
|
||||
|
||||
export function AppSidebar({ navGroups, logo, ...props }: AppSidebarProps) {
|
||||
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 (
|
||||
<Sidebar collapsible="icon" {...props} className="bg-card">
|
||||
@@ -71,6 +84,58 @@ export function AppSidebar({ navGroups, logo, ...props }: AppSidebarProps) {
|
||||
</SidebarGroup>
|
||||
))}
|
||||
</SidebarContent>
|
||||
<SidebarFooter className="p-2">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||
>
|
||||
<Avatar className="size-8 rounded-lg">
|
||||
{user?.avatar && <AvatarImage src={user.avatar as string} alt={user.name} />}
|
||||
<AvatarFallback className="rounded-lg">
|
||||
{user?.initials ?? user?.name?.charAt(0).toUpperCase()}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
{!isCollapsed && (
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">{user?.name}</span>
|
||||
{user?.email && (
|
||||
<span className="truncate text-xs text-muted-foreground">{user.email}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</SidebarMenuButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
side="top"
|
||||
align="start"
|
||||
sideOffset={4}
|
||||
className="w-56"
|
||||
>
|
||||
<DropdownMenuLabel className="font-normal">
|
||||
<div className="flex flex-col">
|
||||
<span className="text-sm font-medium">{user?.name}</span>
|
||||
{user?.email && (
|
||||
<span className="text-xs text-muted-foreground">{user.email}</span>
|
||||
)}
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href="/profile">
|
||||
<UserIcon />
|
||||
Profile
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem variant="destructive" onSelect={handleLogout}>
|
||||
<LogOutIcon />
|
||||
Logout
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarFooter>
|
||||
<SidebarRail />
|
||||
</Sidebar>
|
||||
)
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
"use client"
|
||||
|
||||
import { useCallback, useEffect, useState } from "react"
|
||||
import Link from "next/link"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { useTheme } from "next-themes"
|
||||
import {
|
||||
BellIcon,
|
||||
LogOutIcon,
|
||||
MoonIcon,
|
||||
SearchIcon,
|
||||
SunIcon,
|
||||
UserIcon,
|
||||
} from "lucide-react"
|
||||
|
||||
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 { Button } from "@/shared/components/ui/button"
|
||||
import { SidebarTrigger } from "@/shared/components/ui/sidebar"
|
||||
import {
|
||||
@@ -28,33 +21,17 @@ import {
|
||||
CommandGroup,
|
||||
CommandItem,
|
||||
} from "@/shared/components/ui/command"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/shared/components/ui/dropdown-menu"
|
||||
import { Separator } from "@/shared/components/ui/separator"
|
||||
|
||||
export type DashboardHeaderProps = {
|
||||
user?: UserInfo
|
||||
actions?: React.ReactNode
|
||||
className?: string
|
||||
title?:string
|
||||
}
|
||||
|
||||
export function DashboardHeader({ actions, className }: DashboardHeaderProps) {
|
||||
export function DashboardHeader({ actions, className, title }: DashboardHeaderProps) {
|
||||
const { resolvedTheme, setTheme } = useTheme()
|
||||
const [searchOpen, setSearchOpen] = useState(false)
|
||||
const { logout, user } = useAuthStore((s) => s)
|
||||
const router = useRouter()
|
||||
|
||||
const handleLogout = useCallback(async () => {
|
||||
await logout()
|
||||
router.push("/login")
|
||||
}, [logout, router])
|
||||
|
||||
useEffect(() => {
|
||||
function onKeyDown(e: KeyboardEvent) {
|
||||
@@ -78,75 +55,16 @@ export function DashboardHeader({ actions, className }: DashboardHeaderProps) {
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{title && <h1 className="text-lg font-semibold">{title}</h1>}
|
||||
|
||||
{/* Sidebar toggle — mobile: hamburger, desktop: collapse */}
|
||||
<SidebarTrigger className="-ms-2" />
|
||||
<Separator orientation="vertical" />
|
||||
<SidebarTrigger className="-ms-2 md:hidden" />
|
||||
<Separator orientation="vertical" className="md:hidden" />
|
||||
|
||||
{/* Left side — default actions */}
|
||||
<div className="flex items-center gap-1">
|
||||
{/* User dropdown */}
|
||||
{/* {user && ( */}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="flex items-center gap-2 px-2">
|
||||
<Avatar >
|
||||
{user?.avatar && <AvatarImage src={user?.avatar as string} alt={user?.name} />}
|
||||
<AvatarFallback>
|
||||
{user?.initials ?? user?.name.charAt(0).toUpperCase()}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<span className="hidden text-sm font-medium md:inline-block">
|
||||
{user?.name}
|
||||
</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent align="end" className="w-56">
|
||||
{/* User info header */}
|
||||
<DropdownMenuLabel className="font-normal">
|
||||
<div className="flex items-center gap-3 py-1">
|
||||
<Avatar size="lg">
|
||||
{user?.avatar && <AvatarImage src={user?.avatar as string} alt={user?.name} />}
|
||||
<AvatarFallback className="text-base">
|
||||
{user?.initials ?? user?.name.charAt(0).toUpperCase()}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-sm font-medium text-foreground">{user?.name}</span>
|
||||
{user?.email && (
|
||||
<span className="text-xs text-muted-foreground">{user?.email}</span>
|
||||
)}
|
||||
{user?.role && (
|
||||
<span className="mt-0.5 text-xs font-medium text-primary">{user?.role}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href="/profile">
|
||||
<UserIcon />
|
||||
Profile
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuItem variant="destructive" onSelect={handleLogout}>
|
||||
<LogOutIcon />
|
||||
Logout
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
{/* )} */}
|
||||
|
||||
|
||||
{/* Search trigger */}
|
||||
<Button
|
||||
{/* <Button
|
||||
variant="outline"
|
||||
className="hidden h-8 w-56 justify-start gap-2 text-muted-foreground md:flex"
|
||||
onClick={() => setSearchOpen(true)}
|
||||
@@ -156,7 +74,7 @@ export function DashboardHeader({ actions, className }: DashboardHeaderProps) {
|
||||
<kbd className="pointer-events-none ms-auto inline-flex h-5 select-none items-center gap-0.5 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground">
|
||||
⌘K
|
||||
</kbd>
|
||||
</Button>
|
||||
</Button> */}
|
||||
|
||||
{/* Mobile search icon */}
|
||||
<Button
|
||||
|
||||
@@ -31,11 +31,11 @@ export function DashboardLayout({
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<SidebarProvider defaultOpen={defaultOpen}>
|
||||
<AppSidebar navGroups={navGroups} logo={logo} />
|
||||
<SidebarInset>
|
||||
<div>
|
||||
<AppSidebar navGroups={navGroups} logo={logo} user={user} />
|
||||
|
||||
<SidebarInset className="min-w-0">
|
||||
|
||||
{children}
|
||||
</div>
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
</TooltipProvider>
|
||||
|
||||
@@ -6,39 +6,25 @@ type DashboardPageProps = {
|
||||
children: React.ReactNode
|
||||
header?: React.ReactNode | null
|
||||
headerProps?: DashboardHeaderProps
|
||||
toolbar?: React.ReactNode
|
||||
title?: string
|
||||
fullscreen?: boolean
|
||||
fullscreen?: boolean
|
||||
}
|
||||
|
||||
export default function DashboardPage({ children, header, headerProps, title, fullscreen, toolbar }: DashboardPageProps) {
|
||||
export default function DashboardPage({ children, header, headerProps, fullscreen, }: DashboardPageProps) {
|
||||
const resolvedHeader = header !== undefined
|
||||
? header
|
||||
: <DashboardHeader {...headerProps} />
|
||||
|
||||
return (
|
||||
<div className='page'>
|
||||
<div className={cn("flex flex-col gap-4", fullscreen && "gap-0")}>
|
||||
{resolvedHeader !== null && (
|
||||
<header>
|
||||
{resolvedHeader}
|
||||
</header>
|
||||
)}
|
||||
<main className={cn('p-4 w-full h-full ', fullscreen && 'h-screen p-0 lg:p-0')}>
|
||||
{(title || toolbar) && <div className='flex items-center justify-between gap-4 mb-4'>
|
||||
|
||||
{
|
||||
title &&
|
||||
<h2 className='text-lg lg:text-2xl font-bold '> {title}</h2>
|
||||
}
|
||||
{
|
||||
toolbar &&
|
||||
<div className=''>
|
||||
{toolbar}
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>}
|
||||
{children}
|
||||
<main >
|
||||
<div className={cn(fullscreen ? "w-full px-0" : "p-4")}>
|
||||
{children}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user