Files
gunbuilder-next-tailwind/src/app/admin/AdminNavbar.tsx
T
2025-07-02 04:16:53 -04:00

310 lines
13 KiB
TypeScript

"use client";
import { useState, ReactNode } from 'react';
import { usePathname } from 'next/navigation';
import { useSession } from 'next-auth/react';
import {
Dialog,
DialogBackdrop,
DialogPanel,
Menu,
MenuButton,
MenuItem,
MenuItems,
TransitionChild,
} from '@headlessui/react';
import {
Bars3Icon,
BellIcon,
CalendarIcon,
ChartPieIcon,
Cog6ToothIcon,
DocumentDuplicateIcon,
FolderIcon,
HomeIcon,
UsersIcon,
XMarkIcon,
CubeIcon,
} from '@heroicons/react/24/outline';
import { ChevronDownIcon, MagnifyingGlassIcon } from '@heroicons/react/20/solid';
const navigation = [
{ name: 'Dashboard', href: '/admin', icon: HomeIcon },
{ name: 'Users', href: '/admin/users', icon: UsersIcon },
{ name: 'Category Mapping', href: '/admin/category-mapping', icon: ChartPieIcon },
{ name: 'Products', href: '/admin/products', icon: CubeIcon },
// { name: 'Settings', href: '/admin/settings', icon: Cog6ToothIcon }, // optional/future
];
const userNavigation = [
{ name: 'Your profile', href: '/account/profile' },
{ name: 'Sign out', href: '/api/auth/signout' },
];
function classNames(...classes: string[]) {
return classes.filter(Boolean).join(' ');
}
export default function AdminNavbar({ children }: { children: ReactNode }) {
const [sidebarOpen, setSidebarOpen] = useState(false);
const pathname = usePathname();
const { data: session } = useSession();
// Get user display name
const getUserDisplayName = () => {
if (!session?.user) return 'Admin User';
const user = session.user as any;
if (user.first_name && user.last_name) {
return `${user.first_name} ${user.last_name}`;
}
if (user.name) {
return user.name;
}
if (user.email) {
return user.email.split('@')[0]; // Use email prefix as fallback
}
return 'Admin User';
};
const getUserInitials = () => {
if (!session?.user) return 'A';
const user = session.user as any;
if (user.first_name && user.last_name) {
return `${user.first_name[0]}${user.last_name[0]}`.toUpperCase();
}
if (user.name) {
return user.name.split(' ').map((n: string) => n[0]).join('').toUpperCase().slice(0, 2);
}
if (user.email) {
return user.email[0].toUpperCase();
}
return 'A';
};
return (
<>
<div>
<Dialog open={sidebarOpen} onClose={setSidebarOpen} className="relative z-50 lg:hidden">
<DialogBackdrop
transition
className="fixed inset-0 bg-gray-900/80 transition-opacity duration-300 ease-linear data-[closed]:opacity-0"
/>
<div className="fixed inset-0 flex">
<DialogPanel
transition
className="relative mr-16 flex w-full max-w-xs flex-1 transform transition duration-300 ease-in-out data-[closed]:-translate-x-full"
>
<TransitionChild>
<div className="absolute left-full top-0 flex w-16 justify-center pt-5 duration-300 ease-in-out data-[closed]:opacity-0">
<button type="button" onClick={() => setSidebarOpen(false)} className="-m-2.5 p-2.5">
<span className="sr-only">Close sidebar</span>
<XMarkIcon aria-hidden="true" className="size-6 text-white" />
</button>
</div>
</TransitionChild>
{/* Sidebar component */}
<div className="flex grow flex-col gap-y-5 overflow-y-auto bg-white px-6 pb-4">
<div className="flex h-16 shrink-0 items-center">
<img
alt="Your Company"
src="https://tailwindcss.com/plus-assets/img/logos/mark.svg?color=indigo&shade=600"
className="h-8 w-auto"
/>
</div>
<nav className="flex flex-1 flex-col">
<ul role="list" className="flex flex-1 flex-col gap-y-7">
<li>
<ul role="list" className="-mx-2 space-y-1">
{navigation.map((item) => (
<li key={item.name}>
<a
href={item.href}
className={classNames(
pathname === item.href
? 'bg-gray-50 text-indigo-600'
: 'text-gray-700 hover:bg-gray-50 hover:text-indigo-600',
'group flex gap-x-3 rounded-md p-2 text-sm/6 font-semibold',
)}
>
<item.icon
aria-hidden="true"
className={classNames(
pathname === item.href ? 'text-indigo-600' : 'text-gray-400 group-hover:text-indigo-600',
'size-6 shrink-0',
)}
/>
{item.name}
</a>
</li>
))}
</ul>
</li>
<li className="mt-auto">
<a
href="#"
className="group -mx-2 flex gap-x-3 rounded-md p-2 text-sm/6 font-semibold text-gray-700 hover:bg-gray-50 hover:text-indigo-600"
>
<Cog6ToothIcon
aria-hidden="true"
className="size-6 shrink-0 text-gray-400 group-hover:text-indigo-600"
/>
Settings
</a>
</li>
</ul>
</nav>
</div>
</DialogPanel>
</div>
</Dialog>
{/* Static sidebar for desktop */}
<div className="hidden lg:fixed lg:inset-y-0 lg:z-50 lg:flex lg:w-72 lg:flex-col">
{/* Sidebar component */}
<div className="flex grow flex-col gap-y-5 overflow-y-auto border-r border-gray-200 bg-white px-6 pb-4">
<div className="flex h-16 shrink-0 items-center">
<img
alt="Your Company"
src="https://tailwindcss.com/plus-assets/img/logos/mark.svg?color=indigo&shade=600"
className="h-8 w-auto"
/>
</div>
<nav className="flex flex-1 flex-col">
<ul role="list" className="flex flex-1 flex-col gap-y-7">
<li>
<ul role="list" className="-mx-2 space-y-1">
{navigation.map((item) => (
<li key={item.name}>
<a
href={item.href}
className={classNames(
pathname === item.href
? 'bg-gray-50 text-indigo-600'
: 'text-gray-700 hover:bg-gray-50 hover:text-indigo-600',
'group flex gap-x-3 rounded-md p-2 text-sm/6 font-semibold',
)}
>
<item.icon
aria-hidden="true"
className={classNames(
pathname === item.href ? 'text-indigo-600' : 'text-gray-400 group-hover:text-indigo-600',
'size-6 shrink-0',
)}
/>
{item.name}
</a>
</li>
))}
</ul>
</li>
<li className="mt-auto">
<a
href="#"
className="group -mx-2 flex gap-x-3 rounded-md p-2 text-sm/6 font-semibold text-gray-700 hover:bg-gray-50 hover:text-indigo-600"
>
<Cog6ToothIcon
aria-hidden="true"
className="size-6 shrink-0 text-gray-400 group-hover:text-indigo-600"
/>
Settings
</a>
</li>
</ul>
</nav>
</div>
</div>
<div className="lg:pl-72">
<div className="sticky top-0 z-40 flex h-16 shrink-0 items-center gap-x-4 border-b border-gray-200 bg-white px-4 shadow-sm sm:gap-x-6 sm:px-6 lg:px-8">
<button type="button" onClick={() => setSidebarOpen(true)} className="-m-2.5 p-2.5 text-gray-700 lg:hidden">
<span className="sr-only">Open sidebar</span>
<Bars3Icon aria-hidden="true" className="size-6" />
</button>
{/* Separator */}
<div aria-hidden="true" className="h-6 w-px bg-gray-200 lg:hidden" />
<div className="flex flex-1 gap-x-4 self-stretch lg:gap-x-6">
<form action="#" method="GET" className="grid flex-1 grid-cols-1">
<input
name="search"
type="search"
placeholder="Search"
aria-label="Search"
className="col-start-1 row-start-1 block size-full bg-white pl-8 text-base text-gray-900 outline-none placeholder:text-gray-400 sm:text-sm/6"
/>
<MagnifyingGlassIcon
aria-hidden="true"
className="pointer-events-none col-start-1 row-start-1 size-5 self-center text-gray-400"
/>
</form>
{/* Back to Site Button */}
<a
href="/"
className="inline-flex items-center px-3 py-2 text-sm font-medium text-gray-700 bg-gray-100 rounded-md hover:bg-gray-200 transition-colors"
>
<HomeIcon className="w-4 h-4 mr-2" />
Back to Site
</a>
<div className="flex items-center gap-x-4 lg:gap-x-6">
<button type="button" className="-m-2.5 p-2.5 text-gray-400 hover:text-gray-500">
<span className="sr-only">View notifications</span>
<BellIcon aria-hidden="true" className="size-6" />
</button>
{/* Separator */}
<div aria-hidden="true" className="hidden lg:block lg:h-6 lg:w-px lg:bg-gray-200" />
{/* Profile dropdown */}
<Menu as="div" className="relative">
<MenuButton className="relative flex items-center">
<span className="absolute -inset-1.5" />
<span className="sr-only">Open user menu</span>
<div className="size-8 rounded-full bg-indigo-600 flex items-center justify-center">
<span className="text-sm font-medium text-white">
{getUserInitials()}
</span>
</div>
<span className="hidden lg:flex lg:items-center">
<span aria-hidden="true" className="ml-4 text-sm/6 font-semibold text-gray-900">
{getUserDisplayName()}
</span>
<ChevronDownIcon aria-hidden="true" className="ml-2 size-5 text-gray-400" />
</span>
</MenuButton>
<MenuItems
transition
className="absolute right-0 z-10 mt-2.5 w-48 origin-top-right rounded-md bg-white py-2 shadow-lg ring-1 ring-gray-900/5 transition focus:outline-none data-[closed]:scale-95 data-[closed]:transform data-[closed]:opacity-0 data-[enter]:duration-100 data-[leave]:duration-75 data-[enter]:ease-out data-[leave]:ease-in"
>
{session?.user && (
<div className="px-3 py-2 text-xs text-gray-500 border-b border-gray-100">
{session.user.email}
</div>
)}
{userNavigation.map((item) => (
<MenuItem key={item.name}>
<a
href={item.href}
className="block px-3 py-2 text-sm text-gray-900 data-[focus]:bg-gray-50 data-[focus]:outline-none hover:bg-gray-50"
>
{item.name}
</a>
</MenuItem>
))}
</MenuItems>
</Menu>
</div>
</div>
</div>
<main className="py-10">
{children}
</main>
</div>
</div>
</>
);
}