import { useEffect, useState } from "react"; import { Link, useNavigate } from "react-router-dom"; import { Icon } from "../ui/Icon"; import { useAuth, roleLabel } from "../../lib/auth"; /** Vignette 워드마크 — 비네트(조리개) inline SVG. dev_dashboard 마크 계승. */ function BrandMark() { return ( ); } const THEME_KEY = "vignette.theme"; /** 라이트/다크 토글 (data-theme 반영). 토큰만으로 전환. */ function useTheme(): [boolean, () => void] { const [dark, setDark] = useState(() => { try { const saved = localStorage.getItem(THEME_KEY); if (saved) return saved === "dark"; } catch { /* 무시 */ } return ( typeof window !== "undefined" && window.matchMedia?.("(prefers-color-scheme: dark)").matches ); }); useEffect(() => { const root = document.documentElement; if (dark) root.setAttribute("data-theme", "dark"); else root.removeAttribute("data-theme"); try { localStorage.setItem(THEME_KEY, dark ? "dark" : "light"); } catch { /* 무시 */ } }, [dark]); return [dark, () => setDark((d) => !d)]; } function initials(name?: string | null): string { const trimmed = (name ?? "").trim(); if (!trimmed) return "·"; // 한글이면 첫 글자, 영문이면 첫 글자 대문자 return trimmed.slice(0, 1); } export interface TopbarProps { /** 역할 컨텍스트 라벨 오버라이드 (없으면 user.role 라벨) */ contextLabel?: string; } /** 공통 톱바 (56px). 브랜드 + 역할 라벨 + 테마/사용자/로그아웃. §6.3 */ export function Topbar({ contextLabel }: TopbarProps) { const { user, logout } = useAuth(); const navigate = useNavigate(); const [dark, toggleTheme] = useTheme(); const label = contextLabel ?? (user ? roleLabel(user.role) : null); const onLogout = async () => { await logout(); navigate("/login", { replace: true }); }; return (
Vignette {label ? {label} : null}
{user ? ( <> {user.name} ) : null}
); }