init
This commit is contained in:
46
frontend/components/admin-panel/admin-panel-layout.tsx
Normal file
46
frontend/components/admin-panel/admin-panel-layout.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
"use client";
|
||||
|
||||
import { Footer } from "@/components/admin-panel/footer";
|
||||
import { Sidebar } from "@/components/admin-panel/sidebar";
|
||||
import { useSidebar } from "@/hooks/use-sidebar";
|
||||
import { useStore } from "@/hooks/use-store";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export default function AdminPanelLayout({
|
||||
children
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const sidebar = useStore(useSidebar, (x) => x);
|
||||
if (!sidebar) return null;
|
||||
const { getOpenState, settings } = sidebar;
|
||||
return (
|
||||
<>
|
||||
<Sidebar />
|
||||
<main
|
||||
className={cn(
|
||||
"min-h-[calc(100vh_-_56px)] bg-zinc-50 dark:bg-zinc-900 transition-[margin-left] ease-in-out duration-300",
|
||||
!settings.disabled
|
||||
? getOpenState()
|
||||
? "ml-0 lg:ml-72" // Sidebar geöffnet
|
||||
: "ml-0 lg:ml-[90px]" // Sidebar minimiert
|
||||
: "ml-0" // Sidebar deaktiviert
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</main>
|
||||
<footer
|
||||
className={cn(
|
||||
"transition-[margin-left] ease-in-out duration-300",
|
||||
!settings.disabled
|
||||
? getOpenState()
|
||||
? "ml-0 lg:ml-72"
|
||||
: "ml-0 lg:ml-[90px]"
|
||||
: "ml-0"
|
||||
)}
|
||||
>
|
||||
<Footer />
|
||||
</footer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
189
frontend/components/admin-panel/collapse-menu-button.tsx
Normal file
189
frontend/components/admin-panel/collapse-menu-button.tsx
Normal file
@@ -0,0 +1,189 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import { ChevronDown, Dot, LucideIcon } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { DropdownMenuArrow } from "@radix-ui/react-dropdown-menu";
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger
|
||||
} from "@/components/ui/collapsible";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipTrigger,
|
||||
TooltipContent,
|
||||
TooltipProvider
|
||||
} from "@/components/ui/tooltip";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuSeparator
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
type Submenu = {
|
||||
href: string;
|
||||
label: string;
|
||||
active?: boolean;
|
||||
};
|
||||
|
||||
interface CollapseMenuButtonProps {
|
||||
icon: LucideIcon;
|
||||
label: string;
|
||||
active: boolean;
|
||||
submenus: Submenu[];
|
||||
isOpen: boolean | undefined;
|
||||
}
|
||||
|
||||
export function CollapseMenuButton({
|
||||
icon: Icon,
|
||||
label,
|
||||
submenus,
|
||||
isOpen
|
||||
}: CollapseMenuButtonProps) {
|
||||
const pathname = usePathname();
|
||||
const isSubmenuActive = submenus.some((submenu) =>
|
||||
submenu.active === undefined ? submenu.href === pathname : submenu.active
|
||||
);
|
||||
const [isCollapsed, setIsCollapsed] = useState<boolean>(isSubmenuActive);
|
||||
|
||||
return isOpen ? (
|
||||
<Collapsible
|
||||
open={isCollapsed}
|
||||
onOpenChange={setIsCollapsed}
|
||||
className="w-full"
|
||||
>
|
||||
<CollapsibleTrigger
|
||||
className="[&[data-state=open]>div>div>svg]:rotate-180 mb-1"
|
||||
asChild
|
||||
>
|
||||
<Button
|
||||
variant={isSubmenuActive ? "secondary" : "ghost"}
|
||||
className="w-full justify-start h-10"
|
||||
>
|
||||
<div className="w-full items-center flex justify-between">
|
||||
<div className="flex items-center">
|
||||
<span className="mr-4">
|
||||
<Icon size={18} />
|
||||
</span>
|
||||
<p
|
||||
className={cn(
|
||||
"max-w-[150px] truncate",
|
||||
isOpen
|
||||
? "translate-x-0 opacity-100"
|
||||
: "-translate-x-96 opacity-0"
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"whitespace-nowrap",
|
||||
isOpen
|
||||
? "translate-x-0 opacity-100"
|
||||
: "-translate-x-96 opacity-0"
|
||||
)}
|
||||
>
|
||||
<ChevronDown
|
||||
size={18}
|
||||
className="transition-transform duration-200"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="overflow-hidden data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down">
|
||||
{submenus.map(({ href, label, active }, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
variant={
|
||||
(active === undefined && pathname === href) || active
|
||||
? "secondary"
|
||||
: "ghost"
|
||||
}
|
||||
className="w-full justify-start h-10 mb-1"
|
||||
asChild
|
||||
>
|
||||
<Link href={href}>
|
||||
<span className="mr-4 ml-2">
|
||||
<Dot size={18} />
|
||||
</span>
|
||||
<p
|
||||
className={cn(
|
||||
"max-w-[170px] truncate",
|
||||
isOpen
|
||||
? "translate-x-0 opacity-100"
|
||||
: "-translate-x-96 opacity-0"
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</p>
|
||||
</Link>
|
||||
</Button>
|
||||
))}
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
) : (
|
||||
<DropdownMenu>
|
||||
<TooltipProvider disableHoverableContent>
|
||||
<Tooltip delayDuration={100}>
|
||||
<TooltipTrigger asChild>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant={isSubmenuActive ? "secondary" : "ghost"}
|
||||
className="w-full justify-start h-10 mb-1"
|
||||
>
|
||||
<div className="w-full items-center flex justify-between">
|
||||
<div className="flex items-center">
|
||||
<span className={cn(isOpen === false ? "" : "mr-4")}>
|
||||
<Icon size={18} />
|
||||
</span>
|
||||
<p
|
||||
className={cn(
|
||||
"max-w-[200px] truncate",
|
||||
isOpen === false ? "opacity-0" : "opacity-100"
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" align="start" alignOffset={2}>
|
||||
{label}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<DropdownMenuContent side="right" sideOffset={25} align="start">
|
||||
<DropdownMenuLabel className="max-w-[190px] truncate">
|
||||
{label}
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{submenus.map(({ href, label, active }, index) => (
|
||||
<DropdownMenuItem key={index} asChild>
|
||||
<Link
|
||||
className={`cursor-pointer ${
|
||||
((active === undefined && pathname === href) || active) &&
|
||||
"bg-secondary"
|
||||
}`}
|
||||
href={href}
|
||||
>
|
||||
<p className="max-w-[180px] truncate">{label}</p>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
<DropdownMenuArrow className="fill-border" />
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
15
frontend/components/admin-panel/content-layout.tsx
Normal file
15
frontend/components/admin-panel/content-layout.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Navbar } from "@/components/admin-panel/navbar";
|
||||
|
||||
interface ContentLayoutProps {
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function ContentLayout({ title, children }: ContentLayoutProps) {
|
||||
return (
|
||||
<div>
|
||||
<Navbar title={title} />
|
||||
<div className="container pt-8 pb-8 px-4 sm:px-8">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
12
frontend/components/admin-panel/footer.tsx
Normal file
12
frontend/components/admin-panel/footer.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
export function Footer() {
|
||||
return (
|
||||
<div className="z-20 w-full bg-background/95 shadow backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<div className="mx-4 md:mx-8 flex h-14 items-center">
|
||||
<p className="text-xs md:text-sm leading-loose text-muted-foreground text-left">
|
||||
Built by{" "}
|
||||
Andreas Wilms
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
155
frontend/components/admin-panel/menu.tsx
Normal file
155
frontend/components/admin-panel/menu.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { Ellipsis, LogOut } from "lucide-react";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { getMenuList } from "@/lib/menu-list";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { CollapseMenuButton } from "@/components/admin-panel/collapse-menu-button";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipTrigger,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { logout } from "@/lib/session";
|
||||
|
||||
interface MenuProps {
|
||||
isOpen: boolean | undefined;
|
||||
}
|
||||
|
||||
export function Menu({ isOpen }: MenuProps) {
|
||||
const pathname = usePathname();
|
||||
const menuList = getMenuList(pathname);
|
||||
const router = useRouter();
|
||||
|
||||
async function handleLogOut() {
|
||||
await logout();
|
||||
router.push("/login");
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollArea className="[&>div>div[style]]:!block">
|
||||
<nav className="mt-8 h-full w-full">
|
||||
<ul className="flex flex-col min-h-[calc(100vh-48px-36px-16px-32px)] lg:min-h-[calc(100vh-32px-40px-32px)] items-start space-y-1 px-2">
|
||||
{menuList.map(({ groupLabel, menus }, index) => (
|
||||
<li className={cn("w-full", groupLabel ? "pt-5" : "")} key={index}>
|
||||
{(isOpen && groupLabel) || isOpen === undefined ? (
|
||||
<p className="text-sm font-medium text-muted-foreground px-4 pb-2 max-w-[248px] truncate">
|
||||
{groupLabel}
|
||||
</p>
|
||||
) : !isOpen && isOpen !== undefined && groupLabel ? (
|
||||
<TooltipProvider>
|
||||
<Tooltip delayDuration={100}>
|
||||
<TooltipTrigger className="w-full">
|
||||
<div className="w-full flex justify-center items-center">
|
||||
<Ellipsis className="h-5 w-5" />
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">
|
||||
<p>{groupLabel}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
) : (
|
||||
<p className="pb-2"></p>
|
||||
)}
|
||||
{menus.map(
|
||||
({ href, label, icon: Icon, active, submenus }, index) =>
|
||||
!submenus || submenus.length === 0 ? (
|
||||
<div className="w-full" key={index}>
|
||||
<TooltipProvider disableHoverableContent>
|
||||
<Tooltip delayDuration={100}>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant={
|
||||
(active === undefined &&
|
||||
pathname.startsWith(href)) ||
|
||||
active
|
||||
? "secondary"
|
||||
: "ghost"
|
||||
}
|
||||
className="w-full justify-start h-10 mb-1"
|
||||
asChild
|
||||
>
|
||||
<Link href={href}>
|
||||
<span
|
||||
className={cn(isOpen === false ? "" : "mr-4")}
|
||||
>
|
||||
<Icon size={18} />
|
||||
</span>
|
||||
<p
|
||||
className={cn(
|
||||
"max-w-[200px] truncate",
|
||||
isOpen === false
|
||||
? "-translate-x-96 opacity-0"
|
||||
: "translate-x-0 opacity-100"
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</p>
|
||||
</Link>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
{isOpen === false && (
|
||||
<TooltipContent side="right">
|
||||
{label}
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-full" key={index}>
|
||||
<CollapseMenuButton
|
||||
icon={Icon}
|
||||
label={label}
|
||||
active={
|
||||
active === undefined
|
||||
? pathname.startsWith(href)
|
||||
: active
|
||||
}
|
||||
submenus={submenus}
|
||||
isOpen={isOpen}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
<li className="w-full grow flex items-end">
|
||||
<TooltipProvider disableHoverableContent>
|
||||
<Tooltip delayDuration={100}>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
onClick={handleLogOut}
|
||||
variant="outline"
|
||||
className="w-full justify-center h-10 mt-5"
|
||||
>
|
||||
<span className={cn(isOpen === false ? "" : "mr-4")}>
|
||||
<LogOut size={18} />
|
||||
</span>
|
||||
<p
|
||||
className={cn(
|
||||
"whitespace-nowrap",
|
||||
isOpen === false ? "opacity-0 hidden" : "opacity-100"
|
||||
)}
|
||||
>
|
||||
Abmelden
|
||||
</p>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
{isOpen === false && (
|
||||
<TooltipContent side="right">Sign out</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</ScrollArea>
|
||||
);
|
||||
}
|
||||
24
frontend/components/admin-panel/navbar.tsx
Normal file
24
frontend/components/admin-panel/navbar.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { ModeToggle } from "@/components/mode-toggle";
|
||||
import { UserNav } from "@/components/admin-panel/user-nav";
|
||||
import { SheetMenu } from "@/components/admin-panel/sheet-menu";
|
||||
|
||||
interface NavbarProps {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export function Navbar({ title }: NavbarProps) {
|
||||
return (
|
||||
<header className="sticky top-0 z-10 w-full bg-background/95 shadow backdrop-blur supports-[backdrop-filter]:bg-background/60 dark:shadow-secondary">
|
||||
<div className="mx-4 sm:mx-8 flex h-14 items-center">
|
||||
<div className="flex items-center space-x-4 lg:space-x-0">
|
||||
<SheetMenu />
|
||||
<h1 className="font-bold">{title}</h1>
|
||||
</div>
|
||||
<div className="flex flex-1 items-center justify-end">
|
||||
<ModeToggle />
|
||||
<UserNav />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
39
frontend/components/admin-panel/sheet-menu.tsx
Normal file
39
frontend/components/admin-panel/sheet-menu.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import Link from "next/link";
|
||||
import { MenuIcon, PanelsTopLeft } from "lucide-react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Menu } from "@/components/admin-panel/menu";
|
||||
import {
|
||||
Sheet,
|
||||
SheetHeader,
|
||||
SheetContent,
|
||||
SheetTrigger,
|
||||
SheetTitle
|
||||
} from "@/components/ui/sheet";
|
||||
|
||||
export function SheetMenu() {
|
||||
return (
|
||||
<Sheet>
|
||||
<SheetTrigger className="lg:hidden" asChild>
|
||||
<Button className="h-8" variant="outline" size="icon">
|
||||
<MenuIcon size={20} />
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent className="sm:w-72 px-3 h-full flex flex-col" side="left">
|
||||
<SheetHeader>
|
||||
<Button
|
||||
className="flex justify-center items-center pb-2 pt-1"
|
||||
variant="link"
|
||||
asChild
|
||||
>
|
||||
<Link href="/dashboard" className="flex items-center gap-2">
|
||||
<PanelsTopLeft className="w-6 h-6 mr-1" />
|
||||
<SheetTitle className="font-bold text-lg">Brand</SheetTitle>
|
||||
</Link>
|
||||
</Button>
|
||||
</SheetHeader>
|
||||
<Menu isOpen />
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
);
|
||||
}
|
||||
29
frontend/components/admin-panel/sidebar-toggle.tsx
Normal file
29
frontend/components/admin-panel/sidebar-toggle.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { ChevronLeft } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
interface SidebarToggleProps {
|
||||
isOpen: boolean | undefined;
|
||||
setIsOpen?: () => void;
|
||||
}
|
||||
|
||||
export function SidebarToggle({ isOpen, setIsOpen }: SidebarToggleProps) {
|
||||
return (
|
||||
<div className="invisible lg:visible absolute top-[12px] -right-[16px] z-20">
|
||||
<Button
|
||||
onClick={() => setIsOpen?.()}
|
||||
className="rounded-md w-8 h-8"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
>
|
||||
<ChevronLeft
|
||||
className={cn(
|
||||
"h-4 w-4 transition-transform ease-in-out duration-700",
|
||||
isOpen === false ? "rotate-180" : "rotate-0"
|
||||
)}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
55
frontend/components/admin-panel/sidebar.tsx
Normal file
55
frontend/components/admin-panel/sidebar.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
"use client";
|
||||
import { Menu } from "@/components/admin-panel/menu";
|
||||
import { SidebarToggle } from "@/components/admin-panel/sidebar-toggle";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useSidebar } from "@/hooks/use-sidebar";
|
||||
import { useStore } from "@/hooks/use-store";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { PanelsTopLeft } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
export function Sidebar() {
|
||||
const sidebar = useStore(useSidebar, (x) => x);
|
||||
if (!sidebar) return null;
|
||||
const { isOpen, toggleOpen, getOpenState, setIsHover, settings } = sidebar;
|
||||
return (
|
||||
<aside
|
||||
className={cn(
|
||||
"fixed top-0 left-0 z-20 h-screen -translate-x-full lg:translate-x-0 transition-[width] ease-in-out duration-300",
|
||||
!getOpenState() ? "w-[90px]" : "w-72",
|
||||
settings.disabled && "hidden"
|
||||
)}
|
||||
>
|
||||
<SidebarToggle isOpen={isOpen} setIsOpen={toggleOpen} />
|
||||
<div
|
||||
onMouseEnter={() => setIsHover(true)}
|
||||
onMouseLeave={() => setIsHover(false)}
|
||||
className="relative h-full flex flex-col px-3 py-4 overflow-y-auto shadow-md dark:shadow-zinc-800"
|
||||
>
|
||||
<Button
|
||||
className={cn(
|
||||
"transition-transform ease-in-out duration-300 mb-1",
|
||||
!getOpenState() ? "translate-x-1" : "translate-x-0"
|
||||
)}
|
||||
variant="link"
|
||||
asChild
|
||||
>
|
||||
<Link href="/" className="flex items-center gap-2">
|
||||
<PanelsTopLeft className="w-6 h-6 mr-1" />
|
||||
<h1
|
||||
className={cn(
|
||||
"font-bold text-lg whitespace-nowrap transition-[transform,opacity,display] ease-in-out duration-300",
|
||||
!getOpenState()
|
||||
? "-translate-x-96 opacity-0 hidden"
|
||||
: "translate-x-0 opacity-100"
|
||||
)}
|
||||
>
|
||||
ERP
|
||||
</h1>
|
||||
</Link>
|
||||
</Button>
|
||||
<Menu isOpen={getOpenState()} />
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
78
frontend/components/admin-panel/user-nav.tsx
Normal file
78
frontend/components/admin-panel/user-nav.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { LayoutGrid, LogOut, User } from "lucide-react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
TooltipProvider,
|
||||
} from "@/components/ui/tooltip";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
|
||||
export function UserNav() {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<TooltipProvider disableHoverableContent>
|
||||
<Tooltip delayDuration={100}>
|
||||
<TooltipTrigger asChild>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="relative h-8 w-8 rounded-full"
|
||||
>
|
||||
<Avatar className="h-8 w-8">
|
||||
<AvatarImage src="#" alt="Avatar" />
|
||||
<AvatarFallback className="bg-transparent">JD</AvatarFallback>
|
||||
</Avatar>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">Profile</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
<DropdownMenuContent className="w-56" align="end" forceMount>
|
||||
<DropdownMenuLabel className="font-normal">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<p className="text-sm font-medium leading-none">John Doe</p>
|
||||
<p className="text-xs leading-none text-muted-foreground">
|
||||
johndoe@example.com
|
||||
</p>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem className="hover:cursor-pointer" asChild>
|
||||
<Link href="/dashboard" className="flex items-center">
|
||||
<LayoutGrid className="w-4 h-4 mr-3 text-muted-foreground" />
|
||||
Dashboard
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="hover:cursor-pointer" asChild>
|
||||
<Link href="/account" className="flex items-center">
|
||||
<User className="w-4 h-4 mr-3 text-muted-foreground" />
|
||||
Account
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem className="hover:cursor-pointer" onClick={() => {}}>
|
||||
<LogOut className="w-4 h-4 mr-3 text-muted-foreground" />
|
||||
Sign out
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user