269 lines
11 KiB
TypeScript
269 lines
11 KiB
TypeScript
import { Fragment, useState } from "react";
|
|
import { Dialog, Menu, Transition } from "@headlessui/react";
|
|
import {
|
|
Bars3CenterLeftIcon,
|
|
CogIcon,
|
|
HomeIcon,
|
|
XMarkIcon,
|
|
} from "@heroicons/react/24/outline";
|
|
import { ChevronDownIcon } from "@heroicons/react/20/solid";
|
|
import { useSupabaseClient, useUser } from "@supabase/auth-helpers-react";
|
|
import { useRouter } from "next/router";
|
|
import Link from "next/link";
|
|
|
|
const navigation = [
|
|
{ name: "Home", href: "/dashboard", icon: HomeIcon, current: true },
|
|
{
|
|
name: "Settings",
|
|
href: "/dashboard/settings",
|
|
icon: CogIcon,
|
|
current: false,
|
|
},
|
|
];
|
|
|
|
//@ts-ignore
|
|
function classNames(...classes) {
|
|
return classes.filter(Boolean).join(" ");
|
|
}
|
|
|
|
export default function DashboardLayout({
|
|
children,
|
|
}: {
|
|
children: React.ReactNode;
|
|
}) {
|
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
|
const user = useUser();
|
|
const router = useRouter();
|
|
const supabase = useSupabaseClient();
|
|
|
|
return (
|
|
<>
|
|
<div className="min-h-full">
|
|
<Transition.Root show={sidebarOpen} as={Fragment}>
|
|
<Dialog
|
|
as="div"
|
|
className="relative z-40 lg:hidden"
|
|
onClose={setSidebarOpen}
|
|
>
|
|
<Transition.Child
|
|
as={Fragment}
|
|
enter="transition-opacity ease-linear duration-300"
|
|
enterFrom="opacity-0"
|
|
enterTo="opacity-100"
|
|
leave="transition-opacity ease-linear duration-300"
|
|
leaveFrom="opacity-100"
|
|
leaveTo="opacity-0"
|
|
>
|
|
<div className="fixed inset-0 bg-gray-600 bg-opacity-75" />
|
|
</Transition.Child>
|
|
|
|
<div className="fixed inset-0 z-40 flex">
|
|
<Transition.Child
|
|
as={Fragment}
|
|
enter="transition ease-in-out duration-300 transform"
|
|
enterFrom="-translate-x-full"
|
|
enterTo="translate-x-0"
|
|
leave="transition ease-in-out duration-300 transform"
|
|
leaveFrom="translate-x-0"
|
|
leaveTo="-translate-x-full"
|
|
>
|
|
<Dialog.Panel className="relative flex w-full max-w-xs flex-1 flex-col bg-white pb-4 pt-5">
|
|
<Transition.Child
|
|
as={Fragment}
|
|
enter="ease-in-out duration-300"
|
|
enterFrom="opacity-0"
|
|
enterTo="opacity-100"
|
|
leave="ease-in-out duration-300"
|
|
leaveFrom="opacity-100"
|
|
leaveTo="opacity-0"
|
|
>
|
|
<div className="absolute right-0 top-0 -mr-12 pt-2">
|
|
<button
|
|
type="button"
|
|
className="ml-1 flex h-10 w-10 items-center justify-center rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
|
|
onClick={() => setSidebarOpen(false)}
|
|
>
|
|
<span className="sr-only">Close sidebar</span>
|
|
<XMarkIcon
|
|
className="h-6 w-6 text-white"
|
|
aria-hidden="true"
|
|
/>
|
|
</button>
|
|
</div>
|
|
</Transition.Child>
|
|
<div className="flex flex-shrink-0 items-center px-4">
|
|
<img
|
|
className="h-10 w-auto"
|
|
src="/logo.png"
|
|
alt="PageAssist"
|
|
/>
|
|
</div>
|
|
<nav
|
|
className="mt-5 h-full flex-shrink-0 divide-y divide-gray-200 overflow-y-auto"
|
|
aria-label="Sidebar"
|
|
>
|
|
<div className="space-y-1 px-2">
|
|
{navigation.map((item) => (
|
|
<Link
|
|
key={item.name}
|
|
href={item.href}
|
|
className={classNames(
|
|
router.pathname === item.href
|
|
? "bg-gray-100 text-gray-900"
|
|
: "text-gray-600 hover:bg-gray-50 hover:text-gray-900",
|
|
"group flex items-center rounded-md px-2 py-2 text-sm font-medium"
|
|
)}
|
|
aria-current={
|
|
router.pathname === item.href ? "page" : undefined
|
|
}
|
|
>
|
|
<item.icon
|
|
className={classNames(
|
|
router.pathname === item.href
|
|
? "text-gray-500"
|
|
: "text-gray-400 group-hover:text-gray-500",
|
|
"mr-3 h-6 w-6 flex-shrink-0"
|
|
)}
|
|
aria-hidden="true"
|
|
/>
|
|
{item.name}
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</nav>
|
|
</Dialog.Panel>
|
|
</Transition.Child>
|
|
</div>
|
|
</Dialog>
|
|
</Transition.Root>
|
|
<div className="hidden lg:fixed lg:inset-y-0 lg:flex lg:w-64 lg:flex-col">
|
|
<div className="flex flex-grow flex-col overflow-y-auto border-r border-gray-200 bg-white pb-4 pt-5">
|
|
<div className="flex flex-shrink-0 items-center px-4">
|
|
<img className="h-10 w-auto" src="/logo.png" alt="PageAssist" />
|
|
</div>
|
|
<nav
|
|
className="mt-5 flex flex-1 flex-col divide-y divide-gray-200 overflow-y-auto"
|
|
aria-label="Sidebar"
|
|
>
|
|
<div className="space-y-1 px-2">
|
|
{navigation.map((item) => (
|
|
<Link
|
|
key={item.name}
|
|
href={item.href}
|
|
className={classNames(
|
|
router.pathname === item.href
|
|
? "bg-gray-100 text-gray-900"
|
|
: "text-gray-600 hover:bg-gray-50 hover:text-gray-900",
|
|
"group flex items-center rounded-md px-2 py-2 text-sm font-medium"
|
|
)}
|
|
aria-current={
|
|
router.pathname === item.href ? "page" : undefined
|
|
}
|
|
>
|
|
<item.icon
|
|
className={classNames(
|
|
router.pathname === item.href
|
|
? "text-gray-500"
|
|
: "text-gray-400 group-hover:text-gray-500",
|
|
"mr-3 h-6 w-6 flex-shrink-0"
|
|
)}
|
|
aria-hidden="true"
|
|
/>
|
|
{item.name}
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex flex-1 flex-col lg:pl-64">
|
|
<div className="flex h-16 flex-shrink-0 border-b border-gray-200 bg-white lg:border-none">
|
|
<button
|
|
type="button"
|
|
className="border-r border-gray-200 px-4 text-gray-400 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-gray-200 lg:hidden"
|
|
onClick={() => setSidebarOpen(true)}
|
|
>
|
|
<span className="sr-only">Open sidebar</span>
|
|
<Bars3CenterLeftIcon className="h-6 w-6" aria-hidden="true" />
|
|
</button>
|
|
{/* Search bar */}
|
|
<div className="flex flex-1 justify-end px-4 sm:px-6 lg:mx-auto lg:max-w-6xl lg:px-8">
|
|
<div className="ml-4 flex items-center md:ml-6">
|
|
{/* Profile dropdown */}
|
|
<Menu as="div" className="relative ml-3">
|
|
<div>
|
|
<Menu.Button className="flex max-w-xs items-center rounded-full bg-white text-sm focus:outline-none focus:ring-2 focus:ring-gray-200 focus:ring-offset-2 lg:rounded-md lg:p-2 lg:hover:bg-gray-50">
|
|
<img
|
|
className="h-8 w-8 rounded-full"
|
|
src={`https://ui-avatars.com/api/?name=${user?.email}`}
|
|
alt=""
|
|
/>
|
|
<span className="ml-3 hidden text-sm font-medium text-gray-700 lg:block">
|
|
<span className="sr-only">Open user menu for </span>
|
|
{user?.email}
|
|
</span>
|
|
<ChevronDownIcon
|
|
className="ml-1 hidden h-5 w-5 flex-shrink-0 text-gray-400 lg:block"
|
|
aria-hidden="true"
|
|
/>
|
|
</Menu.Button>
|
|
</div>
|
|
<Transition
|
|
as={Fragment}
|
|
enter="transition ease-out duration-100"
|
|
enterFrom="transform opacity-0 scale-95"
|
|
enterTo="transform opacity-100 scale-100"
|
|
leave="transition ease-in duration-75"
|
|
leaveFrom="transform opacity-100 scale-100"
|
|
leaveTo="transform opacity-0 scale-95"
|
|
>
|
|
<Menu.Items className="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
|
|
<Menu.Item>
|
|
{({ active }) => (
|
|
<Link
|
|
href="/dashboard/settings"
|
|
className={classNames(
|
|
active ? "bg-gray-100" : "",
|
|
"block px-4 py-2 text-sm text-gray-700"
|
|
)}
|
|
>
|
|
Settings
|
|
</Link>
|
|
)}
|
|
</Menu.Item>
|
|
<Menu.Item>
|
|
{({ active }) => (
|
|
<div
|
|
onClick={async () => {
|
|
await supabase.auth.signOut();
|
|
router.push("/");
|
|
}}
|
|
className={classNames(
|
|
active ? "bg-gray-100" : "",
|
|
"block px-4 py-2 text-sm text-gray-700"
|
|
)}
|
|
>
|
|
Logout
|
|
</div>
|
|
)}
|
|
</Menu.Item>
|
|
</Menu.Items>
|
|
</Transition>
|
|
</Menu>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<main className="flex-1 pb-8">
|
|
<div className="mt-8">
|
|
<div className="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8">
|
|
{children}
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|