feat: new sidebar added
							parent
							
								
									74a50f22e4
								
							
						
					
					
						commit
						8ee1aeb8d3
					
				|  | @ -18,7 +18,7 @@ class AuthenticatedSessionController extends Controller | |||
|      */ | ||||
|     public function create(): Response | ||||
|     { | ||||
|         return Inertia::render('Auth/Login', [ | ||||
|         return Inertia::render('auth/login', [ | ||||
|             'canResetPassword' => Route::has('password.request'), | ||||
|             'status' => session('status'), | ||||
|         ]); | ||||
|  |  | |||
|  | @ -17,7 +17,7 @@ class ConfirmablePasswordController extends Controller | |||
|      */ | ||||
|     public function show(): Response | ||||
|     { | ||||
|         return Inertia::render('Auth/ConfirmPassword'); | ||||
|         return Inertia::render('auth/confirm-password'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  |  | |||
|  | @ -17,6 +17,6 @@ class EmailVerificationPromptController extends Controller | |||
|     { | ||||
|         return $request->user()->hasVerifiedEmail() | ||||
|                     ? redirect()->intended(route('dashboard', absolute: false)) | ||||
|                     : Inertia::render('Auth/VerifyEmail', ['status' => session('status')]); | ||||
|                     : Inertia::render('auth/verify-email', ['status' => session('status')]); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -21,7 +21,7 @@ class NewPasswordController extends Controller | |||
|      */ | ||||
|     public function create(Request $request): Response | ||||
|     { | ||||
|         return Inertia::render('Auth/ResetPassword', [ | ||||
|         return Inertia::render('auth/reset-password', [ | ||||
|             'email' => $request->email, | ||||
|             'token' => $request->route('token'), | ||||
|         ]); | ||||
|  |  | |||
|  | @ -17,7 +17,7 @@ class PasswordResetLinkController extends Controller | |||
|      */ | ||||
|     public function create(): Response | ||||
|     { | ||||
|         return Inertia::render('Auth/ForgotPassword', [ | ||||
|         return Inertia::render('auth/forgot-password', [ | ||||
|             'status' => session('status'), | ||||
|         ]); | ||||
|     } | ||||
|  |  | |||
|  | @ -20,7 +20,7 @@ class RegisteredUserController extends Controller | |||
|      */ | ||||
|     public function create(): Response | ||||
|     { | ||||
|         return Inertia::render('Auth/Register'); | ||||
|         return Inertia::render('auth/register'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  |  | |||
|  | @ -18,7 +18,7 @@ class ProfileController extends Controller | |||
|      */ | ||||
|     public function edit(Request $request): Response | ||||
|     { | ||||
|         return Inertia::render('Profile/Edit', [ | ||||
|         return Inertia::render('profile/edit', [ | ||||
|             'mustVerifyEmail' => $request->user() instanceof MustVerifyEmail, | ||||
|             'status' => session('status'), | ||||
|         ]); | ||||
|  |  | |||
|  | @ -11,7 +11,7 @@ | |||
|     "prefix": "" | ||||
|   }, | ||||
|   "aliases": { | ||||
|     "components": "@/Components", | ||||
|     "components": "@/components", | ||||
|     "utils": "@/lib/utils" | ||||
|   } | ||||
| } | ||||
| } | ||||
|  |  | |||
											
												
													File diff suppressed because it is too large
													Load Diff
												
											
										
									
								
											
												
													File diff suppressed because it is too large
													Load Diff
												
											
										
									
								
								
									
									
										
											12
										
									
									package.json
									
									
									
									
								
								
							
							
										
											12
										
									
									package.json
									
									
									
									
								|  | @ -25,14 +25,16 @@ | |||
|     }, | ||||
|     "dependencies": { | ||||
|         "@radix-ui/react-alert-dialog": "^1.0.5", | ||||
|         "@radix-ui/react-avatar": "^1.0.4", | ||||
|         "@radix-ui/react-collapsible": "^1.0.3", | ||||
|         "@radix-ui/react-dropdown-menu": "^2.0.6", | ||||
|         "@radix-ui/react-avatar": "^1.1.1", | ||||
|         "@radix-ui/react-collapsible": "^1.1.1", | ||||
|         "@radix-ui/react-dialog": "^1.1.2", | ||||
|         "@radix-ui/react-dropdown-menu": "^2.1.2", | ||||
|         "@radix-ui/react-label": "^2.0.2", | ||||
|         "@radix-ui/react-popover": "^1.0.7", | ||||
|         "@radix-ui/react-scroll-area": "^1.0.5", | ||||
|         "@radix-ui/react-slot": "^1.0.2", | ||||
|         "@radix-ui/react-tooltip": "^1.0.7", | ||||
|         "@radix-ui/react-separator": "^1.1.0", | ||||
|         "@radix-ui/react-slot": "^1.1.0", | ||||
|         "@radix-ui/react-tooltip": "^1.1.3", | ||||
|         "class-variance-authority": "^0.7.0", | ||||
|         "clsx": "^2.1.1", | ||||
|         "date-fns": "^3.6.0", | ||||
|  |  | |||
|  | @ -5,47 +5,63 @@ | |||
| @layer base { | ||||
|     :root { | ||||
|         --background: 0 0% 100%; | ||||
|         --foreground: 222.2 84% 4.9%; | ||||
|         --foreground: 240 10% 3.9%; | ||||
|         --card: 0 0% 100%; | ||||
|         --card-foreground: 222.2 84% 4.9%; | ||||
|         --card-foreground: 240 10% 3.9%; | ||||
|         --popover: 0 0% 100%; | ||||
|         --popover-foreground: 222.2 84% 4.9%; | ||||
|         --primary: 221.2 83.2% 53.3%; | ||||
|         --primary-foreground: 210 40% 98%; | ||||
|         --secondary: 210 40% 96.1%; | ||||
|         --secondary-foreground: 222.2 47.4% 11.2%; | ||||
|         --muted: 210 40% 96.1%; | ||||
|         --muted-foreground: 215.4 16.3% 46.9%; | ||||
|         --accent: 210 40% 96.1%; | ||||
|         --accent-foreground: 222.2 47.4% 11.2%; | ||||
|         --popover-foreground: 240 10% 3.9%; | ||||
|         --primary: 240 5.9% 10%; | ||||
|         --primary-foreground: 0 0% 98%; | ||||
|         --secondary: 240 4.8% 95.9%; | ||||
|         --secondary-foreground: 240 5.9% 10%; | ||||
|         --muted: 240 4.8% 95.9%; | ||||
|         --muted-foreground: 240 3.8% 46.1%; | ||||
|         --accent: 240 4.8% 95.9%; | ||||
|         --accent-foreground: 240 5.9% 10%; | ||||
|         --destructive: 0 84.2% 60.2%; | ||||
|         --destructive-foreground: 210 40% 98%; | ||||
|         --border: 214.3 31.8% 91.4%; | ||||
|         --input: 214.3 31.8% 91.4%; | ||||
|         --ring: 221.2 83.2% 53.3%; | ||||
|         --destructive-foreground: 0 0% 98%; | ||||
|         --border: 240 5.9% 90%; | ||||
|         --input: 240 5.9% 90%; | ||||
|         --ring: 240 5.9% 10%; | ||||
|         --radius: 0.5rem; | ||||
|         --sidebar-background: 0 0% 98%; | ||||
|         --sidebar-foreground: 240 5.3% 26.1%; | ||||
|         --sidebar-primary: 240 5.9% 10%; | ||||
|         --sidebar-primary-foreground: 0 0% 98%; | ||||
|         --sidebar-accent: 240 4.8% 95.9%; | ||||
|         --sidebar-accent-foreground: 240 5.9% 10%; | ||||
|         --sidebar-border: 220 13% 91%; | ||||
|         --sidebar-ring: 217.2 91.2% 59.8%; | ||||
|     } | ||||
| 
 | ||||
|     .dark { | ||||
|         --background: 222.2 84% 4.9%; | ||||
|         --foreground: 210 40% 98%; | ||||
|         --card: 222.2 84% 4.9%; | ||||
|         --card-foreground: 210 40% 98%; | ||||
|         --popover: 222.2 84% 4.9%; | ||||
|         --popover-foreground: 210 40% 98%; | ||||
|         --primary: 217.2 91.2% 59.8%; | ||||
|         --primary-foreground: 222.2 47.4% 11.2%; | ||||
|         --secondary: 217.2 32.6% 17.5%; | ||||
|         --secondary-foreground: 210 40% 98%; | ||||
|         --muted: 217.2 32.6% 17.5%; | ||||
|         --muted-foreground: 215 20.2% 65.1%; | ||||
|         --accent: 217.2 32.6% 17.5%; | ||||
|         --accent-foreground: 210 40% 98%; | ||||
|         --background: 240 10% 3.9%; | ||||
|         --foreground: 0 0% 98%; | ||||
|         --card: 240 10% 3.9%; | ||||
|         --card-foreground: 0 0% 98%; | ||||
|         --popover: 240 10% 3.9%; | ||||
|         --popover-foreground: 0 0% 98%; | ||||
|         --primary: 0 0% 98%; | ||||
|         --primary-foreground: 240 5.9% 10%; | ||||
|         --secondary: 240 3.7% 15.9%; | ||||
|         --secondary-foreground: 0 0% 98%; | ||||
|         --muted: 240 3.7% 15.9%; | ||||
|         --muted-foreground: 240 5% 64.9%; | ||||
|         --accent: 240 3.7% 15.9%; | ||||
|         --accent-foreground: 0 0% 98%; | ||||
|         --destructive: 0 62.8% 30.6%; | ||||
|         --destructive-foreground: 210 40% 98%; | ||||
|         --border: 217.2 32.6% 17.5%; | ||||
|         --input: 217.2 32.6% 17.5%; | ||||
|         --ring: 224.3 76.3% 48%; | ||||
|         --destructive-foreground: 0 0% 98%; | ||||
|         --border: 240 3.7% 15.9%; | ||||
|         --input: 240 3.7% 15.9%; | ||||
|         --ring: 240 4.9% 83.9%; | ||||
|         --sidebar-background: 240 5.9% 10%; | ||||
|         --sidebar-foreground: 240 4.8% 95.9%; | ||||
|         --sidebar-primary: 224.3 76.3% 48%; | ||||
|         --sidebar-primary-foreground: 0 0% 100%; | ||||
|         --sidebar-accent: 240 3.7% 15.9%; | ||||
|         --sidebar-accent-foreground: 240 4.8% 95.9%; | ||||
|         --sidebar-border: 240 3.7% 15.9%; | ||||
|         --sidebar-ring: 217.2 91.2% 59.8%; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,60 +0,0 @@ | |||
| import { Sheet, SheetContent, SheetTrigger } from "@/Components/ui/sheet"; | ||||
| import { Button } from "@/Components/ui/button"; | ||||
| import { AlignJustifyIcon, Menu } from "lucide-react"; | ||||
| import { Link } from "@inertiajs/react"; | ||||
| import { MenuItemProp } from "@/types"; | ||||
| import ApplicationLogo from "@/Components/ApplicationLogo"; | ||||
| import { DropdownMenuSeparator } from "./ui/dropdown-menu"; | ||||
| 
 | ||||
| const MobileMenu = ({ links }: { links: MenuItemProp[] }) => { | ||||
|     return ( | ||||
|         <Sheet> | ||||
|             <SheetTrigger asChild> | ||||
|                 <Button | ||||
|                     variant="outline" | ||||
|                     size="icon" | ||||
|                     className="shrink-0 md:hidden" | ||||
|                 > | ||||
|                     <Menu className="h-5 w-5" /> | ||||
|                     <span className="sr-only">Toggle navigation menu</span> | ||||
|                 </Button> | ||||
|             </SheetTrigger> | ||||
|             <SheetContent side="left" className="flex flex-col"> | ||||
|                 <nav className="text-lg font-medium"> | ||||
|                     <Link | ||||
|                         href="#" | ||||
|                         className="flex items-center gap-2 text-lg font-semibold mb-4" | ||||
|                     > | ||||
|                         <ApplicationLogo className="h-8 w-8 fill-current text-gray-500" /> | ||||
| 
 | ||||
|                         <span>Acme Inc</span> | ||||
|                     </Link> | ||||
| 
 | ||||
|                     <DropdownMenuSeparator /> | ||||
| 
 | ||||
|                     <div className="space-y-2"> | ||||
|                         {links.map((link, index) => ( | ||||
|                             <MobileMenuItem key={index} link={link} /> | ||||
|                         ))} | ||||
|                     </div> | ||||
|                 </nav> | ||||
|             </SheetContent> | ||||
|         </Sheet> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| const MobileMenuItem = ({ link }: { link: MenuItemProp }) => { | ||||
|     const Icon = (link.icon ?? AlignJustifyIcon) as React.ElementType; | ||||
| 
 | ||||
|     return ( | ||||
|         <Link | ||||
|             href={link.href} | ||||
|             className="mx-[-0.65rem] flex items-center gap-4 rounded-xl px-3 py-2 text-muted-foreground hover:text-foreground" | ||||
|         > | ||||
|             <Icon className="h-5 w-5" /> | ||||
|             {link.title} | ||||
|         </Link> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| export default MobileMenu; | ||||
|  | @ -1,116 +0,0 @@ | |||
| import { Link, usePage } from "@inertiajs/react"; | ||||
| import { AlignJustifyIcon, Home, Icon } from "lucide-react"; | ||||
| import { cn } from "@/lib/utils"; | ||||
| import { buttonVariants } from "@/Components/ui/button"; | ||||
| import { | ||||
|     Tooltip, | ||||
|     TooltipContent, | ||||
|     TooltipProvider, | ||||
|     TooltipTrigger, | ||||
| } from "@/Components/ui/tooltip"; | ||||
| import { MenuItemProp } from "@/types"; | ||||
| import ApplicationLogo from "@/Components/ApplicationLogo"; | ||||
| import { ReactNode } from "react"; | ||||
| 
 | ||||
| type Props = { | ||||
|     links: MenuItemProp[]; | ||||
|     isCollapsed?: boolean; | ||||
| }; | ||||
| 
 | ||||
| type MenuItemProps = { | ||||
|     link: MenuItemProp; | ||||
|     isActive?: boolean; | ||||
| }; | ||||
| 
 | ||||
| const CollapsedMenuItem = ({ link, isActive }: MenuItemProps) => { | ||||
|     const Icon = (link.icon ?? AlignJustifyIcon) as React.ElementType; | ||||
| 
 | ||||
|     return ( | ||||
|         <Tooltip delayDuration={0}> | ||||
|             <TooltipTrigger asChild> | ||||
|                 <Link | ||||
|                     href={link.href} | ||||
|                     className={cn( | ||||
|                         buttonVariants({ | ||||
|                             variant: isActive ? "default" : "ghost", | ||||
|                             size: "icon", | ||||
|                         }), | ||||
|                         "h-9 w-9", | ||||
|                         link.variant === "default" && | ||||
|                             "dark:bg-muted dark:text-muted-foreground dark:hover:bg-muted dark:hover:text-white" | ||||
|                     )} | ||||
|                 > | ||||
|                     {<Icon className="h-4 w-4" />} | ||||
|                     <span className="sr-only">{link.title}</span> | ||||
|                 </Link> | ||||
|             </TooltipTrigger> | ||||
|             <TooltipContent side="right" className="flex items-center gap-4"> | ||||
|                 {link.title} | ||||
|             </TooltipContent> | ||||
|         </Tooltip> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| const ExpandedMenuItem = ({ link, isActive }: MenuItemProps) => { | ||||
|     const Icon = (link.icon ?? AlignJustifyIcon) as React.ElementType; | ||||
| 
 | ||||
|     return ( | ||||
|         <Link | ||||
|             href={link.href} | ||||
|             className={cn( | ||||
|                 buttonVariants({ variant: isActive ? "default" : "ghost" }), | ||||
|                 "justify-start" | ||||
|             )} | ||||
|         > | ||||
|             {<Icon className="mr-2 h-4 w-4" />} | ||||
|             {link.title} | ||||
|         </Link> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| const Sidebar = ({ links, isCollapsed }: Props) => { | ||||
|     const { url } = usePage(); | ||||
| 
 | ||||
|     return ( | ||||
|         <TooltipProvider> | ||||
|             <nav className="hidden bg-muted/40 md:block h-full"> | ||||
|                 <div className="flex h-full max-h-screen flex-col gap-2"> | ||||
|                     <div className="flex h-14 items-center border-b px-4 lg:h-[60px] lg:px-6"> | ||||
|                         <Link | ||||
|                             href="/" | ||||
|                             className={cn("flex items-center font-semibold", { | ||||
|                                 "justify-center": isCollapsed, | ||||
|                             })} | ||||
|                         > | ||||
|                             <ApplicationLogo className="h-6 w-6 fill-current text-gray-500" /> | ||||
|                             {!isCollapsed && ( | ||||
|                                 <span className="pl-2">Acme Inc</span> | ||||
|                             )} | ||||
|                         </Link> | ||||
|                     </div> | ||||
|                     <div className="flex-1"> | ||||
|                         <nav className="grid items-start space-y-2 text-sm font-medium lg:px-4"> | ||||
|                             {links.map((link, index) => | ||||
|                                 isCollapsed ? ( | ||||
|                                     <CollapsedMenuItem | ||||
|                                         key={index} | ||||
|                                         link={link} | ||||
|                                         isActive={link.href.includes(url)} | ||||
|                                     /> | ||||
|                                 ) : ( | ||||
|                                     <ExpandedMenuItem | ||||
|                                         key={index} | ||||
|                                         link={link} | ||||
|                                         isActive={link.href.includes(url)} | ||||
|                                     /> | ||||
|                                 ) | ||||
|                             )} | ||||
|                         </nav> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </nav> | ||||
|         </TooltipProvider> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| export default Sidebar; | ||||
|  | @ -1,156 +0,0 @@ | |||
| import { PropsWithChildren, ReactNode, useState } from "react"; | ||||
| import { Link } from "@inertiajs/react"; | ||||
| import { MenuItemProp, User } from "@/types"; | ||||
| import { CircleUser, Search, UserIcon } from "lucide-react"; | ||||
| import { Button } from "@/Components/ui/button"; | ||||
| import { | ||||
|     DropdownMenu, | ||||
|     DropdownMenuContent, | ||||
|     DropdownMenuItem, | ||||
|     DropdownMenuLabel, | ||||
|     DropdownMenuSeparator, | ||||
|     DropdownMenuTrigger, | ||||
| } from "@/Components/ui/dropdown-menu"; | ||||
| import { Input } from "@/Components/ui/input"; | ||||
| import Sidebar from "@/Components/Sidebar"; | ||||
| import MobileMenu from "@/Components/MobileMenu"; | ||||
| import { | ||||
|     ResizableHandle, | ||||
|     ResizablePanel, | ||||
|     ResizablePanelGroup, | ||||
| } from "@/Components/ui/resizable"; | ||||
| import { ScrollArea } from "@/Components/ui/scroll-area"; | ||||
| import { cn } from "@/lib/utils"; | ||||
| import AppearanceDropdown from "@/Components/AppearanceDropdown"; | ||||
| 
 | ||||
| const links: MenuItemProp[] = [ | ||||
|     { | ||||
|         title: "Dashboard", | ||||
|         href: route("dashboard"), | ||||
|         variant: "default", | ||||
|     }, | ||||
|     { | ||||
|         title: "Profile", | ||||
|         href: route("profile.edit"), | ||||
|         variant: "ghost", | ||||
|         icon: UserIcon, | ||||
|     }, | ||||
| ]; | ||||
| 
 | ||||
| export default function AuthenticatedLayout({ | ||||
|     user, | ||||
|     header, | ||||
|     children, | ||||
| }: PropsWithChildren<{ | ||||
|     user: User; | ||||
|     header?: ReactNode; | ||||
| }>) { | ||||
|     const [isCollapsed, setIsCollapsed] = useState(false); | ||||
| 
 | ||||
|     return ( | ||||
|         <ResizablePanelGroup | ||||
|             direction="horizontal" | ||||
|             className="h-screen w-full md:grid-cols-[220px_1fr] lg:grid-cols-[280px_1fr] fixed" | ||||
|         > | ||||
|             <ResizablePanel | ||||
|                 defaultSize={14} | ||||
|                 minSize={8} | ||||
|                 maxSize={20} | ||||
|                 collapsedSize={3.5} | ||||
|                 collapsible={true} | ||||
|                 onCollapse={() => { | ||||
|                     setIsCollapsed(true); | ||||
|                 }} | ||||
|                 onExpand={() => { | ||||
|                     setIsCollapsed(false); | ||||
|                 }} | ||||
|                 className={cn("min-w-[65px] hidden md:block", { | ||||
|                     "transition-all duration-300 ease-in-out": isCollapsed, | ||||
|                 })} | ||||
|             > | ||||
|                 <Sidebar links={links} isCollapsed={isCollapsed} /> | ||||
|             </ResizablePanel> | ||||
| 
 | ||||
|             <ResizableHandle withHandle className={"hidden md:flex"} /> | ||||
| 
 | ||||
|             <ResizablePanel className="h-full w-full flex flex-col"> | ||||
|                 <header className="flex h-14 items-center gap-4 border-b bg-muted/40 px-4 lg:h-[60px] lg:px-6"> | ||||
|                     <MobileMenu links={links} /> | ||||
| 
 | ||||
|                     <div className="w-full flex-1 flex gap-4 justify-between items-center"> | ||||
|                         <form className="flex-1"> | ||||
|                             <div className="relative"> | ||||
|                                 <Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" /> | ||||
|                                 <Input | ||||
|                                     type="search" | ||||
|                                     placeholder="Search products..." | ||||
|                                     className="w-full appearance-none bg-background pl-8 shadow-none md:w-2/3 lg:w-1/3" | ||||
|                                 /> | ||||
|                             </div> | ||||
|                         </form> | ||||
| 
 | ||||
|                         <div className="space-x-4"> | ||||
|                             <AppearanceDropdown /> | ||||
| 
 | ||||
|                             <DropdownMenu> | ||||
|                                 <DropdownMenuTrigger asChild> | ||||
|                                     <Button | ||||
|                                         variant="secondary" | ||||
|                                         size="icon" | ||||
|                                         className="rounded-full" | ||||
|                                     > | ||||
|                                         <CircleUser className="h-5 w-5" /> | ||||
|                                         <span className="sr-only"> | ||||
|                                             Toggle user menu | ||||
|                                         </span> | ||||
|                                     </Button> | ||||
|                                 </DropdownMenuTrigger> | ||||
|                                 <DropdownMenuContent | ||||
|                                     className="w-56" | ||||
|                                     align="end" | ||||
|                                 > | ||||
|                                     <DropdownMenuLabel className="font-normal"> | ||||
|                                         <div className="flex flex-col space-y-1"> | ||||
|                                             <p className="text-sm font-medium leading-none"> | ||||
|                                                 {user.name} | ||||
|                                             </p> | ||||
|                                             <p className="text-xs leading-none text-muted-foreground"> | ||||
|                                                 {user.email} | ||||
|                                             </p> | ||||
|                                         </div> | ||||
|                                     </DropdownMenuLabel> | ||||
|                                     <DropdownMenuSeparator /> | ||||
|                                     <DropdownMenuItem asChild> | ||||
|                                         <Link | ||||
|                                             className="cursor-pointer" | ||||
|                                             href={route("profile.edit")} | ||||
|                                         > | ||||
|                                             Profile | ||||
|                                         </Link> | ||||
|                                     </DropdownMenuItem> | ||||
|                                     <DropdownMenuSeparator /> | ||||
|                                     <DropdownMenuItem asChild> | ||||
|                                         <Link | ||||
|                                             className="cursor-pointer w-full" | ||||
|                                             href={route("logout")} | ||||
|                                             method={"post"} | ||||
|                                             as={"button"} | ||||
|                                         > | ||||
|                                             Log out | ||||
|                                         </Link> | ||||
|                                     </DropdownMenuItem> | ||||
|                                 </DropdownMenuContent> | ||||
|                             </DropdownMenu> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </header> | ||||
| 
 | ||||
|                 <ScrollArea className="px-6 pt-6 flex-1"> | ||||
|                     <div className="pb-4">{header}</div> | ||||
| 
 | ||||
|                     <div className="mb-6">{children}</div> | ||||
|                 </ScrollArea> | ||||
|             </ResizablePanel> | ||||
|         </ResizablePanelGroup> | ||||
|     ); | ||||
| } | ||||
|  | @ -1,20 +0,0 @@ | |||
| import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout"; | ||||
| import { Head } from "@inertiajs/react"; | ||||
| import { PageProps } from "@/types"; | ||||
| 
 | ||||
| export default function Dashboard({ auth }: PageProps) { | ||||
|     return ( | ||||
|         <AuthenticatedLayout | ||||
|             user={auth.user} | ||||
|             header={ | ||||
|                 <h2 className="font-semibold text-xl leading-tight"> | ||||
|                     Dashboard | ||||
|                 </h2> | ||||
|             } | ||||
|         > | ||||
|             <Head title="Dashboard" /> | ||||
| 
 | ||||
|             <div className="flex flex-col gap-4 md:gap-8"></div> | ||||
|         </AuthenticatedLayout> | ||||
|     ); | ||||
| } | ||||
|  | @ -4,7 +4,7 @@ import "../css/app.css"; | |||
| import { createRoot } from "react-dom/client"; | ||||
| import { createInertiaApp } from "@inertiajs/react"; | ||||
| import { resolvePageComponent } from "laravel-vite-plugin/inertia-helpers"; | ||||
| import { ThemeProvider } from "@/Components/ThemeProvider"; | ||||
| import { ThemeProvider } from "@/components/theme-provider"; | ||||
| 
 | ||||
| const appName = import.meta.env.VITE_APP_NAME || "Laravel"; | ||||
| 
 | ||||
|  | @ -12,8 +12,8 @@ createInertiaApp({ | |||
|     title: (title) => `${title} - ${appName}`, | ||||
|     resolve: (name) => | ||||
|         resolvePageComponent( | ||||
|             `./Pages/${name}.tsx`, | ||||
|             import.meta.glob("./Pages/**/*.tsx") | ||||
|             `./pages/${name}.tsx`, | ||||
|             import.meta.glob("./pages/**/*.tsx") | ||||
|         ), | ||||
|     setup({ el, App, props }) { | ||||
|         const root = createRoot(el); | ||||
|  |  | |||
|  | @ -0,0 +1,102 @@ | |||
| "use client" | ||||
| 
 | ||||
| import * as React from "react" | ||||
| import { | ||||
|   Command, | ||||
|   Frame, Home, | ||||
|   LifeBuoy, | ||||
|   Send, | ||||
|   SquareTerminal, | ||||
| } from "lucide-react" | ||||
| 
 | ||||
| import { NavMain } from "@/components/nav-main" | ||||
| import { NavSecondary } from "@/components/nav-secondary" | ||||
| import { NavUser } from "@/components/nav-user" | ||||
| import { | ||||
|   Sidebar, | ||||
|   SidebarContent, | ||||
|   SidebarFooter, | ||||
|   SidebarHeader, | ||||
|   SidebarMenu, | ||||
|   SidebarMenuButton, | ||||
|   SidebarMenuItem, | ||||
| } from "@/components/ui/sidebar" | ||||
| import {Link, usePage} from "@inertiajs/react"; | ||||
| import {PageProps} from "@/types"; | ||||
| 
 | ||||
| const data = { | ||||
|   user: { | ||||
|     name: "shadcn", | ||||
|     email: "m@example.com", | ||||
|     avatar: "/avatars/shadcn.jpg", | ||||
|   }, | ||||
|   navMain: [ | ||||
|     { | ||||
|       title: "Dashboard", | ||||
|       url: "/dashboard", | ||||
|       icon: Home, | ||||
|     }, | ||||
|     { | ||||
|       title: "Projects", | ||||
|       url: "#", | ||||
|       icon: SquareTerminal, | ||||
|       isActive: true, | ||||
|       items: [ | ||||
|         { | ||||
|           title: "History", | ||||
|           url: "#", | ||||
|         }, | ||||
|       ], | ||||
|     }, | ||||
|   { | ||||
|       title: "Design Engineering", | ||||
|       url: "#", | ||||
|       icon: Frame, | ||||
|   }, | ||||
|   ], | ||||
|   navSecondary: [ | ||||
|     { | ||||
|       title: "Support", | ||||
|       url: "#", | ||||
|       icon: LifeBuoy, | ||||
|     }, | ||||
|     { | ||||
|       title: "Feedback", | ||||
|       url: "#", | ||||
|       icon: Send, | ||||
|     }, | ||||
|   ], | ||||
| } | ||||
| 
 | ||||
| export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) { | ||||
|     const { auth } = usePage<PageProps>().props; | ||||
| 
 | ||||
|     return ( | ||||
|     <Sidebar variant="inset" {...props}> | ||||
|       <SidebarHeader> | ||||
|         <SidebarMenu> | ||||
|           <SidebarMenuItem> | ||||
|             <SidebarMenuButton size="lg" asChild> | ||||
|               <Link href={route('dashboard')}> | ||||
|                 <div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground"> | ||||
|                   <Command className="size-4" /> | ||||
|                 </div> | ||||
|                 <div className="grid flex-1 text-left text-sm leading-tight"> | ||||
|                   <span className="truncate font-semibold">Acme Inc</span> | ||||
|                   <span className="truncate text-xs">Enterprise</span> | ||||
|                 </div> | ||||
|               </Link> | ||||
|             </SidebarMenuButton> | ||||
|           </SidebarMenuItem> | ||||
|         </SidebarMenu> | ||||
|       </SidebarHeader> | ||||
|       <SidebarContent> | ||||
|         <NavMain items={data.navMain} /> | ||||
|         <NavSecondary items={data.navSecondary} className="mt-auto" /> | ||||
|       </SidebarContent> | ||||
|       <SidebarFooter> | ||||
|         <NavUser user={auth.user} /> | ||||
|       </SidebarFooter> | ||||
|     </Sidebar> | ||||
|   ) | ||||
| } | ||||
|  | @ -1,22 +1,21 @@ | |||
| import { | ||||
|     DropdownMenu, | ||||
|     DropdownMenuContent, | ||||
|     DropdownMenuItem, | ||||
|     DropdownMenuTrigger, | ||||
| } from "@/Components/ui/dropdown-menu"; | ||||
| import { Button } from "@/Components/ui/button"; | ||||
| import { Moon, Sun } from "lucide-react"; | ||||
| import { useTheme } from "next-themes"; | ||||
| import {DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger,} from "@/components/ui/dropdown-menu"; | ||||
| import {Button} from "@/components/ui/button"; | ||||
| import {Moon, Sun} from "lucide-react"; | ||||
| import {useTheme} from "next-themes"; | ||||
| import {cn} from "@/lib/utils"; | ||||
| 
 | ||||
| const AppearanceDropdown = () => { | ||||
|     const { setTheme } = useTheme(); | ||||
|     const {setTheme, theme} = useTheme(); | ||||
| 
 | ||||
|     return ( | ||||
|         <DropdownMenu> | ||||
|             <DropdownMenuTrigger asChild> | ||||
|                 <Button variant="outline" size="icon"> | ||||
|                     <Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" /> | ||||
|                     <Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" /> | ||||
|                 <Button | ||||
|                     variant="ghost" | ||||
|                     size="icon" | ||||
|                     className={cn("h-7 w-7")} | ||||
|                 > | ||||
|                     {theme == 'light' ? <Sun/> : <Moon/>} | ||||
|                     <span className="sr-only">Toggle theme</span> | ||||
|                 </Button> | ||||
|             </DropdownMenuTrigger> | ||||
|  | @ -0,0 +1,79 @@ | |||
| "use client" | ||||
| 
 | ||||
| import { ChevronRight, type LucideIcon } from "lucide-react" | ||||
| 
 | ||||
| import { | ||||
|   Collapsible, | ||||
|   CollapsibleContent, | ||||
|   CollapsibleTrigger, | ||||
| } from "@/components/ui/collapsible" | ||||
| import { | ||||
|   SidebarGroup, | ||||
|   SidebarGroupLabel, | ||||
|   SidebarMenu, | ||||
|   SidebarMenuAction, | ||||
|   SidebarMenuButton, | ||||
|   SidebarMenuItem, | ||||
|   SidebarMenuSub, | ||||
|   SidebarMenuSubButton, | ||||
|   SidebarMenuSubItem, | ||||
| } from "@/components/ui/sidebar" | ||||
| import {Link} from "@inertiajs/react"; | ||||
| 
 | ||||
| export function NavMain({ | ||||
|   items, | ||||
| }: { | ||||
|   items: { | ||||
|     title: string | ||||
|     url: string | ||||
|     icon: LucideIcon | ||||
|     isActive?: boolean | ||||
|     items?: { | ||||
|       title: string | ||||
|       url: string | ||||
|     }[] | ||||
|   }[] | ||||
| }) { | ||||
|   return ( | ||||
|     <SidebarGroup> | ||||
|       <SidebarGroupLabel>Navigation</SidebarGroupLabel> | ||||
|       <SidebarMenu> | ||||
|         {items.map((item) => ( | ||||
|           <Collapsible key={item.title} asChild defaultOpen={item.isActive}> | ||||
|             <SidebarMenuItem> | ||||
|               <SidebarMenuButton asChild tooltip={item.title}> | ||||
|                 <a href={item.url}> | ||||
|                   <item.icon /> | ||||
|                   <span>{item.title}</span> | ||||
|                 </a> | ||||
|               </SidebarMenuButton> | ||||
|               {item.items?.length ? ( | ||||
|                 <> | ||||
|                   <CollapsibleTrigger asChild> | ||||
|                     <SidebarMenuAction className="data-[state=open]:rotate-90"> | ||||
|                       <ChevronRight /> | ||||
|                       <span className="sr-only">Toggle</span> | ||||
|                     </SidebarMenuAction> | ||||
|                   </CollapsibleTrigger> | ||||
|                   <CollapsibleContent> | ||||
|                     <SidebarMenuSub> | ||||
|                       {item.items?.map((subItem) => ( | ||||
|                         <SidebarMenuSubItem key={subItem.title}> | ||||
|                           <SidebarMenuSubButton asChild> | ||||
|                             <Link href={subItem.url}> | ||||
|                               <span>{subItem.title}</span> | ||||
|                             </Link> | ||||
|                           </SidebarMenuSubButton> | ||||
|                         </SidebarMenuSubItem> | ||||
|                       ))} | ||||
|                     </SidebarMenuSub> | ||||
|                   </CollapsibleContent> | ||||
|                 </> | ||||
|               ) : null} | ||||
|             </SidebarMenuItem> | ||||
|           </Collapsible> | ||||
|         ))} | ||||
|       </SidebarMenu> | ||||
|     </SidebarGroup> | ||||
|   ) | ||||
| } | ||||
|  | @ -0,0 +1,40 @@ | |||
| import * as React from "react" | ||||
| import { type LucideIcon } from "lucide-react" | ||||
| 
 | ||||
| import { | ||||
|   SidebarGroup, | ||||
|   SidebarGroupContent, | ||||
|   SidebarMenu, | ||||
|   SidebarMenuButton, | ||||
|   SidebarMenuItem, | ||||
| } from "@/components/ui/sidebar" | ||||
| 
 | ||||
| export function NavSecondary({ | ||||
|   items, | ||||
|   ...props | ||||
| }: { | ||||
|   items: { | ||||
|     title: string | ||||
|     url: string | ||||
|     icon: LucideIcon | ||||
|   }[] | ||||
| } & React.ComponentPropsWithoutRef<typeof SidebarGroup>) { | ||||
|   return ( | ||||
|     <SidebarGroup {...props}> | ||||
|       <SidebarGroupContent> | ||||
|         <SidebarMenu> | ||||
|           {items.map((item) => ( | ||||
|             <SidebarMenuItem key={item.title}> | ||||
|               <SidebarMenuButton asChild size="sm"> | ||||
|                 <a href={item.url}> | ||||
|                   <item.icon /> | ||||
|                   <span>{item.title}</span> | ||||
|                 </a> | ||||
|               </SidebarMenuButton> | ||||
|             </SidebarMenuItem> | ||||
|           ))} | ||||
|         </SidebarMenu> | ||||
|       </SidebarGroupContent> | ||||
|     </SidebarGroup> | ||||
|   ) | ||||
| } | ||||
|  | @ -0,0 +1,103 @@ | |||
| "use client" | ||||
| 
 | ||||
| import { | ||||
|   BadgeCheck, | ||||
|   ChevronsUpDown, | ||||
|   LogOut, | ||||
| } from "lucide-react" | ||||
| 
 | ||||
| import { | ||||
|   Avatar, | ||||
|   AvatarFallback, | ||||
| } from "@/components/ui/avatar" | ||||
| import { | ||||
|   DropdownMenu, | ||||
|   DropdownMenuContent, | ||||
|   DropdownMenuGroup, | ||||
|   DropdownMenuItem, | ||||
|   DropdownMenuLabel, | ||||
|   DropdownMenuSeparator, | ||||
|   DropdownMenuTrigger, | ||||
| } from "@/components/ui/dropdown-menu" | ||||
| import { | ||||
|   SidebarMenu, | ||||
|   SidebarMenuButton, | ||||
|   SidebarMenuItem, | ||||
|   useSidebar, | ||||
| } from "@/components/ui/sidebar" | ||||
| import {User} from "@/types"; | ||||
| import {Link} from "@inertiajs/react"; | ||||
| 
 | ||||
| export function NavUser({ | ||||
|   user, | ||||
| }: { | ||||
|   user: User | ||||
| }) { | ||||
|   const { isMobile } = useSidebar() | ||||
| 
 | ||||
|   return ( | ||||
|     <SidebarMenu> | ||||
|       <SidebarMenuItem> | ||||
|         <DropdownMenu> | ||||
|           <DropdownMenuTrigger asChild> | ||||
|             <SidebarMenuButton | ||||
|               size="lg" | ||||
|               className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground" | ||||
|             > | ||||
|               <Avatar className="h-8 w-8 rounded-lg"> | ||||
|                 {/*<AvatarImage src={user.avatar} alt={user.name} />*/} | ||||
|                 <AvatarFallback className="rounded-lg">CN</AvatarFallback> | ||||
|               </Avatar> | ||||
|               <div className="grid flex-1 text-left text-sm leading-tight"> | ||||
|                 <span className="truncate font-semibold">{user.name}</span> | ||||
|                 <span className="truncate text-xs">{user.email}</span> | ||||
|               </div> | ||||
|               <ChevronsUpDown className="ml-auto size-4" /> | ||||
|             </SidebarMenuButton> | ||||
|           </DropdownMenuTrigger> | ||||
|           <DropdownMenuContent | ||||
|             className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg" | ||||
|             side={isMobile ? "bottom" : "right"} | ||||
|             align="end" | ||||
|             sideOffset={4} | ||||
|           > | ||||
|             <DropdownMenuLabel className="p-0 font-normal"> | ||||
|               <div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm"> | ||||
|                 <Avatar className="h-8 w-8 rounded-lg"> | ||||
|                   {/*<AvatarImage src={user.avatar} alt={user.name} />*/} | ||||
|                   <AvatarFallback className="rounded-lg">AC</AvatarFallback> | ||||
|                 </Avatar> | ||||
|                 <div className="grid flex-1 text-left text-sm leading-tight"> | ||||
|                   <span className="truncate font-semibold">{user.name}</span> | ||||
|                   <span className="truncate text-xs">{user.email}</span> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </DropdownMenuLabel> | ||||
|             <DropdownMenuSeparator /> | ||||
|             <DropdownMenuGroup> | ||||
|               <DropdownMenuItem asChild> | ||||
|                 <Link href={route('profile.edit')}> | ||||
|                   <BadgeCheck /> | ||||
|                   Edit Profile | ||||
|                 </Link> | ||||
|               </DropdownMenuItem> | ||||
|             </DropdownMenuGroup> | ||||
|             <DropdownMenuSeparator /> | ||||
| 
 | ||||
|             <DropdownMenuItem asChild> | ||||
|               <Link | ||||
|                   className="w-full" | ||||
|                   href={route("logout")} | ||||
|                   method={"post"} | ||||
|                   as={"button"} | ||||
|               > | ||||
|                 <LogOut /> | ||||
|                 Log out | ||||
|               </Link> | ||||
|             </DropdownMenuItem> | ||||
|           </DropdownMenuContent> | ||||
|         </DropdownMenu> | ||||
|       </SidebarMenuItem> | ||||
|     </SidebarMenu> | ||||
|   ) | ||||
| } | ||||
|  | @ -4,7 +4,7 @@ import * as React from "react" | |||
| import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" | ||||
| 
 | ||||
| import { cn } from "@/lib/utils" | ||||
| import { buttonVariants } from "@/Components/ui/button" | ||||
| import { buttonVariants } from "@/components/ui/button" | ||||
| import {VariantProps} from "class-variance-authority"; | ||||
| 
 | ||||
| const AlertDialog = AlertDialogPrimitive.Root | ||||
|  | @ -0,0 +1,115 @@ | |||
| import * as React from "react" | ||||
| import { Slot } from "@radix-ui/react-slot" | ||||
| import { ChevronRight, MoreHorizontal } from "lucide-react" | ||||
| 
 | ||||
| import { cn } from "@/lib/utils" | ||||
| 
 | ||||
| const Breadcrumb = React.forwardRef< | ||||
|   HTMLElement, | ||||
|   React.ComponentPropsWithoutRef<"nav"> & { | ||||
|     separator?: React.ReactNode | ||||
|   } | ||||
| >(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />) | ||||
| Breadcrumb.displayName = "Breadcrumb" | ||||
| 
 | ||||
| const BreadcrumbList = React.forwardRef< | ||||
|   HTMLOListElement, | ||||
|   React.ComponentPropsWithoutRef<"ol"> | ||||
| >(({ className, ...props }, ref) => ( | ||||
|   <ol | ||||
|     ref={ref} | ||||
|     className={cn( | ||||
|       "flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5", | ||||
|       className | ||||
|     )} | ||||
|     {...props} | ||||
|   /> | ||||
| )) | ||||
| BreadcrumbList.displayName = "BreadcrumbList" | ||||
| 
 | ||||
| const BreadcrumbItem = React.forwardRef< | ||||
|   HTMLLIElement, | ||||
|   React.ComponentPropsWithoutRef<"li"> | ||||
| >(({ className, ...props }, ref) => ( | ||||
|   <li | ||||
|     ref={ref} | ||||
|     className={cn("inline-flex items-center gap-1.5", className)} | ||||
|     {...props} | ||||
|   /> | ||||
| )) | ||||
| BreadcrumbItem.displayName = "BreadcrumbItem" | ||||
| 
 | ||||
| const BreadcrumbLink = React.forwardRef< | ||||
|   HTMLAnchorElement, | ||||
|   React.ComponentPropsWithoutRef<"a"> & { | ||||
|     asChild?: boolean | ||||
|   } | ||||
| >(({ asChild, className, ...props }, ref) => { | ||||
|   const Comp = asChild ? Slot : "a" | ||||
| 
 | ||||
|   return ( | ||||
|     <Comp | ||||
|       ref={ref} | ||||
|       className={cn("transition-colors hover:text-foreground", className)} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| }) | ||||
| BreadcrumbLink.displayName = "BreadcrumbLink" | ||||
| 
 | ||||
| const BreadcrumbPage = React.forwardRef< | ||||
|   HTMLSpanElement, | ||||
|   React.ComponentPropsWithoutRef<"span"> | ||||
| >(({ className, ...props }, ref) => ( | ||||
|   <span | ||||
|     ref={ref} | ||||
|     role="link" | ||||
|     aria-disabled="true" | ||||
|     aria-current="page" | ||||
|     className={cn("font-normal text-foreground", className)} | ||||
|     {...props} | ||||
|   /> | ||||
| )) | ||||
| BreadcrumbPage.displayName = "BreadcrumbPage" | ||||
| 
 | ||||
| const BreadcrumbSeparator = ({ | ||||
|   children, | ||||
|   className, | ||||
|   ...props | ||||
| }: React.ComponentProps<"li">) => ( | ||||
|   <li | ||||
|     role="presentation" | ||||
|     aria-hidden="true" | ||||
|     className={cn("[&>svg]:w-3.5 [&>svg]:h-3.5", className)} | ||||
|     {...props} | ||||
|   > | ||||
|     {children ?? <ChevronRight />} | ||||
|   </li> | ||||
| ) | ||||
| BreadcrumbSeparator.displayName = "BreadcrumbSeparator" | ||||
| 
 | ||||
| const BreadcrumbEllipsis = ({ | ||||
|   className, | ||||
|   ...props | ||||
| }: React.ComponentProps<"span">) => ( | ||||
|   <span | ||||
|     role="presentation" | ||||
|     aria-hidden="true" | ||||
|     className={cn("flex h-9 w-9 items-center justify-center", className)} | ||||
|     {...props} | ||||
|   > | ||||
|     <MoreHorizontal className="h-4 w-4" /> | ||||
|     <span className="sr-only">More</span> | ||||
|   </span> | ||||
| ) | ||||
| BreadcrumbEllipsis.displayName = "BreadcrumbElipssis" | ||||
| 
 | ||||
| export { | ||||
|   Breadcrumb, | ||||
|   BreadcrumbList, | ||||
|   BreadcrumbItem, | ||||
|   BreadcrumbLink, | ||||
|   BreadcrumbPage, | ||||
|   BreadcrumbSeparator, | ||||
|   BreadcrumbEllipsis, | ||||
| } | ||||
|  | @ -5,7 +5,7 @@ import { cva, type VariantProps } from "class-variance-authority" | |||
| import { cn } from "@/lib/utils" | ||||
| 
 | ||||
| const buttonVariants = cva( | ||||
|   "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", | ||||
|   "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", | ||||
|   { | ||||
|     variants: { | ||||
|       variant: { | ||||
|  | @ -0,0 +1,11 @@ | |||
| "use client" | ||||
| 
 | ||||
| import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" | ||||
| 
 | ||||
| const Collapsible = CollapsiblePrimitive.Root | ||||
| 
 | ||||
| const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger | ||||
| 
 | ||||
| const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent | ||||
| 
 | ||||
| export { Collapsible, CollapsibleTrigger, CollapsibleContent } | ||||
|  | @ -83,7 +83,7 @@ const DropdownMenuItem = React.forwardRef< | |||
|   <DropdownMenuPrimitive.Item | ||||
|     ref={ref} | ||||
|     className={cn( | ||||
|       "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", | ||||
|       "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", | ||||
|       inset && "pl-8", | ||||
|       className | ||||
|     )} | ||||
|  | @ -11,7 +11,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>( | |||
|       <input | ||||
|         type={type} | ||||
|         className={cn( | ||||
|           "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", | ||||
|           "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", | ||||
|           className | ||||
|         )} | ||||
|         ref={ref} | ||||
|  | @ -0,0 +1,31 @@ | |||
| "use client" | ||||
| 
 | ||||
| import * as React from "react" | ||||
| import * as SeparatorPrimitive from "@radix-ui/react-separator" | ||||
| 
 | ||||
| import { cn } from "@/lib/utils" | ||||
| 
 | ||||
| const Separator = React.forwardRef< | ||||
|   React.ElementRef<typeof SeparatorPrimitive.Root>, | ||||
|   React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root> | ||||
| >( | ||||
|   ( | ||||
|     { className, orientation = "horizontal", decorative = true, ...props }, | ||||
|     ref | ||||
|   ) => ( | ||||
|     <SeparatorPrimitive.Root | ||||
|       ref={ref} | ||||
|       decorative={decorative} | ||||
|       orientation={orientation} | ||||
|       className={cn( | ||||
|         "shrink-0 bg-border", | ||||
|         orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]", | ||||
|         className | ||||
|       )} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| ) | ||||
| Separator.displayName = SeparatorPrimitive.Root.displayName | ||||
| 
 | ||||
| export { Separator } | ||||
|  | @ -0,0 +1,764 @@ | |||
| "use client" | ||||
| 
 | ||||
| import * as React from "react" | ||||
| import { Slot } from "@radix-ui/react-slot" | ||||
| import { VariantProps, cva } from "class-variance-authority" | ||||
| import { PanelLeft } from "lucide-react" | ||||
| 
 | ||||
| import { useIsMobile } from "@/hooks/use-mobile" | ||||
| import { cn } from "@/lib/utils" | ||||
| import { Button } from "@/components/ui/button" | ||||
| import { Input } from "@/components/ui/input" | ||||
| import { Separator } from "@/components/ui/separator" | ||||
| import { Sheet, SheetContent } from "@/components/ui/sheet" | ||||
| import { Skeleton } from "@/components/ui/skeleton" | ||||
| import { | ||||
|   Tooltip, | ||||
|   TooltipContent, | ||||
|   TooltipProvider, | ||||
|   TooltipTrigger, | ||||
| } from "@/components/ui/tooltip" | ||||
| 
 | ||||
| const SIDEBAR_COOKIE_NAME = "sidebar:state" | ||||
| const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7 | ||||
| const SIDEBAR_WIDTH = "16rem" | ||||
| const SIDEBAR_WIDTH_MOBILE = "18rem" | ||||
| const SIDEBAR_WIDTH_ICON = "3rem" | ||||
| const SIDEBAR_KEYBOARD_SHORTCUT = "b" | ||||
| 
 | ||||
| type SidebarContext = { | ||||
|   state: "expanded" | "collapsed" | ||||
|   open: boolean | ||||
|   setOpen: (open: boolean) => void | ||||
|   openMobile: boolean | ||||
|   setOpenMobile: (open: boolean) => void | ||||
|   isMobile: boolean | ||||
|   toggleSidebar: () => void | ||||
| } | ||||
| 
 | ||||
| const SidebarContext = React.createContext<SidebarContext | null>(null) | ||||
| 
 | ||||
| function useSidebar() { | ||||
|   const context = React.useContext(SidebarContext) | ||||
|   if (!context) { | ||||
|     throw new Error("useSidebar must be used within a Sidebar.") | ||||
|   } | ||||
| 
 | ||||
|   return context | ||||
| } | ||||
| 
 | ||||
| const SidebarProvider = React.forwardRef< | ||||
|   HTMLDivElement, | ||||
|   React.ComponentProps<"div"> & { | ||||
|     defaultOpen?: boolean | ||||
|     open?: boolean | ||||
|     onOpenChange?: (open: boolean) => void | ||||
|   } | ||||
| >( | ||||
|   ( | ||||
|     { | ||||
|       defaultOpen = true, | ||||
|       open: openProp, | ||||
|       onOpenChange: setOpenProp, | ||||
|       className, | ||||
|       style, | ||||
|       children, | ||||
|       ...props | ||||
|     }, | ||||
|     ref | ||||
|   ) => { | ||||
|     const isMobile = useIsMobile() | ||||
|     const [openMobile, setOpenMobile] = React.useState(false) | ||||
| 
 | ||||
|     // This is the internal state of the sidebar.
 | ||||
|     // We use openProp and setOpenProp for control from outside the component.
 | ||||
|     const [_open, _setOpen] = React.useState(defaultOpen) | ||||
|     const open = openProp ?? _open | ||||
|     const setOpen = React.useCallback( | ||||
|       (value: boolean | ((value: boolean) => boolean)) => { | ||||
|         if (setOpenProp) { | ||||
|           return setOpenProp?.( | ||||
|             typeof value === "function" ? value(open) : value | ||||
|           ) | ||||
|         } | ||||
| 
 | ||||
|         _setOpen(value) | ||||
| 
 | ||||
|         // This sets the cookie to keep the sidebar state.
 | ||||
|         document.cookie = `${SIDEBAR_COOKIE_NAME}=${open}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}` | ||||
|       }, | ||||
|       [setOpenProp, open] | ||||
|     ) | ||||
| 
 | ||||
|     // Helper to toggle the sidebar.
 | ||||
|     const toggleSidebar = React.useCallback(() => { | ||||
|       return isMobile | ||||
|         ? setOpenMobile((open) => !open) | ||||
|         : setOpen((open) => !open) | ||||
|     }, [isMobile, setOpen, setOpenMobile]) | ||||
| 
 | ||||
|     // Adds a keyboard shortcut to toggle the sidebar.
 | ||||
|     React.useEffect(() => { | ||||
|       const handleKeyDown = (event: KeyboardEvent) => { | ||||
|         if ( | ||||
|           event.key === SIDEBAR_KEYBOARD_SHORTCUT && | ||||
|           (event.metaKey || event.ctrlKey) | ||||
|         ) { | ||||
|           event.preventDefault() | ||||
|           toggleSidebar() | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       window.addEventListener("keydown", handleKeyDown) | ||||
|       return () => window.removeEventListener("keydown", handleKeyDown) | ||||
|     }, [toggleSidebar]) | ||||
| 
 | ||||
|     // We add a state so that we can do data-state="expanded" or "collapsed".
 | ||||
|     // This makes it easier to style the sidebar with Tailwind classes.
 | ||||
|     const state = open ? "expanded" : "collapsed" | ||||
| 
 | ||||
|     const contextValue = React.useMemo<SidebarContext>( | ||||
|       () => ({ | ||||
|         state, | ||||
|         open, | ||||
|         setOpen, | ||||
|         isMobile, | ||||
|         openMobile, | ||||
|         setOpenMobile, | ||||
|         toggleSidebar, | ||||
|       }), | ||||
|       [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar] | ||||
|     ) | ||||
| 
 | ||||
|     return ( | ||||
|       <SidebarContext.Provider value={contextValue}> | ||||
|         <TooltipProvider delayDuration={0}> | ||||
|           <div | ||||
|             style={ | ||||
|               { | ||||
|                 "--sidebar-width": SIDEBAR_WIDTH, | ||||
|                 "--sidebar-width-icon": SIDEBAR_WIDTH_ICON, | ||||
|                 ...style, | ||||
|               } as React.CSSProperties | ||||
|             } | ||||
|             className={cn( | ||||
|               "group/sidebar-wrapper flex min-h-svh w-full text-sidebar-foreground has-[[data-variant=inset]]:bg-sidebar", | ||||
|               className | ||||
|             )} | ||||
|             ref={ref} | ||||
|             {...props} | ||||
|           > | ||||
|             {children} | ||||
|           </div> | ||||
|         </TooltipProvider> | ||||
|       </SidebarContext.Provider> | ||||
|     ) | ||||
|   } | ||||
| ) | ||||
| SidebarProvider.displayName = "SidebarProvider" | ||||
| 
 | ||||
| const Sidebar = React.forwardRef< | ||||
|   HTMLDivElement, | ||||
|   React.ComponentProps<"div"> & { | ||||
|     side?: "left" | "right" | ||||
|     variant?: "sidebar" | "floating" | "inset" | ||||
|     collapsible?: "offcanvas" | "icon" | "none" | ||||
|   } | ||||
| >( | ||||
|   ( | ||||
|     { | ||||
|       side = "left", | ||||
|       variant = "sidebar", | ||||
|       collapsible = "offcanvas", | ||||
|       className, | ||||
|       children, | ||||
|       ...props | ||||
|     }, | ||||
|     ref | ||||
|   ) => { | ||||
|     const { isMobile, state, openMobile, setOpenMobile } = useSidebar() | ||||
| 
 | ||||
|     if (collapsible === "none") { | ||||
|       return ( | ||||
|         <div | ||||
|           className={cn( | ||||
|             "flex h-full w-[--sidebar-width] flex-col bg-sidebar text-sidebar-foreground", | ||||
|             className | ||||
|           )} | ||||
|           ref={ref} | ||||
|           {...props} | ||||
|         > | ||||
|           {children} | ||||
|         </div> | ||||
|       ) | ||||
|     } | ||||
| 
 | ||||
|     if (isMobile) { | ||||
|       return ( | ||||
|         <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}> | ||||
|           <SheetContent | ||||
|             data-sidebar="sidebar" | ||||
|             data-mobile="true" | ||||
|             className="w-[--sidebar-width] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden" | ||||
|             style={ | ||||
|               { | ||||
|                 "--sidebar-width": SIDEBAR_WIDTH_MOBILE, | ||||
|               } as React.CSSProperties | ||||
|             } | ||||
|             side={side} | ||||
|           > | ||||
|             <div className="flex h-full w-full flex-col">{children}</div> | ||||
|           </SheetContent> | ||||
|         </Sheet> | ||||
|       ) | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <div | ||||
|         ref={ref} | ||||
|         className="group peer hidden md:block" | ||||
|         data-state={state} | ||||
|         data-collapsible={state === "collapsed" ? collapsible : ""} | ||||
|         data-variant={variant} | ||||
|         data-side={side} | ||||
|       > | ||||
|         {/* This is what handles the sidebar gap on desktop */} | ||||
|         <div | ||||
|           className={cn( | ||||
|             "duration-200 relative h-svh w-[--sidebar-width] bg-transparent transition-[width] ease-linear", | ||||
|             "group-data-[collapsible=offcanvas]:w-0", | ||||
|             "group-data-[side=right]:rotate-180", | ||||
|             variant === "floating" || variant === "inset" | ||||
|               ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]" | ||||
|               : "group-data-[collapsible=icon]:w-[--sidebar-width-icon]" | ||||
|           )} | ||||
|         /> | ||||
|         <div | ||||
|           className={cn( | ||||
|             "duration-200 fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width] transition-[left,right,width] ease-linear md:flex", | ||||
|             side === "left" | ||||
|               ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]" | ||||
|               : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]", | ||||
|             // Adjust the padding for floating and inset variants.
 | ||||
|             variant === "floating" || variant === "inset" | ||||
|               ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]" | ||||
|               : "group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l", | ||||
|             className | ||||
|           )} | ||||
|           {...props} | ||||
|         > | ||||
|           <div | ||||
|             data-sidebar="sidebar" | ||||
|             className="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow" | ||||
|           > | ||||
|             {children} | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     ) | ||||
|   } | ||||
| ) | ||||
| Sidebar.displayName = "Sidebar" | ||||
| 
 | ||||
| const SidebarTrigger = React.forwardRef< | ||||
|   React.ElementRef<typeof Button>, | ||||
|   React.ComponentProps<typeof Button> | ||||
| >(({ className, onClick, ...props }, ref) => { | ||||
|   const { toggleSidebar } = useSidebar() | ||||
| 
 | ||||
|   return ( | ||||
|     <Button | ||||
|       ref={ref} | ||||
|       data-sidebar="trigger" | ||||
|       variant="ghost" | ||||
|       size="icon" | ||||
|       className={cn("h-7 w-7", className)} | ||||
|       onClick={(event) => { | ||||
|         onClick?.(event) | ||||
|         toggleSidebar() | ||||
|       }} | ||||
|       {...props} | ||||
|     > | ||||
|       <PanelLeft /> | ||||
|       <span className="sr-only">Toggle Sidebar</span> | ||||
|     </Button> | ||||
|   ) | ||||
| }) | ||||
| SidebarTrigger.displayName = "SidebarTrigger" | ||||
| 
 | ||||
| const SidebarRail = React.forwardRef< | ||||
|   HTMLButtonElement, | ||||
|   React.ComponentProps<"button"> | ||||
| >(({ className, ...props }, ref) => { | ||||
|   const { toggleSidebar } = useSidebar() | ||||
| 
 | ||||
|   return ( | ||||
|     <button | ||||
|       ref={ref} | ||||
|       data-sidebar="rail" | ||||
|       aria-label="Toggle Sidebar" | ||||
|       tabIndex={-1} | ||||
|       onClick={toggleSidebar} | ||||
|       title="Toggle Sidebar" | ||||
|       className={cn( | ||||
|         "absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex", | ||||
|         "[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize", | ||||
|         "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize", | ||||
|         "group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar", | ||||
|         "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2", | ||||
|         "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2", | ||||
|         className | ||||
|       )} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| }) | ||||
| SidebarRail.displayName = "SidebarRail" | ||||
| 
 | ||||
| const SidebarInset = React.forwardRef< | ||||
|   HTMLDivElement, | ||||
|   React.ComponentProps<"main"> | ||||
| >(({ className, ...props }, ref) => { | ||||
|   return ( | ||||
|     <main | ||||
|       ref={ref} | ||||
|       className={cn( | ||||
|         "relative flex min-h-svh flex-1 flex-col bg-background", | ||||
|         "peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow", | ||||
|         className | ||||
|       )} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| }) | ||||
| SidebarInset.displayName = "SidebarInset" | ||||
| 
 | ||||
| const SidebarInput = React.forwardRef< | ||||
|   React.ElementRef<typeof Input>, | ||||
|   React.ComponentProps<typeof Input> | ||||
| >(({ className, ...props }, ref) => { | ||||
|   return ( | ||||
|     <Input | ||||
|       ref={ref} | ||||
|       data-sidebar="input" | ||||
|       className={cn( | ||||
|         "h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring", | ||||
|         className | ||||
|       )} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| }) | ||||
| SidebarInput.displayName = "SidebarInput" | ||||
| 
 | ||||
| const SidebarHeader = React.forwardRef< | ||||
|   HTMLDivElement, | ||||
|   React.ComponentProps<"div"> | ||||
| >(({ className, ...props }, ref) => { | ||||
|   return ( | ||||
|     <div | ||||
|       ref={ref} | ||||
|       data-sidebar="header" | ||||
|       className={cn("flex flex-col gap-2 p-2", className)} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| }) | ||||
| SidebarHeader.displayName = "SidebarHeader" | ||||
| 
 | ||||
| const SidebarFooter = React.forwardRef< | ||||
|   HTMLDivElement, | ||||
|   React.ComponentProps<"div"> | ||||
| >(({ className, ...props }, ref) => { | ||||
|   return ( | ||||
|     <div | ||||
|       ref={ref} | ||||
|       data-sidebar="footer" | ||||
|       className={cn("flex flex-col gap-2 p-2", className)} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| }) | ||||
| SidebarFooter.displayName = "SidebarFooter" | ||||
| 
 | ||||
| const SidebarSeparator = React.forwardRef< | ||||
|   React.ElementRef<typeof Separator>, | ||||
|   React.ComponentProps<typeof Separator> | ||||
| >(({ className, ...props }, ref) => { | ||||
|   return ( | ||||
|     <Separator | ||||
|       ref={ref} | ||||
|       data-sidebar="separator" | ||||
|       className={cn("mx-2 w-auto bg-sidebar-border", className)} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| }) | ||||
| SidebarSeparator.displayName = "SidebarSeparator" | ||||
| 
 | ||||
| const SidebarContent = React.forwardRef< | ||||
|   HTMLDivElement, | ||||
|   React.ComponentProps<"div"> | ||||
| >(({ className, ...props }, ref) => { | ||||
|   return ( | ||||
|     <div | ||||
|       ref={ref} | ||||
|       data-sidebar="content" | ||||
|       className={cn( | ||||
|         "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden", | ||||
|         className | ||||
|       )} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| }) | ||||
| SidebarContent.displayName = "SidebarContent" | ||||
| 
 | ||||
| const SidebarGroup = React.forwardRef< | ||||
|   HTMLDivElement, | ||||
|   React.ComponentProps<"div"> | ||||
| >(({ className, ...props }, ref) => { | ||||
|   return ( | ||||
|     <div | ||||
|       ref={ref} | ||||
|       data-sidebar="group" | ||||
|       className={cn("relative flex w-full min-w-0 flex-col p-2", className)} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| }) | ||||
| SidebarGroup.displayName = "SidebarGroup" | ||||
| 
 | ||||
| const SidebarGroupLabel = React.forwardRef< | ||||
|   HTMLDivElement, | ||||
|   React.ComponentProps<"div"> & { asChild?: boolean } | ||||
| >(({ className, asChild = false, ...props }, ref) => { | ||||
|   const Comp = asChild ? Slot : "div" | ||||
| 
 | ||||
|   return ( | ||||
|     <Comp | ||||
|       ref={ref} | ||||
|       data-sidebar="group-label" | ||||
|       className={cn( | ||||
|         "duration-200 flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opa] ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", | ||||
|         "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0", | ||||
|         className | ||||
|       )} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| }) | ||||
| SidebarGroupLabel.displayName = "SidebarGroupLabel" | ||||
| 
 | ||||
| const SidebarGroupAction = React.forwardRef< | ||||
|   HTMLButtonElement, | ||||
|   React.ComponentProps<"button"> & { asChild?: boolean } | ||||
| >(({ className, asChild = false, ...props }, ref) => { | ||||
|   const Comp = asChild ? Slot : "button" | ||||
| 
 | ||||
|   return ( | ||||
|     <Comp | ||||
|       ref={ref} | ||||
|       data-sidebar="group-action" | ||||
|       className={cn( | ||||
|         "absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", | ||||
|         // Increases the hit area of the button on mobile.
 | ||||
|         "after:absolute after:-inset-2 after:md:hidden", | ||||
|         "group-data-[collapsible=icon]:hidden", | ||||
|         className | ||||
|       )} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| }) | ||||
| SidebarGroupAction.displayName = "SidebarGroupAction" | ||||
| 
 | ||||
| const SidebarGroupContent = React.forwardRef< | ||||
|   HTMLDivElement, | ||||
|   React.ComponentProps<"div"> | ||||
| >(({ className, ...props }, ref) => ( | ||||
|   <div | ||||
|     ref={ref} | ||||
|     data-sidebar="group-content" | ||||
|     className={cn("w-full text-sm", className)} | ||||
|     {...props} | ||||
|   /> | ||||
| )) | ||||
| SidebarGroupContent.displayName = "SidebarGroupContent" | ||||
| 
 | ||||
| const SidebarMenu = React.forwardRef< | ||||
|   HTMLUListElement, | ||||
|   React.ComponentProps<"ul"> | ||||
| >(({ className, ...props }, ref) => ( | ||||
|   <ul | ||||
|     ref={ref} | ||||
|     data-sidebar="menu" | ||||
|     className={cn("flex w-full min-w-0 flex-col gap-1", className)} | ||||
|     {...props} | ||||
|   /> | ||||
| )) | ||||
| SidebarMenu.displayName = "SidebarMenu" | ||||
| 
 | ||||
| const SidebarMenuItem = React.forwardRef< | ||||
|   HTMLLIElement, | ||||
|   React.ComponentProps<"li"> | ||||
| >(({ className, ...props }, ref) => ( | ||||
|   <li | ||||
|     ref={ref} | ||||
|     data-sidebar="menu-item" | ||||
|     className={cn("group/menu-item relative", className)} | ||||
|     {...props} | ||||
|   /> | ||||
| )) | ||||
| SidebarMenuItem.displayName = "SidebarMenuItem" | ||||
| 
 | ||||
| const sidebarMenuButtonVariants = cva( | ||||
|   "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0", | ||||
|   { | ||||
|     variants: { | ||||
|       variant: { | ||||
|         default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", | ||||
|         outline: | ||||
|           "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]", | ||||
|       }, | ||||
|       size: { | ||||
|         default: "h-8 text-sm", | ||||
|         sm: "h-7 text-xs", | ||||
|         lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0", | ||||
|       }, | ||||
|     }, | ||||
|     defaultVariants: { | ||||
|       variant: "default", | ||||
|       size: "default", | ||||
|     }, | ||||
|   } | ||||
| ) | ||||
| 
 | ||||
| const SidebarMenuButton = React.forwardRef< | ||||
|   HTMLButtonElement, | ||||
|   React.ComponentProps<"button"> & { | ||||
|     asChild?: boolean | ||||
|     isActive?: boolean | ||||
|     tooltip?: string | React.ComponentProps<typeof TooltipContent> | ||||
|   } & VariantProps<typeof sidebarMenuButtonVariants> | ||||
| >( | ||||
|   ( | ||||
|     { | ||||
|       asChild = false, | ||||
|       isActive = false, | ||||
|       variant = "default", | ||||
|       size = "default", | ||||
|       tooltip, | ||||
|       className, | ||||
|       ...props | ||||
|     }, | ||||
|     ref | ||||
|   ) => { | ||||
|     const Comp = asChild ? Slot : "button" | ||||
|     const { isMobile, state } = useSidebar() | ||||
| 
 | ||||
|     const button = ( | ||||
|       <Comp | ||||
|         ref={ref} | ||||
|         data-sidebar="menu-button" | ||||
|         data-size={size} | ||||
|         data-active={isActive} | ||||
|         className={cn(sidebarMenuButtonVariants({ variant, size }), className)} | ||||
|         {...props} | ||||
|       /> | ||||
|     ) | ||||
| 
 | ||||
|     if (!tooltip) { | ||||
|       return button | ||||
|     } | ||||
| 
 | ||||
|     if (typeof tooltip === "string") { | ||||
|       tooltip = { | ||||
|         children: tooltip, | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <Tooltip> | ||||
|         <TooltipTrigger asChild>{button}</TooltipTrigger> | ||||
|         <TooltipContent | ||||
|           side="right" | ||||
|           align="center" | ||||
|           hidden={state !== "collapsed" || isMobile} | ||||
|           {...tooltip} | ||||
|         /> | ||||
|       </Tooltip> | ||||
|     ) | ||||
|   } | ||||
| ) | ||||
| SidebarMenuButton.displayName = "SidebarMenuButton" | ||||
| 
 | ||||
| const SidebarMenuAction = React.forwardRef< | ||||
|   HTMLButtonElement, | ||||
|   React.ComponentProps<"button"> & { | ||||
|     asChild?: boolean | ||||
|     showOnHover?: boolean | ||||
|   } | ||||
| >(({ className, asChild = false, showOnHover = false, ...props }, ref) => { | ||||
|   const Comp = asChild ? Slot : "button" | ||||
| 
 | ||||
|   return ( | ||||
|     <Comp | ||||
|       ref={ref} | ||||
|       data-sidebar="menu-action" | ||||
|       className={cn( | ||||
|         "absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0", | ||||
|         // Increases the hit area of the button on mobile.
 | ||||
|         "after:absolute after:-inset-2 after:md:hidden", | ||||
|         "peer-data-[size=sm]/menu-button:top-1", | ||||
|         "peer-data-[size=default]/menu-button:top-1.5", | ||||
|         "peer-data-[size=lg]/menu-button:top-2.5", | ||||
|         "group-data-[collapsible=icon]:hidden", | ||||
|         showOnHover && | ||||
|           "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0", | ||||
|         className | ||||
|       )} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| }) | ||||
| SidebarMenuAction.displayName = "SidebarMenuAction" | ||||
| 
 | ||||
| const SidebarMenuBadge = React.forwardRef< | ||||
|   HTMLDivElement, | ||||
|   React.ComponentProps<"div"> | ||||
| >(({ className, ...props }, ref) => ( | ||||
|   <div | ||||
|     ref={ref} | ||||
|     data-sidebar="menu-badge" | ||||
|     className={cn( | ||||
|       "absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground select-none pointer-events-none", | ||||
|       "peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground", | ||||
|       "peer-data-[size=sm]/menu-button:top-1", | ||||
|       "peer-data-[size=default]/menu-button:top-1.5", | ||||
|       "peer-data-[size=lg]/menu-button:top-2.5", | ||||
|       "group-data-[collapsible=icon]:hidden", | ||||
|       className | ||||
|     )} | ||||
|     {...props} | ||||
|   /> | ||||
| )) | ||||
| SidebarMenuBadge.displayName = "SidebarMenuBadge" | ||||
| 
 | ||||
| const SidebarMenuSkeleton = React.forwardRef< | ||||
|   HTMLDivElement, | ||||
|   React.ComponentProps<"div"> & { | ||||
|     showIcon?: boolean | ||||
|   } | ||||
| >(({ className, showIcon = false, ...props }, ref) => { | ||||
|   // Random width between 50 to 90%.
 | ||||
|   const width = React.useMemo(() => { | ||||
|     return `${Math.floor(Math.random() * 40) + 50}%` | ||||
|   }, []) | ||||
| 
 | ||||
|   return ( | ||||
|     <div | ||||
|       ref={ref} | ||||
|       data-sidebar="menu-skeleton" | ||||
|       className={cn("rounded-md h-8 flex gap-2 px-2 items-center", className)} | ||||
|       {...props} | ||||
|     > | ||||
|       {showIcon && ( | ||||
|         <Skeleton | ||||
|           className="size-4 rounded-md" | ||||
|           data-sidebar="menu-skeleton-icon" | ||||
|         /> | ||||
|       )} | ||||
|       <Skeleton | ||||
|         className="h-4 flex-1 max-w-[--skeleton-width]" | ||||
|         data-sidebar="menu-skeleton-text" | ||||
|         style={ | ||||
|           { | ||||
|             "--skeleton-width": width, | ||||
|           } as React.CSSProperties | ||||
|         } | ||||
|       /> | ||||
|     </div> | ||||
|   ) | ||||
| }) | ||||
| SidebarMenuSkeleton.displayName = "SidebarMenuSkeleton" | ||||
| 
 | ||||
| const SidebarMenuSub = React.forwardRef< | ||||
|   HTMLUListElement, | ||||
|   React.ComponentProps<"ul"> | ||||
| >(({ className, ...props }, ref) => ( | ||||
|   <ul | ||||
|     ref={ref} | ||||
|     data-sidebar="menu-sub" | ||||
|     className={cn( | ||||
|       "mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5", | ||||
|       "group-data-[collapsible=icon]:hidden", | ||||
|       className | ||||
|     )} | ||||
|     {...props} | ||||
|   /> | ||||
| )) | ||||
| SidebarMenuSub.displayName = "SidebarMenuSub" | ||||
| 
 | ||||
| const SidebarMenuSubItem = React.forwardRef< | ||||
|   HTMLLIElement, | ||||
|   React.ComponentProps<"li"> | ||||
| >(({ ...props }, ref) => <li ref={ref} {...props} />) | ||||
| SidebarMenuSubItem.displayName = "SidebarMenuSubItem" | ||||
| 
 | ||||
| const SidebarMenuSubButton = React.forwardRef< | ||||
|   HTMLAnchorElement, | ||||
|   React.ComponentProps<"a"> & { | ||||
|     asChild?: boolean | ||||
|     size?: "sm" | "md" | ||||
|     isActive?: boolean | ||||
|   } | ||||
| >(({ asChild = false, size = "md", isActive, className, ...props }, ref) => { | ||||
|   const Comp = asChild ? Slot : "a" | ||||
| 
 | ||||
|   return ( | ||||
|     <Comp | ||||
|       ref={ref} | ||||
|       data-sidebar="menu-sub-button" | ||||
|       data-size={size} | ||||
|       data-active={isActive} | ||||
|       className={cn( | ||||
|         "flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-none ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground", | ||||
|         "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground", | ||||
|         size === "sm" && "text-xs", | ||||
|         size === "md" && "text-sm", | ||||
|         "group-data-[collapsible=icon]:hidden", | ||||
|         className | ||||
|       )} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| }) | ||||
| SidebarMenuSubButton.displayName = "SidebarMenuSubButton" | ||||
| 
 | ||||
| export { | ||||
|   Sidebar, | ||||
|   SidebarContent, | ||||
|   SidebarFooter, | ||||
|   SidebarGroup, | ||||
|   SidebarGroupAction, | ||||
|   SidebarGroupContent, | ||||
|   SidebarGroupLabel, | ||||
|   SidebarHeader, | ||||
|   SidebarInput, | ||||
|   SidebarInset, | ||||
|   SidebarMenu, | ||||
|   SidebarMenuAction, | ||||
|   SidebarMenuBadge, | ||||
|   SidebarMenuButton, | ||||
|   SidebarMenuItem, | ||||
|   SidebarMenuSkeleton, | ||||
|   SidebarMenuSub, | ||||
|   SidebarMenuSubButton, | ||||
|   SidebarMenuSubItem, | ||||
|   SidebarProvider, | ||||
|   SidebarRail, | ||||
|   SidebarSeparator, | ||||
|   SidebarTrigger, | ||||
|   useSidebar, | ||||
| } | ||||
|  | @ -0,0 +1,15 @@ | |||
| import { cn } from "@/lib/utils" | ||||
| 
 | ||||
| function Skeleton({ | ||||
|   className, | ||||
|   ...props | ||||
| }: React.HTMLAttributes<HTMLDivElement>) { | ||||
|   return ( | ||||
|     <div | ||||
|       className={cn("animate-pulse rounded-md bg-muted", className)} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| } | ||||
| 
 | ||||
| export { Skeleton } | ||||
|  | @ -0,0 +1,19 @@ | |||
| import * as React from "react" | ||||
| 
 | ||||
| const MOBILE_BREAKPOINT = 768 | ||||
| 
 | ||||
| export function useIsMobile() { | ||||
|   const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined) | ||||
| 
 | ||||
|   React.useEffect(() => { | ||||
|     const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`) | ||||
|     const onChange = () => { | ||||
|       setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) | ||||
|     } | ||||
|     mql.addEventListener("change", onChange) | ||||
|     setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) | ||||
|     return () => mql.removeEventListener("change", onChange) | ||||
|   }, []) | ||||
| 
 | ||||
|   return !!isMobile | ||||
| } | ||||
|  | @ -0,0 +1,46 @@ | |||
| import { PropsWithChildren, ReactNode } from "react"; | ||||
| import {AppSidebar} from "@/components/app-sidebar"; | ||||
| import {SidebarInset, SidebarProvider, SidebarTrigger} from "@/components/ui/sidebar"; | ||||
| import {Separator} from "@/components/ui/separator"; | ||||
| import { | ||||
|     Breadcrumb, | ||||
|     BreadcrumbItem, | ||||
|     BreadcrumbList, BreadcrumbPage, | ||||
| } from "@/components/ui/breadcrumb"; | ||||
| import AppearanceDropdown from "@/components/appearance-dropdown"; | ||||
| 
 | ||||
| export default function AuthenticatedLayout({ | ||||
|     header, | ||||
|     children | ||||
| }: PropsWithChildren<{ | ||||
|     header?: ReactNode; | ||||
| }>) { | ||||
|     return ( | ||||
|         <SidebarProvider> | ||||
|             <AppSidebar /> | ||||
| 
 | ||||
|             <SidebarInset> | ||||
|                 <header className="sticky top-0 bg-background flex h-16 shrink-0 items-center gap-2 justify-between p-4 border-b md:border-none md:rounded-xl"> | ||||
|                     <div className="flex items-center gap-2"> | ||||
|                         <SidebarTrigger className="-ml-1" /> | ||||
|                         <Separator orientation="vertical" className="mr-2 h-4" /> | ||||
|                         <Breadcrumb> | ||||
|                             <BreadcrumbList> | ||||
|                                 <BreadcrumbItem> | ||||
|                                     <BreadcrumbPage>{header}</BreadcrumbPage> | ||||
|                                 </BreadcrumbItem> | ||||
|                             </BreadcrumbList> | ||||
|                         </Breadcrumb> | ||||
|                     </div> | ||||
|                     <div> | ||||
|                         <AppearanceDropdown /> | ||||
|                     </div> | ||||
|                 </header> | ||||
| 
 | ||||
|                 <main className="p-4 md:pt-0 h-full"> | ||||
|                     {children} | ||||
|                 </main> | ||||
|             </SidebarInset> | ||||
|         </SidebarProvider> | ||||
|     ); | ||||
| } | ||||
|  | @ -1,4 +1,4 @@ | |||
| import ApplicationLogo from "@/Components/ApplicationLogo"; | ||||
| import ApplicationLogo from "@/components/application-logo"; | ||||
| import { Link } from "@inertiajs/react"; | ||||
| import { PropsWithChildren } from "react"; | ||||
| 
 | ||||
|  | @ -1,5 +1,5 @@ | |||
| import { FormEventHandler, useEffect } from "react"; | ||||
| import GuestLayout from "@/Layouts/GuestLayout"; | ||||
| import GuestLayout from "@/layouts/guest-layout"; | ||||
| import { Head, useForm } from "@inertiajs/react"; | ||||
| import { | ||||
|     Card, | ||||
|  | @ -7,11 +7,11 @@ import { | |||
|     CardDescription, | ||||
|     CardFooter, | ||||
|     CardHeader, | ||||
| } from "@/Components/ui/card"; | ||||
| import { Label } from "@/Components/ui/label"; | ||||
| import { Input } from "@/Components/ui/input"; | ||||
| import { InputError } from "@/Components/ui/InputError"; | ||||
| import { Button } from "@/Components/ui/button"; | ||||
| } from "@/components/ui/card"; | ||||
| import { Label } from "@/components/ui/label"; | ||||
| import { Input } from "@/components/ui/input"; | ||||
| import { InputError } from "@/components/ui/input-error"; | ||||
| import { Button } from "@/components/ui/button"; | ||||
| 
 | ||||
| export default function ConfirmPassword() { | ||||
|     const { data, setData, post, processing, errors, reset } = useForm({ | ||||
|  | @ -1,16 +1,16 @@ | |||
| import GuestLayout from "@/Layouts/GuestLayout"; | ||||
| import GuestLayout from "@/layouts/guest-layout"; | ||||
| import { Head, useForm } from "@inertiajs/react"; | ||||
| import { FormEventHandler } from "react"; | ||||
| import { Input } from "@/Components/ui/input"; | ||||
| import { InputError } from "@/Components/ui/InputError"; | ||||
| import { Button } from "@/Components/ui/button"; | ||||
| import { Input } from "@/components/ui/input"; | ||||
| import { InputError } from "@/components/ui/input-error"; | ||||
| import { Button } from "@/components/ui/button"; | ||||
| import { | ||||
|     Card, | ||||
|     CardContent, | ||||
|     CardDescription, | ||||
|     CardFooter, | ||||
|     CardHeader, | ||||
| } from "@/Components/ui/card"; | ||||
| } from "@/components/ui/card"; | ||||
| 
 | ||||
| export default function ForgotPassword({ status }: { status?: string }) { | ||||
|     const { data, setData, post, processing, errors } = useForm({ | ||||
|  | @ -1,5 +1,5 @@ | |||
| import { FormEventHandler, useEffect } from "react"; | ||||
| import GuestLayout from "@/Layouts/GuestLayout"; | ||||
| import GuestLayout from "@/layouts/guest-layout"; | ||||
| import { Head, Link, useForm } from "@inertiajs/react"; | ||||
| import { | ||||
|     Card, | ||||
|  | @ -7,11 +7,11 @@ import { | |||
|     CardDescription, | ||||
|     CardHeader, | ||||
|     CardTitle, | ||||
| } from "@/Components/ui/card"; | ||||
| import { Label } from "@/Components/ui/label"; | ||||
| import { Input } from "@/Components/ui/input"; | ||||
| import { Button } from "@/Components/ui/button"; | ||||
| import { InputError } from "@/Components/ui/InputError"; | ||||
| } from "@/components/ui/card"; | ||||
| import { Label } from "@/components/ui/label"; | ||||
| import { Input } from "@/components/ui/input"; | ||||
| import { Button } from "@/components/ui/button"; | ||||
| import { InputError } from "@/components/ui/input-error"; | ||||
| 
 | ||||
| export default function Login({ | ||||
|     status, | ||||
|  | @ -1,17 +1,17 @@ | |||
| import { FormEventHandler, useEffect } from "react"; | ||||
| import GuestLayout from "@/Layouts/GuestLayout"; | ||||
| import GuestLayout from "@/layouts/guest-layout"; | ||||
| import { Head, Link, useForm } from "@inertiajs/react"; | ||||
| import { Button } from "@/Components/ui/button"; | ||||
| import { Button } from "@/components/ui/button"; | ||||
| import { | ||||
|     Card, | ||||
|     CardContent, | ||||
|     CardDescription, | ||||
|     CardHeader, | ||||
|     CardTitle, | ||||
| } from "@/Components/ui/card"; | ||||
| import { Input } from "@/Components/ui/input"; | ||||
| import { Label } from "@/Components/ui/label"; | ||||
| import { InputError } from "@/Components/ui/InputError"; | ||||
| } from "@/components/ui/card"; | ||||
| import { Input } from "@/components/ui/input"; | ||||
| import { Label } from "@/components/ui/label"; | ||||
| import { InputError } from "@/components/ui/input-error"; | ||||
| 
 | ||||
| export default function Register() { | ||||
|     const { data, setData, post, processing, errors, reset } = useForm({ | ||||
|  | @ -1,10 +1,10 @@ | |||
| import { FormEventHandler, useEffect } from "react"; | ||||
| import GuestLayout from "@/Layouts/GuestLayout"; | ||||
| import GuestLayout from "@/layouts/guest-layout"; | ||||
| import { Head, useForm } from "@inertiajs/react"; | ||||
| import { Label } from "@/Components/ui/label"; | ||||
| import { Input } from "@/Components/ui/input"; | ||||
| import { InputError } from "@/Components/ui/InputError"; | ||||
| import { Button } from "@/Components/ui/button"; | ||||
| import { Label } from "@/components/ui/label"; | ||||
| import { Input } from "@/components/ui/input"; | ||||
| import { InputError } from "@/components/ui/input-error"; | ||||
| import { Button } from "@/components/ui/button"; | ||||
| import { | ||||
|     Card, | ||||
|     CardContent, | ||||
|  | @ -12,7 +12,7 @@ import { | |||
|     CardFooter, | ||||
|     CardHeader, | ||||
|     CardTitle, | ||||
| } from "@/Components/ui/card"; | ||||
| } from "@/components/ui/card"; | ||||
| 
 | ||||
| export default function ResetPassword({ | ||||
|     token, | ||||
|  | @ -1,13 +1,13 @@ | |||
| import GuestLayout from "@/Layouts/GuestLayout"; | ||||
| import GuestLayout from "@/layouts/guest-layout"; | ||||
| import { Head, Link, useForm } from "@inertiajs/react"; | ||||
| import { FormEventHandler } from "react"; | ||||
| import { Button } from "@/Components/ui/button"; | ||||
| import { Button } from "@/components/ui/button"; | ||||
| import { | ||||
|     Card, | ||||
|     CardContent, | ||||
|     CardDescription, | ||||
|     CardHeader, | ||||
| } from "@/Components/ui/card"; | ||||
| } from "@/components/ui/card"; | ||||
| 
 | ||||
| export default function VerifyEmail({ status }: { status?: string }) { | ||||
|     const { post, processing } = useForm({}); | ||||
|  | @ -0,0 +1,22 @@ | |||
| import AuthenticatedLayout from "@/layouts/authenticated-layout"; | ||||
| import { Head } from "@inertiajs/react"; | ||||
| import { PageProps } from "@/types"; | ||||
| 
 | ||||
| export default function Dashboard() { | ||||
|     return ( | ||||
|         <AuthenticatedLayout | ||||
|             header="Dashboard" | ||||
|         > | ||||
|             <Head title="Dashboard" /> | ||||
| 
 | ||||
|             <div className="flex flex-1 flex-col gap-4 h-full"> | ||||
|                 <div className="grid auto-rows-min gap-4 md:grid-cols-3"> | ||||
|                     <div className="aspect-video rounded-xl bg-muted/50" /> | ||||
|                     <div className="aspect-video rounded-xl bg-muted/50" /> | ||||
|                     <div className="aspect-video rounded-xl bg-muted/50" /> | ||||
|                 </div> | ||||
|                 <div className="flex-1 rounded-xl bg-muted/50 h-full" /> | ||||
|             </div> | ||||
|         </AuthenticatedLayout> | ||||
|     ); | ||||
| } | ||||
|  | @ -1,28 +1,23 @@ | |||
| import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout"; | ||||
| import DeleteUserForm from "./Partials/DeleteUserForm"; | ||||
| import UpdatePasswordForm from "./Partials/UpdatePasswordForm"; | ||||
| import UpdateProfileInformationForm from "./Partials/UpdateProfileInformationForm"; | ||||
| import AuthenticatedLayout from "@/layouts/authenticated-layout"; | ||||
| import DeleteUserForm from "@/pages/profile/partials/delete-user-form"; | ||||
| import UpdatePasswordForm from "@/pages/profile/partials/update-password-form"; | ||||
| import UpdateProfileInformationForm from "@/pages/profile/partials/update-profile-information-form"; | ||||
| import { Head } from "@inertiajs/react"; | ||||
| import { PageProps } from "@/types"; | ||||
| import { | ||||
|     Card, | ||||
|     CardContent, | ||||
|     CardDescription, | ||||
|     CardHeader, | ||||
|     CardTitle, | ||||
| } from "@/Components/ui/card"; | ||||
| } from "@/components/ui/card"; | ||||
| 
 | ||||
| export default function Edit({ | ||||
|     auth, | ||||
|     mustVerifyEmail, | ||||
|     status, | ||||
| }: PageProps<{ mustVerifyEmail: boolean; status?: string }>) { | ||||
| }: { mustVerifyEmail: boolean; status?: string }) { | ||||
|     return ( | ||||
|         <AuthenticatedLayout | ||||
|             user={auth.user} | ||||
|             header={ | ||||
|                 <h2 className="font-semibold text-xl leading-tight">Profile</h2> | ||||
|             } | ||||
|             header={'Edit Profile'} | ||||
|         > | ||||
|             <Head title="Profile" /> | ||||
| 
 | ||||
|  | @ -1,9 +1,9 @@ | |||
| import { FormEventHandler, useRef, useState } from "react"; | ||||
| import { InputError } from "@/Components/ui/InputError"; | ||||
| import { InputError } from "@/components/ui/input-error"; | ||||
| import { useForm } from "@inertiajs/react"; | ||||
| import { Button } from "@/Components/ui/button"; | ||||
| import { Label } from "@/Components/ui/label"; | ||||
| import { Input } from "@/Components/ui/input"; | ||||
| import { Button } from "@/components/ui/button"; | ||||
| import { Label } from "@/components/ui/label"; | ||||
| import { Input } from "@/components/ui/input"; | ||||
| import { | ||||
|     AlertDialog, | ||||
|     AlertDialogAction, | ||||
|  | @ -14,7 +14,7 @@ import { | |||
|     AlertDialogHeader, | ||||
|     AlertDialogTitle, | ||||
|     AlertDialogTrigger, | ||||
| } from "@/Components/ui/alert-dialog"; | ||||
| } from "@/components/ui/alert-dialog"; | ||||
| 
 | ||||
| export default function DeleteUserForm({ | ||||
|     className = "", | ||||
|  | @ -62,7 +62,7 @@ export default function DeleteUserForm({ | |||
|                     </Button> | ||||
|                 </AlertDialogTrigger> | ||||
| 
 | ||||
|                 <AlertDialogContent asChild> | ||||
|                 <AlertDialogContent> | ||||
|                     <form onSubmit={deleteUser}> | ||||
|                         <AlertDialogHeader> | ||||
|                             <AlertDialogTitle> | ||||
|  | @ -76,7 +76,7 @@ export default function DeleteUserForm({ | |||
|                             </AlertDialogDescription> | ||||
|                         </AlertDialogHeader> | ||||
| 
 | ||||
|                         <div> | ||||
|                         <div className="py-4"> | ||||
|                             <Label htmlFor="password" className="sr-only"> | ||||
|                                 Password | ||||
|                             </Label> | ||||
|  | @ -1,10 +1,10 @@ | |||
| import { useRef, FormEventHandler } from "react"; | ||||
| import { useForm } from "@inertiajs/react"; | ||||
| import { Transition } from "@headlessui/react"; | ||||
| import { Label } from "@/Components/ui/label"; | ||||
| import { Input } from "@/Components/ui/input"; | ||||
| import { InputError } from "@/Components/ui/InputError"; | ||||
| import { Button } from "@/Components/ui/button"; | ||||
| import { Label } from "@/components/ui/label"; | ||||
| import { Input } from "@/components/ui/input"; | ||||
| import { InputError } from "@/components/ui/input-error"; | ||||
| import { Button } from "@/components/ui/button"; | ||||
| 
 | ||||
| export default function UpdatePasswordForm({ | ||||
|     className = "", | ||||
|  | @ -2,10 +2,10 @@ import { Link, useForm, usePage } from "@inertiajs/react"; | |||
| import { Transition } from "@headlessui/react"; | ||||
| import { FormEventHandler } from "react"; | ||||
| import { PageProps } from "@/types"; | ||||
| import { Label } from "@/Components/ui/label"; | ||||
| import { Input } from "@/Components/ui/input"; | ||||
| import { InputError } from "@/Components/ui/InputError"; | ||||
| import { Button } from "@/Components/ui/button"; | ||||
| import { Label } from "@/components/ui/label"; | ||||
| import { Input } from "@/components/ui/input"; | ||||
| import { InputError } from "@/components/ui/input-error"; | ||||
| import { Button } from "@/components/ui/button"; | ||||
| 
 | ||||
| export default function UpdateProfileInformation({ | ||||
|     mustVerifyEmail, | ||||
|  | @ -13,7 +13,7 @@ | |||
|         <!-- Scripts --> | ||||
|         @routes | ||||
|         @viteReactRefresh | ||||
|         @vite(['resources/js/app.tsx', "resources/js/Pages/{$page['component']}.tsx"]) | ||||
|         @vite(['resources/js/app.tsx', "resources/js/pages/{$page['component']}.tsx"]) | ||||
|         @inertiaHead | ||||
|     </head> | ||||
|     <body class="font-sans antialiased"> | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ use Illuminate\Support\Facades\Route; | |||
| use Inertia\Inertia; | ||||
| 
 | ||||
| Route::get('/', function () { | ||||
|     return Inertia::render('Welcome', [ | ||||
|     return Inertia::render('welcome', [ | ||||
|         'canLogin' => Route::has('login'), | ||||
|         'canRegister' => Route::has('register'), | ||||
|         'laravelVersion' => Application::VERSION, | ||||
|  | @ -15,7 +15,7 @@ Route::get('/', function () { | |||
| }); | ||||
| 
 | ||||
| Route::get('/dashboard', function () { | ||||
|     return Inertia::render('Dashboard'); | ||||
|     return Inertia::render('dashboard'); | ||||
| })->middleware(['auth', 'verified'])->name('dashboard'); | ||||
| 
 | ||||
| Route::middleware(['auth', 'verified'])->group(function () { | ||||
|  |  | |||
|  | @ -7,69 +7,87 @@ module.exports = { | |||
|   ], | ||||
|   prefix: "", | ||||
|   theme: { | ||||
|     container: { | ||||
|       center: true, | ||||
|       padding: "2rem", | ||||
|       screens: { | ||||
|         "2xl": "1400px", | ||||
|       }, | ||||
|     }, | ||||
|     extend: { | ||||
|       colors: { | ||||
|         border: "hsl(var(--border))", | ||||
|         input: "hsl(var(--input))", | ||||
|         ring: "hsl(var(--ring))", | ||||
|         background: "hsl(var(--background))", | ||||
|         foreground: "hsl(var(--foreground))", | ||||
|         primary: { | ||||
|           DEFAULT: "hsl(var(--primary))", | ||||
|           foreground: "hsl(var(--primary-foreground))", | ||||
|         }, | ||||
|         secondary: { | ||||
|           DEFAULT: "hsl(var(--secondary))", | ||||
|           foreground: "hsl(var(--secondary-foreground))", | ||||
|         }, | ||||
|         destructive: { | ||||
|           DEFAULT: "hsl(var(--destructive))", | ||||
|           foreground: "hsl(var(--destructive-foreground))", | ||||
|         }, | ||||
|         muted: { | ||||
|           DEFAULT: "hsl(var(--muted))", | ||||
|           foreground: "hsl(var(--muted-foreground))", | ||||
|         }, | ||||
|         accent: { | ||||
|           DEFAULT: "hsl(var(--accent))", | ||||
|           foreground: "hsl(var(--accent-foreground))", | ||||
|         }, | ||||
|         popover: { | ||||
|           DEFAULT: "hsl(var(--popover))", | ||||
|           foreground: "hsl(var(--popover-foreground))", | ||||
|         }, | ||||
|         card: { | ||||
|           DEFAULT: "hsl(var(--card))", | ||||
|           foreground: "hsl(var(--card-foreground))", | ||||
|         }, | ||||
|       }, | ||||
|       borderRadius: { | ||||
|         lg: "var(--radius)", | ||||
|         md: "calc(var(--radius) - 2px)", | ||||
|         sm: "calc(var(--radius) - 4px)", | ||||
|       }, | ||||
|       keyframes: { | ||||
|         "accordion-down": { | ||||
|           from: { height: "0" }, | ||||
|           to: { height: "var(--radix-accordion-content-height)" }, | ||||
|         }, | ||||
|         "accordion-up": { | ||||
|           from: { height: "var(--radix-accordion-content-height)" }, | ||||
|           to: { height: "0" }, | ||||
|         }, | ||||
|       }, | ||||
|       animation: { | ||||
|         "accordion-down": "accordion-down 0.2s ease-out", | ||||
|         "accordion-up": "accordion-up 0.2s ease-out", | ||||
|       }, | ||||
|     }, | ||||
|   	container: { | ||||
|   		center: 'true', | ||||
|   		padding: '2rem', | ||||
|   		screens: { | ||||
|   			'2xl': '1400px' | ||||
|   		} | ||||
|   	}, | ||||
|   	extend: { | ||||
|   		colors: { | ||||
|   			border: 'hsl(var(--border))', | ||||
|   			input: 'hsl(var(--input))', | ||||
|   			ring: 'hsl(var(--ring))', | ||||
|   			background: 'hsl(var(--background))', | ||||
|   			foreground: 'hsl(var(--foreground))', | ||||
|   			primary: { | ||||
|   				DEFAULT: 'hsl(var(--primary))', | ||||
|   				foreground: 'hsl(var(--primary-foreground))' | ||||
|   			}, | ||||
|   			secondary: { | ||||
|   				DEFAULT: 'hsl(var(--secondary))', | ||||
|   				foreground: 'hsl(var(--secondary-foreground))' | ||||
|   			}, | ||||
|   			destructive: { | ||||
|   				DEFAULT: 'hsl(var(--destructive))', | ||||
|   				foreground: 'hsl(var(--destructive-foreground))' | ||||
|   			}, | ||||
|   			muted: { | ||||
|   				DEFAULT: 'hsl(var(--muted))', | ||||
|   				foreground: 'hsl(var(--muted-foreground))' | ||||
|   			}, | ||||
|   			accent: { | ||||
|   				DEFAULT: 'hsl(var(--accent))', | ||||
|   				foreground: 'hsl(var(--accent-foreground))' | ||||
|   			}, | ||||
|   			popover: { | ||||
|   				DEFAULT: 'hsl(var(--popover))', | ||||
|   				foreground: 'hsl(var(--popover-foreground))' | ||||
|   			}, | ||||
|   			card: { | ||||
|   				DEFAULT: 'hsl(var(--card))', | ||||
|   				foreground: 'hsl(var(--card-foreground))' | ||||
|   			}, | ||||
|   			sidebar: { | ||||
|   				DEFAULT: 'hsl(var(--sidebar-background))', | ||||
|   				foreground: 'hsl(var(--sidebar-foreground))', | ||||
|   				primary: 'hsl(var(--sidebar-primary))', | ||||
|   				'primary-foreground': 'hsl(var(--sidebar-primary-foreground))', | ||||
|   				accent: 'hsl(var(--sidebar-accent))', | ||||
|   				'accent-foreground': 'hsl(var(--sidebar-accent-foreground))', | ||||
|   				border: 'hsl(var(--sidebar-border))', | ||||
|   				ring: 'hsl(var(--sidebar-ring))' | ||||
|   			} | ||||
|   		}, | ||||
|   		borderRadius: { | ||||
|   			lg: 'var(--radius)', | ||||
|   			md: 'calc(var(--radius) - 2px)', | ||||
|   			sm: 'calc(var(--radius) - 4px)' | ||||
|   		}, | ||||
|   		keyframes: { | ||||
|   			'accordion-down': { | ||||
|   				from: { | ||||
|   					height: '0' | ||||
|   				}, | ||||
|   				to: { | ||||
|   					height: 'var(--radix-accordion-content-height)' | ||||
|   				} | ||||
|   			}, | ||||
|   			'accordion-up': { | ||||
|   				from: { | ||||
|   					height: 'var(--radix-accordion-content-height)' | ||||
|   				}, | ||||
|   				to: { | ||||
|   					height: '0' | ||||
|   				} | ||||
|   			} | ||||
|   		}, | ||||
|   		animation: { | ||||
|   			'accordion-down': 'accordion-down 0.2s ease-out', | ||||
|   			'accordion-up': 'accordion-up 0.2s ease-out' | ||||
|   		} | ||||
|   	} | ||||
|   }, | ||||
|   plugins: [require("tailwindcss-animate")], | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue