From 228d08337f7bdb23b84d9289c72166846858a47f Mon Sep 17 00:00:00 2001 From: Sean Date: Tue, 30 Dec 2025 17:22:02 -0500 Subject: [PATCH] new admin ui --- app/admin/layout.tsx | 65 ++++++-- components/AdminLeftNavigation.tsx | 246 ++++++++++++++++++++--------- package-lock.json | 213 +++++++++++++++++++++++++ package.json | 1 + 4 files changed, 430 insertions(+), 95 deletions(-) diff --git a/app/admin/layout.tsx b/app/admin/layout.tsx index 2b615f9..4902986 100644 --- a/app/admin/layout.tsx +++ b/app/admin/layout.tsx @@ -3,7 +3,9 @@ import type React from "react"; import { useEffect, useMemo, useState } from "react"; import { usePathname, useRouter } from "next/navigation"; -import AdminLeftNavigation from "@/components/AdminLeftNavigation"; +import AdminLeftNavigation, { + type AdminNavGroup, +} from "@/components/AdminLeftNavigation"; import { useAuth } from "@/context/AuthContext"; import { LayoutDashboard, @@ -13,24 +15,53 @@ import { Store, Users, Settings, - LucideMail, + Mail, Wand2, + FolderKanban, + Shield, } from "lucide-react"; -const navItems = [ - { label: "Dashboard", href: "/admin", icon: }, - { label: "Imports", href: "/admin/import-status", icon: }, - { label: "Mappings", href: "/admin/mapping", icon: }, - { label: "Products", href: "/admin/products", icon: }, - { label: "Merchants", href: "/admin/merchants", icon: }, - { label: "Platforms", href: "/admin/platforms", icon: }, - { label: "Enrichment", href: "/admin/enrichment", icon: }, - { label: "Users", href: "/admin/users", icon: }, - { label: "Settings", href: "/admin/settings", icon: }, - { label: "List Emails", href: "/admin/email", icon: }, - { label: "Send an Email", href: "/admin/email/send", icon: }, - { label: "Beta Invites", href: "/admin/beta-invites", icon: }, - +const navGroups: AdminNavGroup[] = [ + { + label: "Overview", + icon: , + items: [{ label: "Dashboard", href: "/admin", icon: }], + }, + { + label: "Catalog", + icon: , + items: [ + { label: "Products", href: "/admin/products", icon: }, + { label: "Platforms", href: "/admin/platforms", icon: }, + ], + }, + { + label: "Data Ops", + icon: , + items: [ + { label: "Imports", href: "/admin/import-status", icon: }, + { label: "Mappings", href: "/admin/mapping", icon: }, + { label: "Enrichment", href: "/admin/enrichment", icon: }, + ], + }, + { + label: "Growth & Comms", + icon: , + items: [ + { label: "Beta Invites", href: "/admin/beta-invites", icon: }, + { label: "List Emails", href: "/admin/email", icon: }, + { label: "Send an Email", href: "/admin/email/send", icon: }, + ], + }, + { + label: "Access & Config", + icon: , + items: [ + { label: "Users", href: "/admin/users", icon: }, + { label: "Merchants", href: "/admin/merchants", icon: }, + { label: "Settings", href: "/admin/settings", icon: }, + ], + }, ]; export default function AdminLayout({ @@ -79,7 +110,7 @@ export default function AdminLayout({ setCollapsed((v) => !v)} - items={navItems} + groups={navGroups} /> {/* Main column */} diff --git a/components/AdminLeftNavigation.tsx b/components/AdminLeftNavigation.tsx index 6952d79..bf2b10e 100644 --- a/components/AdminLeftNavigation.tsx +++ b/components/AdminLeftNavigation.tsx @@ -1,109 +1,199 @@ "use client"; -import type React from "react"; +import * as React from "react"; import Link from "next/link"; -import { - LayoutDashboard, - Download, - Layers, - Boxes, - Store, - Users, - Settings, - LucideMail, - Wand2, -} from "lucide-react"; +import { usePathname } from "next/navigation"; +import { Disclosure, DisclosureButton, DisclosurePanel } from "@headlessui/react"; +import { ChevronRight, PanelLeftClose, PanelLeftOpen } from "lucide-react"; export type AdminNavItem = { label: string; href: string; - icon: React.ReactNode; + icon?: React.ReactNode; }; -type AdminLeftNavigationProps = { - collapsed: boolean; - onToggleCollapsed: () => void; - items?: AdminNavItem[]; // ✅ NEW +export type AdminNavGroup = { + label: string; + icon?: React.ReactNode; + items: AdminNavItem[]; }; -const defaultNavItems: AdminNavItem[] = [ - { label: "Dashboard", href: "/admin", icon: }, - { label: "Imports", href: "/admin/import-status", icon: }, - { label: "Mappings", href: "/admin/mapping", icon: }, - { label: "Products", href: "/admin/products", icon: }, - { label: "Merchants", href: "/admin/merchants", icon: }, - { label: "Platforms", href: "/admin/platforms", icon: }, - { label: "Enrichment", href: "/admin/enrichment", icon: }, - { label: "Users", href: "/admin/users", icon: }, - { label: "Settings", href: "/admin/settings", icon: }, - { label: "Manage Emails", href: "/admin/email/manage", icon: }, - { label: "Send an Email", href: "/admin/email/send", icon: }, - { label: "Beta Invites", href: "/admin/beta-invites", icon: , - }, -]; +function cx(...classes: Array) { + return classes.filter(Boolean).join(" "); +} + +function isActiveRoute(pathname: string, href: string) { + // Exact match for /admin, prefix match for nested pages + if (href === "/admin") return pathname === "/admin"; + return pathname === href || pathname.startsWith(`${href}/`); +} + +function groupHasActiveItem(pathname: string, group: AdminNavGroup) { + return group.items.some((i) => isActiveRoute(pathname, i.href)); +} export default function AdminLeftNavigation({ collapsed, onToggleCollapsed, - items = defaultNavItems, // ✅ NEW default -}: AdminLeftNavigationProps) { + groups, +}: { + collapsed: boolean; + onToggleCollapsed: () => void; + groups: AdminNavGroup[]; +}) { + const pathname = usePathname() || "/admin"; + return ( ); } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 9d88e99..29fe58f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "gunbuilder-prototype", "version": "0.1.0", "dependencies": { + "@headlessui/react": "^2.2.9", "@heroicons/react": "^2.2.0", "clsx": "^2.1.1", "lucide-react": "^0.555.0", @@ -140,6 +141,79 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.26.28", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz", + "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@floating-ui/utils": "^0.2.8", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", + "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.4" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@headlessui/react": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.9.tgz", + "integrity": "sha512-Mb+Un58gwBn0/yWZfyrCh0TJyurtT+dETj7YHleylHk5od3dv2XqETPGWMyQ5/7sYN7oWdyM1u9MvC0OC8UmzQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.26.16", + "@react-aria/focus": "^3.20.2", + "@react-aria/interactions": "^3.25.0", + "@tanstack/react-virtual": "^3.13.9", + "use-sync-external-store": "^1.5.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/@heroicons/react": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz", @@ -505,6 +579,103 @@ "node": ">=14" } }, + "node_modules/@react-aria/focus": { + "version": "3.21.3", + "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.21.3.tgz", + "integrity": "sha512-FsquWvjSCwC2/sBk4b+OqJyONETUIXQ2vM0YdPAuC+QFQh2DT6TIBo6dOZVSezlhudDla69xFBd6JvCFq1AbUw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.26.0", + "@react-aria/utils": "^3.32.0", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/interactions": { + "version": "3.26.0", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.26.0.tgz", + "integrity": "sha512-AAEcHiltjfbmP1i9iaVw34Mb7kbkiHpYdqieWufldh4aplWgsF11YQZOfaCJW4QoR2ML4Zzoa9nfFwLXA52R7Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.32.0", + "@react-stately/flags": "^3.1.2", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/ssr": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz", + "integrity": "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/utils": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.32.0.tgz", + "integrity": "sha512-/7Rud06+HVBIlTwmwmJa2W8xVtgxgzm0+kLbuFooZRzKDON6hhozS1dOMR/YLMxyJOaYOTpImcP4vRR9gL1hEg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.10", + "@react-stately/flags": "^3.1.2", + "@react-stately/utils": "^3.11.0", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/flags": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@react-stately/flags/-/flags-3.1.2.tgz", + "integrity": "sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@react-stately/utils": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.11.0.tgz", + "integrity": "sha512-8LZpYowJ9eZmmYLpudbo/eclIRnbhWIJZ994ncmlKlouNzKohtM8qTC6B1w1pwUbiwGdUoyzLuQbeaIor5Dvcw==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/shared": { + "version": "3.32.1", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.32.1.tgz", + "integrity": "sha512-famxyD5emrGGpFuUlgOP6fVW2h/ZaF405G5KDi3zPHzyjAWys/8W6NAVJtNbkCkhedmvL0xOhvt8feGXyXaw5w==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -535,6 +706,33 @@ "tslib": "^2.4.0" } }, + "node_modules/@tanstack/react-virtual": { + "version": "3.13.14", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.14.tgz", + "integrity": "sha512-WG0d7mBD54eA7dgA3+sO5csS0B49QKqM6Gy5Rf31+Oq/LTKROQSao9m2N/vz1IqVragOKU5t5k1LAcqh/DfTxw==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.13.14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.13.14", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.14.tgz", + "integrity": "sha512-b5Uvd8J2dc7ICeX9SRb/wkCxWk7pUwN214eEPAQsqrsktSKTCmyLxOQWSMgogBByXclZeAdgZ3k4o0fIYUIBqQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", @@ -5655,6 +5853,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tabbable": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.3.0.tgz", + "integrity": "sha512-EIHvdY5bPLuWForiR/AN2Bxngzpuwn1is4asboytXtpTgsArc+WmSJKVLlhdh71u7jFcryDqB2A8lQvj78MkyQ==", + "license": "MIT" + }, "node_modules/tailwindcss": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz", @@ -6094,6 +6298,15 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 05e03e8..0ca7a6e 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "lint": "next lint" }, "dependencies": { + "@headlessui/react": "^2.2.9", "@heroicons/react": "^2.2.0", "clsx": "^2.1.1", "lucide-react": "^0.555.0",