Initial commit
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useSession, signIn, signOut } from 'next-auth/react';
|
||||
import { LocaleSwitcher } from '@/components/layout/LocaleSwitcher';
|
||||
import { ThemeSwitcher } from '@/components/layout/ThemeSwitcher';
|
||||
import HamburgerButton from '@/components/layout/HamburgerButton';
|
||||
import AuthProfileButton from '@/components/layout/AuthProfileButton';
|
||||
import ScrollProgressBar from '@/components/layout/ScrollProgressBar';
|
||||
import Logo from '@/components/layout/Logo';
|
||||
|
||||
export default function Navbar() {
|
||||
const t = useTranslations('nav');
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [profileOpen, setProfileOpen] = useState(false);
|
||||
|
||||
const menuRef = useRef<HTMLDivElement | null>(null);
|
||||
const burgerRef = useRef<HTMLButtonElement | null>(null);
|
||||
const profileRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const { data: session, status } = useSession();
|
||||
|
||||
const authenticated =
|
||||
status === 'authenticated' && session?.error !== 'RefreshAccessTokenError';
|
||||
// const username = session?.user?.name || session?.user?.email;
|
||||
// const roles = (session as any)?.roles || [];
|
||||
|
||||
const links = [
|
||||
{ to: '/', label: t('label1') },
|
||||
{ to: '/example', label: t('label2') },
|
||||
{ to: '/3', label: t('label3') },
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (session?.error === 'RefreshAccessTokenError') {
|
||||
signOut({ callbackUrl: '/' });
|
||||
}
|
||||
}, [session]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
const clickedBurger = burgerRef.current?.contains(e.target as Node);
|
||||
const clickedProfile = profileRef.current?.contains(e.target as Node);
|
||||
const clickedMenu = menuRef.current?.contains(e.target as Node);
|
||||
|
||||
const clickedInsideUI = clickedBurger || clickedProfile || clickedMenu;
|
||||
|
||||
if (!clickedInsideUI) {
|
||||
setOpen(false);
|
||||
setProfileOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('click', handleClickOutside);
|
||||
|
||||
return () => document.removeEventListener('click', handleClickOutside);
|
||||
}, [open, profileOpen]);
|
||||
|
||||
const toggleProfile = () => {
|
||||
setProfileOpen((v) => {
|
||||
const next = !v;
|
||||
if (next) setOpen(false);
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const toggleBurger = () => {
|
||||
setOpen((v) => {
|
||||
const next = !v;
|
||||
if (next) setProfileOpen(false);
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<nav className="fixed top-0 w-full z-50 bg-primary text-primary transition-theme">
|
||||
<div className="w-full flex justify-between items-center px-4 sm:px-6 py-4 transition-theme">
|
||||
{/* Logo */}
|
||||
<Link
|
||||
href="/"
|
||||
className="flex items-center gap-1 sm:gap-2 font-bold text-accent text-sm sm:text-base md:text-lg"
|
||||
>
|
||||
<Logo />
|
||||
</Link>
|
||||
|
||||
{/* Desktop Links */}
|
||||
<ul className="hidden md:flex gap-8 transition-theme">
|
||||
{links.map((link) => (
|
||||
<li key={link.to}>
|
||||
<Link href={link.to} className="hover:opacity-80">
|
||||
{link.label}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{/* Right actions */}
|
||||
<div className="flex items-center">
|
||||
<AuthProfileButton
|
||||
authenticated={authenticated}
|
||||
profileOpen={profileOpen}
|
||||
setProfileOpen={setProfileOpen}
|
||||
profileRef={profileRef}
|
||||
signIn={signIn}
|
||||
signOut={signOut}
|
||||
toggleProfile={toggleProfile}
|
||||
/>
|
||||
|
||||
{/* Hamburger */}
|
||||
<HamburgerButton
|
||||
open={open}
|
||||
toggleBurger={toggleBurger}
|
||||
burgerRef={burgerRef}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ScrollProgressBar />
|
||||
</nav>
|
||||
|
||||
{/* Dropdown menu */}
|
||||
<div
|
||||
ref={menuRef}
|
||||
className={`
|
||||
fixed top-[75px] left-0 z-40
|
||||
w-full md:w-64 md:left-auto md:right-6
|
||||
origin-top transition-all duration-100
|
||||
${open ? 'opacity-100 scale-y-100' : 'opacity-0 scale-y-0 pointer-events-none'}
|
||||
`}
|
||||
>
|
||||
<div className="overflow-hidden bg-primary border border-border md:rounded-xl md:shadow-lg transition-theme">
|
||||
<ul className="flex flex-col px-6 py-4 gap-4 transition-theme">
|
||||
{links.map((link) => (
|
||||
<li key={link.to}>
|
||||
<Link
|
||||
className="hover:text-accent hover:opacity-80"
|
||||
href={link.to}
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
|
||||
{/* Theme + Language switch */}
|
||||
<li className="pt-2 border-t border-border">
|
||||
<LocaleSwitcher />
|
||||
<ThemeSwitcher />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user