feat(admin): implement navigation & layout (Plan 02, Phases 1–3)
Phase 1 — Active route highlighting - NavMain derives isActive from usePathname() at render time - Flat links: exact match; collapsible groups: startsWith; sub-items: exact match - Removed hardcoded isActive fields from NAV_LINKS Phase 2 — Dynamic breadcrumbs - Added ROUTE_LABELS map to app.constants.ts - Created DynamicBreadcrumb component (pathname-driven, ShadCN primitives) - Wired DynamicBreadcrumb into dashboard layout header Phase 3 — Mobile shell (observation only, no structural changes needed) Icons — migrated all sidebar/breadcrumb icons from lucide-react to @hugeicons/react + @hugeicons/core-free-icons Infra - tsconfig.json: moduleResolution → bundler (required for hugeicons ESM exports) - Renamed middleware.ts → proxy.ts (Next.js 15.5 convention) - Added @hugeicons/mcp-server to .mcp.json Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"style": "radix-nova",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"css": "src/app/globals.css",
|
||||
"css": "app/globals.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"iconLibrary": "lucide",
|
||||
"iconLibrary": "hugeicons",
|
||||
"rtl": false,
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
@@ -19,5 +19,7 @@
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"menuColor": "default",
|
||||
"menuAccent": "subtle",
|
||||
"registries": {}
|
||||
}
|
||||
|
||||
2
apps/admin/next-env.d.ts
vendored
2
apps/admin/next-env.d.ts
vendored
@@ -1,6 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
/// <reference path="./.next/types/routes.d.ts" />
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
"type-check": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@base-ui/react": "^1.2.0",
|
||||
"@clerk/nextjs": "^6.38.2",
|
||||
"@hugeicons/core-free-icons": "^3.3.0",
|
||||
"@hugeicons/react": "^1.1.5",
|
||||
"@repo/convex": "*",
|
||||
"@repo/types": "*",
|
||||
"@repo/utils": "*",
|
||||
@@ -27,4 +30,4 @@
|
||||
"tailwindcss": "^4.2.0",
|
||||
"tw-animate-css": "^1.4.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { AdminUserSync } from "../../components/auth/AdminUserSync";
|
||||
import { AdminAuthGate } from "../../components/auth/AdminAuthGate";
|
||||
import { AppSidebar } from "../../components/layout/sidebar/app-sidebar";
|
||||
import { DynamicBreadcrumb } from "../../components/layout/DynamicBreadcrumb";
|
||||
import {
|
||||
SidebarInset,
|
||||
SidebarProvider,
|
||||
@@ -27,6 +28,7 @@ export default function DashboardLayout({
|
||||
orientation="vertical"
|
||||
className="mr-2 data-[orientation=vertical]:h-4"
|
||||
/>
|
||||
<DynamicBreadcrumb />
|
||||
</div>
|
||||
</header>
|
||||
<main className="flex flex-1 flex-col gap-4 p-4">
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
export default function DashboardPage() {
|
||||
return (
|
||||
<main>
|
||||
<h1>Admin Dashboard</h1>
|
||||
<div className="flex flex-1 flex-col gap-4 p-4 pt-0">
|
||||
<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="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min lg:min-h-[100vh]" />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,120 +5,122 @@
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@theme inline {
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--radius-2xl: calc(var(--radius) + 8px);
|
||||
--radius-3xl: calc(var(--radius) + 12px);
|
||||
--radius-4xl: calc(var(--radius) + 16px);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-ring: var(--ring);
|
||||
--color-input: var(--input);
|
||||
--color-border: var(--border);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-card: var(--card);
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--radius-2xl: calc(var(--radius) + 8px);
|
||||
--radius-3xl: calc(var(--radius) + 12px);
|
||||
--radius-4xl: calc(var(--radius) + 16px);
|
||||
}
|
||||
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.60 0.10 185);
|
||||
--primary-foreground: oklch(0.98 0.01 181);
|
||||
--secondary: oklch(0.967 0.001 286.375);
|
||||
--secondary-foreground: oklch(0.21 0.006 285.885);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.58 0.22 27);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.85 0.13 181);
|
||||
--chart-2: oklch(0.78 0.13 182);
|
||||
--chart-3: oklch(0.70 0.12 183);
|
||||
--chart-4: oklch(0.60 0.10 185);
|
||||
--chart-5: oklch(0.51 0.09 186);
|
||||
--radius: 0.45rem;
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.60 0.10 185);
|
||||
--sidebar-primary-foreground: oklch(0.98 0.01 181);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.205 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.922 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.205 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.70 0.12 183);
|
||||
--primary-foreground: oklch(0.28 0.04 193);
|
||||
--secondary: oklch(0.274 0.006 286.033);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.371 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--chart-1: oklch(0.85 0.13 181);
|
||||
--chart-2: oklch(0.78 0.13 182);
|
||||
--chart-3: oklch(0.70 0.12 183);
|
||||
--chart-4: oklch(0.60 0.10 185);
|
||||
--chart-5: oklch(0.51 0.09 186);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.78 0.13 182);
|
||||
--sidebar-primary-foreground: oklch(0.28 0.04 193);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,20 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Inter } from "next/font/google";
|
||||
import { DM_Sans, Geist, Geist_Mono } from "next/font/google";
|
||||
import { ClerkProvider } from "@clerk/nextjs";
|
||||
import { ConvexClientProvider } from "@repo/convex";
|
||||
import "./globals.css";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
const dmSans = DM_Sans({subsets:['latin'],variable:'--font-sans'});
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
@@ -21,7 +31,7 @@ export default function RootLayout({
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>
|
||||
<body className={ dmSans.className}>
|
||||
<ClerkProvider>
|
||||
<ConvexClientProvider>
|
||||
{children}
|
||||
|
||||
@@ -6,8 +6,8 @@ export default function NotFound() {
|
||||
<main className="flex flex-1 items-center justify-center bg-[var(--background)] px-4 py-16 md:px-6 md:py-24">
|
||||
<div className="mx-auto flex w-full max-w-4xl flex-col items-center gap-8 md:flex-row md:gap-12">
|
||||
<div className="flex w-full flex-col items-center text-center md:w-1/2 md:items-start md:text-left">
|
||||
<h2 className="text-2xl font-bold tracking-tight md:text-3xl flex flex-wrap gap-x-3 gap-y-0 items-baseline">
|
||||
<span className="font-medium">Page</span>
|
||||
<h2 className="text-2xl font-black tracking-tight md:text-3xl flex flex-wrap gap-x-3 gap-y-0 items-baseline">
|
||||
<span className="font-extralight">Page</span>
|
||||
<span>not found</span>
|
||||
</h2>
|
||||
|
||||
|
||||
69
apps/admin/src/components/layout/DynamicBreadcrumb.tsx
Normal file
69
apps/admin/src/components/layout/DynamicBreadcrumb.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { HugeiconsIcon } from "@hugeicons/react";
|
||||
import { Home01Icon } from "@hugeicons/core-free-icons";
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/components/ui/breadcrumb";
|
||||
import { ROUTE_LABELS } from "@/lib/constants/app.constants";
|
||||
|
||||
export function DynamicBreadcrumb() {
|
||||
const pathname = usePathname();
|
||||
const segments = pathname.split("/").filter(Boolean);
|
||||
|
||||
// Root path — render "Dashboard" as a single page crumb
|
||||
if (segments.length === 0) {
|
||||
return (
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Dashboard</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
{/* Home icon link */}
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink asChild>
|
||||
<Link href="/">
|
||||
<HugeiconsIcon icon={Home01Icon} size={16} />
|
||||
</Link>
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
|
||||
{segments.map((segment, index) => {
|
||||
const href = "/" + segments.slice(0, index + 1).join("/");
|
||||
const label = ROUTE_LABELS[segment] ?? segment;
|
||||
const isLast = index === segments.length - 1;
|
||||
|
||||
return (
|
||||
<span key={href} className="contents">
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
{isLast ? (
|
||||
<BreadcrumbPage>{label}</BreadcrumbPage>
|
||||
) : (
|
||||
<BreadcrumbLink asChild>
|
||||
<Link href={href}>{label}</Link>
|
||||
</BreadcrumbLink>
|
||||
)}
|
||||
</BreadcrumbItem>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import {
|
||||
LayoutDashboard,
|
||||
ShoppingCart,
|
||||
Package,
|
||||
Users,
|
||||
Tag,
|
||||
Star,
|
||||
Settings,
|
||||
PawPrint,
|
||||
} from "lucide-react";
|
||||
import { HugeiconsIcon } from "@hugeicons/react";
|
||||
import { Store01Icon } from "@hugeicons/core-free-icons";
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
@@ -23,19 +15,8 @@ import {
|
||||
} from "@/components/ui/sidebar";
|
||||
import { NavMain } from "./nav-main";
|
||||
import { NavUser } from "./nav-user";
|
||||
|
||||
const navItems = [
|
||||
{ title: "Dashboard", url: "/", icon: LayoutDashboard },
|
||||
{ title: "Orders", url: "/orders", icon: ShoppingCart },
|
||||
{ title: "Products", url: "/products", icon: Package },
|
||||
{ title: "Customers", url: "/customers", icon: Users },
|
||||
{ title: "Categories", url: "/categories", icon: Tag },
|
||||
{ title: "Reviews", url: "/reviews", icon: Star },
|
||||
];
|
||||
|
||||
const settingsItems = [
|
||||
{ title: "Settings", url: "/settings", icon: Settings },
|
||||
];
|
||||
import { NAV_LINKS } from "@/lib/constants/app.constants";
|
||||
import { PawPrint } from "lucide-react";
|
||||
|
||||
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
return (
|
||||
@@ -57,8 +38,14 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
</SidebarHeader>
|
||||
|
||||
<SidebarContent>
|
||||
<NavMain items={navItems} />
|
||||
<NavMain items={settingsItems} />
|
||||
{/* dashboard */}
|
||||
<NavMain overview={NAV_LINKS.overview} navMain={[]} isOverview={true} />
|
||||
|
||||
{/* Application */}
|
||||
<NavMain navMain={NAV_LINKS.navMain} overview={[]} isOverview={false} />
|
||||
|
||||
{/* Users */}
|
||||
<NavMain overview={NAV_LINKS.users} navMain={[]} isOverview={true} />
|
||||
</SidebarContent>
|
||||
|
||||
<SidebarFooter>
|
||||
|
||||
@@ -1,39 +1,102 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { type LucideIcon } from "lucide-react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { HugeiconsIcon, type IconSvgElement } from "@hugeicons/react";
|
||||
import { ArrowRight01Icon } from "@hugeicons/core-free-icons";
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupLabel,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarMenuSub,
|
||||
SidebarMenuSubButton,
|
||||
SidebarMenuSubItem,
|
||||
} from "@/components/ui/sidebar";
|
||||
import { CollapsibleTrigger, CollapsibleContent, Collapsible } from "@/components/ui/collapsible";
|
||||
|
||||
export function NavMain({
|
||||
items,
|
||||
overview,
|
||||
isOverview,
|
||||
navMain,
|
||||
}: {
|
||||
items: {
|
||||
overview: {
|
||||
title: string;
|
||||
url: string;
|
||||
icon: LucideIcon;
|
||||
isActive?: boolean;
|
||||
icon: IconSvgElement;
|
||||
}[];
|
||||
isOverview?: boolean;
|
||||
navMain: {
|
||||
title: string;
|
||||
url: string;
|
||||
icon: IconSvgElement;
|
||||
items?: {
|
||||
title: string;
|
||||
url: string;
|
||||
}[];
|
||||
}[];
|
||||
}) {
|
||||
const pathname = usePathname();
|
||||
|
||||
if (isOverview) {
|
||||
return (
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Platform</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{overview.map((item) => (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
<SidebarMenuButton asChild tooltip={item.title} isActive={pathname === item.url}>
|
||||
<Link href={item.url}>
|
||||
<HugeiconsIcon icon={item.icon} size={16} />
|
||||
<span>{item.title}</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Platform</SidebarGroupLabel>
|
||||
<SidebarGroupLabel>Application</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
<SidebarMenuButton asChild tooltip={item.title} isActive={item.isActive}>
|
||||
<Link href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
{navMain.map((item) => {
|
||||
const isGroupActive = pathname.startsWith(item.url);
|
||||
return (
|
||||
<Collapsible
|
||||
key={item.title}
|
||||
asChild
|
||||
defaultOpen={isGroupActive}
|
||||
className="group/collapsible"
|
||||
>
|
||||
<SidebarMenuItem>
|
||||
<CollapsibleTrigger asChild>
|
||||
<SidebarMenuButton tooltip={item.title} isActive={isGroupActive}>
|
||||
<HugeiconsIcon icon={item.icon} size={16} />
|
||||
<span>{item.title}</span>
|
||||
<HugeiconsIcon icon={ArrowRight01Icon} className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
|
||||
</SidebarMenuButton>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<SidebarMenuSub>
|
||||
{item.items?.map((subItem) => (
|
||||
<SidebarMenuSubItem key={subItem.title}>
|
||||
<SidebarMenuSubButton asChild isActive={pathname === subItem.url}>
|
||||
<Link href={subItem.url}>
|
||||
<span>{subItem.title}</span>
|
||||
</Link>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
))}
|
||||
</SidebarMenuSub>
|
||||
</CollapsibleContent>
|
||||
</SidebarMenuItem>
|
||||
</Collapsible>
|
||||
);
|
||||
})}
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
);
|
||||
|
||||
@@ -6,9 +6,9 @@ export default function StillBuildingPlaceholder() {
|
||||
<main className="flex flex-1 items-center justify-center bg-[var(--background)] px-4 py-16 md:px-6 md:py-24">
|
||||
<div className="mx-auto flex w-full max-w-4xl flex-col items-center gap-8 md:flex-row md:gap-12">
|
||||
<div className="flex w-full flex-col items-center text-center md:w-1/2 md:items-start md:text-left">
|
||||
<h2 className="text-2xl font-bold tracking-tight md:text-3xl flex flex-wrap gap-x-3 gap-y-0 items-baseline">
|
||||
<span className="font-medium">Building</span>
|
||||
<span>in progress</span>
|
||||
<h2 className="text-2xl font-black tracking-tight md:text-3xl flex flex-wrap gap-x-3 gap-y-0 items-baseline">
|
||||
<span className="font-extralight">Building</span>
|
||||
<span>in Progress</span>
|
||||
</h2>
|
||||
|
||||
<p className="mt-4 max-w-md text-base leading-relaxed md:text-lg">
|
||||
|
||||
@@ -1,30 +1,53 @@
|
||||
import { BookOpen, Bot, ShoppingCart, LayoutDashboard, Users } from "lucide-react";
|
||||
import {
|
||||
DashboardSquare02Icon,
|
||||
ShoppingCart01Icon,
|
||||
PackageIcon,
|
||||
UserMultipleIcon,
|
||||
} from "@hugeicons/core-free-icons";
|
||||
|
||||
export const ROUTE_LABELS: Record<string, string> = {
|
||||
orders: "Orders",
|
||||
products: "Products",
|
||||
categories: "Categories",
|
||||
images: "Images",
|
||||
variants: "Variants",
|
||||
customers: "Customers",
|
||||
reviews: "Reviews",
|
||||
messages: "Messages",
|
||||
newsletter: "Newsletter",
|
||||
users: "Users",
|
||||
settings: "Settings",
|
||||
returns: "Returns",
|
||||
};
|
||||
|
||||
export const NAV_LINKS = {
|
||||
overview: [
|
||||
{
|
||||
title: "Dashboard",
|
||||
url: "/",
|
||||
icon: LayoutDashboard,
|
||||
icon: DashboardSquare02Icon,
|
||||
},
|
||||
],
|
||||
navMain: [
|
||||
{
|
||||
title: "Sales",
|
||||
url: "/orders",
|
||||
icon: ShoppingCart,
|
||||
isActive: true,
|
||||
icon: ShoppingCart01Icon,
|
||||
items: [
|
||||
{
|
||||
title: "Orders",
|
||||
url: "/orders",
|
||||
},
|
||||
{
|
||||
title: "Returns",
|
||||
url: "/returns",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Products",
|
||||
url: "/products",
|
||||
icon: Bot,
|
||||
icon: PackageIcon,
|
||||
items: [
|
||||
{
|
||||
title: "Categories",
|
||||
@@ -32,7 +55,7 @@ export const NAV_LINKS = {
|
||||
},
|
||||
{
|
||||
title: "Products",
|
||||
url: "/products/products",
|
||||
url: "/products",
|
||||
},
|
||||
{
|
||||
title: "Images",
|
||||
@@ -44,54 +67,31 @@ export const NAV_LINKS = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Documentation",
|
||||
url: "#",
|
||||
icon: BookOpen,
|
||||
items: [
|
||||
{
|
||||
title: "Introduction",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Get Started",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Tutorials",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Changelog",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Customers",
|
||||
url: "/customers",
|
||||
icon: Users,
|
||||
icon: UserMultipleIcon,
|
||||
items: [
|
||||
{
|
||||
title: "Reviews",
|
||||
url: "#",
|
||||
url: "/customers/reviews",
|
||||
},
|
||||
{
|
||||
title: "Messages",
|
||||
url: "#",
|
||||
url: "/customers/messages",
|
||||
},
|
||||
{
|
||||
title: "Newsletter",
|
||||
url: "#",
|
||||
url: "/customers/newsletter",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
sales: [
|
||||
users: [
|
||||
{
|
||||
title: "Orders",
|
||||
url: "/orders",
|
||||
icon: ShoppingCart,
|
||||
title: "Users",
|
||||
url: "/users",
|
||||
icon: UserMultipleIcon,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
"incremental": true,
|
||||
"module": "esnext",
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "node",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"jsx": "react-jsx",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
@@ -23,14 +23,17 @@
|
||||
],
|
||||
"target": "ES2017",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
".next/types/**/*.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx"
|
||||
"**/*.tsx",
|
||||
".next/dev/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
|
||||
Reference in New Issue
Block a user