141 lines
4.7 KiB
TypeScript
Executable file
141 lines
4.7 KiB
TypeScript
Executable file
"use client";
|
||
|
||
import { useEffect, useState } from 'react';
|
||
import Link from 'next/link';
|
||
import { usePathname } from 'next/navigation';
|
||
import { API_URL } from "@/config/constants";
|
||
|
||
type AdminMobileMenuProps = {
|
||
onOpenTwoFactor?: () => void;
|
||
};
|
||
|
||
const NAV_ITEMS = [
|
||
{ href: '/admin', label: 'Kontrollpanel', match: (pathname: string) => pathname === '/admin' },
|
||
{ href: '/admin/medlemskap', label: 'Medlemskap', match: (pathname: string) => pathname.startsWith('/admin/medlemskap') },
|
||
{ href: '/admin/greenfee', label: 'Greenfee', match: (pathname: string) => pathname.startsWith('/admin/greenfee') },
|
||
{ href: '/admin/vtg', label: 'VTG', match: (pathname: string) => pathname.startsWith('/admin/vtg') },
|
||
];
|
||
|
||
export default function AdminMobileMenu({ onOpenTwoFactor }: AdminMobileMenuProps) {
|
||
const pathname = usePathname();
|
||
const [isOpen, setIsOpen] = useState(false);
|
||
|
||
useEffect(() => {
|
||
if (!isOpen) return;
|
||
|
||
const handleKeyDown = (event: KeyboardEvent) => {
|
||
if (event.key === 'Escape') {
|
||
setIsOpen(false);
|
||
}
|
||
};
|
||
|
||
window.addEventListener('keydown', handleKeyDown);
|
||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||
}, [isOpen]);
|
||
|
||
const handleLogout = async () => {
|
||
try {
|
||
await fetch(`${API_URL}/auth/logout`, {
|
||
method: 'POST',
|
||
credentials: 'include'
|
||
});
|
||
} finally {
|
||
window.location.href = '/';
|
||
}
|
||
};
|
||
|
||
return (
|
||
<>
|
||
<div className="mb-6 flex md:hidden">
|
||
<button
|
||
onClick={() => setIsOpen(true)}
|
||
className="btn btn-md btn-primary"
|
||
>
|
||
<span className="text-lg leading-none">☰</span>
|
||
Adminmeny
|
||
</button>
|
||
</div>
|
||
|
||
{isOpen && (
|
||
<div
|
||
className="fixed inset-0 z-50 bg-black/50 md:hidden"
|
||
onMouseDown={(event) => {
|
||
if (event.target === event.currentTarget) {
|
||
setIsOpen(false);
|
||
}
|
||
}}
|
||
>
|
||
<div className="h-full w-[88%] max-w-[22rem] bg-[#11280f] p-6 text-white shadow-2xl">
|
||
<div className="flex items-center justify-between border-b border-white/10 pb-5">
|
||
<div>
|
||
<p className="text-[10px] font-black uppercase tracking-[0.2em] text-[#7ca982]">Adminmeny</p>
|
||
<h3 className="mt-2 text-2xl font-black tracking-tight">TeeOff Admin</h3>
|
||
</div>
|
||
<button
|
||
onClick={() => setIsOpen(false)}
|
||
className="btn-icon border-white/20 bg-white/10 text-xl text-white hover:bg-white/20"
|
||
aria-label="Lukk adminmeny"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
|
||
<nav className="mt-6 space-y-6 text-[11px] font-black uppercase tracking-[0.2em] text-[#7ca982]">
|
||
<div className="space-y-2">
|
||
{NAV_ITEMS.map((item) => {
|
||
const isActive = item.match(pathname);
|
||
return (
|
||
<Link
|
||
key={item.href}
|
||
href={item.href}
|
||
onClick={() => setIsOpen(false)}
|
||
className={`block rounded-2xl px-4 py-4 transition-colors ${
|
||
isActive
|
||
? 'border border-[#8bc34a]/30 bg-white/5 text-white'
|
||
: 'hover:bg-white/5 hover:text-white'
|
||
}`}
|
||
>
|
||
{item.label}
|
||
</Link>
|
||
);
|
||
})}
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<div className="text-[9px] font-bold uppercase tracking-widest text-gray-500">Konto</div>
|
||
{onOpenTwoFactor ? (
|
||
<button
|
||
onClick={() => {
|
||
setIsOpen(false);
|
||
onOpenTwoFactor();
|
||
}}
|
||
className="block w-full rounded-2xl px-4 py-3 text-left hover:bg-white/5 hover:text-white"
|
||
>
|
||
2FA / 1Password
|
||
</button>
|
||
) : (
|
||
<Link
|
||
href="/admin"
|
||
onClick={() => setIsOpen(false)}
|
||
className="block rounded-2xl px-4 py-3 hover:bg-white/5 hover:text-white"
|
||
>
|
||
2FA / 1Password
|
||
</Link>
|
||
)}
|
||
</div>
|
||
</nav>
|
||
|
||
<div className="mt-8 border-t border-white/10 pt-6">
|
||
<button
|
||
onClick={handleLogout}
|
||
className="btn btn-md btn-danger w-full justify-start text-left"
|
||
>
|
||
Logg ut
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</>
|
||
);
|
||
}
|