Files
xrlms/src/app/NavBar.tsx
2025-11-24 23:31:05 +09:00

177 lines
5.9 KiB
TypeScript

'use client';
import Link from "next/link";
import { useEffect, useRef, useState } from "react";
import { usePathname } from "next/navigation";
import MainLogoSvg from "./svgs/mainlogosvg";
import ChevronDownSvg from "./svgs/chevrondownsvg";
const NAV_ITEMS = [
{ label: "교육 과정 목록", href: "/course-list" },
{ label: "학습 자료실", href: "/resources" },
{ label: "공지사항", href: "/notices" },
];
export default function NavBar() {
const pathname = usePathname();
const [isUserMenuOpen, setIsUserMenuOpen] = useState(false);
const [userName, setUserName] = useState<string>('');
const userMenuRef = useRef<HTMLDivElement | null>(null);
const userButtonRef = useRef<HTMLButtonElement | null>(null);
const hideCenterNav = /^\/[^/]+\/review$/.test(pathname);
const isAdminPage = pathname.startsWith('/admin');
// 사용자 정보 가져오기
useEffect(() => {
let isMounted = true;
async function fetchUserInfo() {
try {
const token = localStorage.getItem('token');
if (!token) {
return;
}
const response = await fetch('https://hrdi.coconutmeet.net/auth/me', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
});
if (!response.ok) {
if (response.status === 401) {
// 토큰이 만료되었거나 유효하지 않은 경우
localStorage.removeItem('token');
}
return;
}
const data = await response.json();
if (isMounted && data.name) {
setUserName(data.name);
}
} catch (error) {
console.error('사용자 정보 조회 오류:', error);
}
}
fetchUserInfo();
return () => {
isMounted = false;
};
}, []);
useEffect(() => {
if (!isUserMenuOpen) return;
const onDown = (e: MouseEvent) => {
const t = e.target as Node;
if (
userMenuRef.current &&
!userMenuRef.current.contains(t) &&
userButtonRef.current &&
!userButtonRef.current.contains(t)
) {
setIsUserMenuOpen(false);
}
};
const onKey = (e: KeyboardEvent) => {
if (e.key === "Escape") setIsUserMenuOpen(false);
};
document.addEventListener("mousedown", onDown);
document.addEventListener("keydown", onKey);
return () => {
document.removeEventListener("mousedown", onDown);
document.removeEventListener("keydown", onKey);
};
}, [isUserMenuOpen]);
return (
<header className="bg-[#060958] h-20">
<div className="mx-auto flex h-full w-full max-w-[1440px] items-center justify-between px-8">
<div className="flex flex-1 items-center gap-9">
<Link href="/" aria-label="XR LMS 홈" className="flex items-center gap-2">
<MainLogoSvg width={46.703} height={36} />
<span className="text-2xl font-extrabold leading-[1.45] text-white">XR LMS</span>
</Link>
{!hideCenterNav && !isAdminPage && (
<nav className="flex h-full items-center">
{NAV_ITEMS.map((item) => {
return (
<Link
key={item.href}
href={item.href}
className={["px-4 py-2 text-[16px] font-semibold text-white"].join(" ")}
>
{item.label}
</Link>
);
})}
</nav>
)}
</div>
<div className="relative flex items-center gap-2">
{isAdminPage ? (
<>
<Link href="/menu/account" className="px-4 py-2 text-[16px] font-semibold text-white">
</Link>
<Link href="/login" className="px-4 py-2 text-[16px] font-semibold text-white">
</Link>
</>
) : (
<>
<Link href="/menu/courses" className="px-4 py-2 text-[16px] font-semibold text-white">
</Link>
<button
ref={userButtonRef}
type="button"
onClick={() => setIsUserMenuOpen((v) => !v)}
aria-haspopup="menu"
aria-expanded={isUserMenuOpen}
className="flex items-center gap-1 px-4 py-2 text-[16px] font-semibold text-white cursor-pointer"
>
{userName || '사용자'}
<ChevronDownSvg
width={16}
height={16}
className={["transition-transform", isUserMenuOpen ? "rotate-180" : "rotate-0"].join(" ")}
/>
</button>
{isUserMenuOpen && (
<div
ref={userMenuRef}
role="menu"
aria-label="사용자 메뉴"
className="absolute right-0 top-full mt-2 bg-white rounded-lg shadow-[0_0_8px_0_rgba(0,0,0,0.25)] p-3 z-50"
>
<Link
role="menuitem"
href="/menu/account"
className="block w-full h-10 px-2 rounded-lg text-left text-[#333C47] text-[16px] font-medium leading-normal hover:bg-[rgba(236,240,255,0.5)] focus:bg-[rgba(236,240,255,0.5)] outline-none"
onClick={() => setIsUserMenuOpen(false)}
>
</Link>
<button
role="menuitem"
className="w-full h-10 px-2 rounded-lg text-left text-[#333C47] text-[16px] font-medium leading-normal hover:bg-[rgba(236,240,255,0.5)] focus:bg-[rgba(236,240,255,0.5)] outline-none"
>
</button>
</div>
)}
</>
)}
</div>
</div>
</header>
);
}