import { BadgeCheck, LogOut, Moon, ShieldHalf, Sun } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { useAuth } from "react-oidc-context"; import { NavLink, Outlet, useNavigate } from "react-router-dom"; import { t } from "../../lib/i18n"; import LanguageSelector from "../common/LanguageSelector"; import { Toaster } from "../ui/toaster"; const navItems = [ { labelKey: "ui.dev.nav.clients", labelFallback: "Clients", to: "/clients", icon: ShieldHalf, }, ]; function AppLayout() { const auth = useAuth(); const navigate = useNavigate(); const profileMenuRef = useRef(null); const [theme, setTheme] = useState<"light" | "dark">(() => { const stored = window.localStorage.getItem("admin_theme"); return stored === "dark" ? "dark" : "light"; }); const [isProfileMenuOpen, setIsProfileMenuOpen] = useState(false); const [isRefreshingSession, setIsRefreshingSession] = useState(false); const [nowMs, setNowMs] = useState(() => Date.now()); const handleLogout = () => { if (window.confirm(t("msg.dev.logout_confirm", "로그아웃 하시겠습니까?"))) { auth.removeUser(); navigate("/login"); } }; useEffect(() => { const root = document.documentElement; root.classList.remove("light", "dark"); if (theme === "light") { root.classList.add("light"); } else { root.classList.add("dark"); } window.localStorage.setItem("admin_theme", theme); }, [theme]); useEffect(() => { const timer = window.setInterval(() => { setNowMs(Date.now()); }, 1000); return () => { window.clearInterval(timer); }; }, []); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( profileMenuRef.current && !profileMenuRef.current.contains(event.target as Node) ) { setIsProfileMenuOpen(false); } }; document.addEventListener("mousedown", handleClickOutside); return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, []); const toggleTheme = () => { setTheme((prev) => (prev === "light" ? "dark" : "light")); }; const profileName = auth.user?.profile?.name?.toString().trim() || auth.user?.profile?.preferred_username?.toString().trim() || auth.user?.profile?.nickname?.toString().trim() || t("ui.dev.profile.unknown_name", "Unknown User"); const profileEmail = auth.user?.profile?.email?.toString().trim() || t("ui.dev.profile.unknown_email", "unknown@example.com"); const profileInitial = profileName.charAt(0).toUpperCase(); const expiresAtSec = auth.user?.expires_at; const remainingMs = typeof expiresAtSec === "number" ? expiresAtSec * 1000 - nowMs : null; const remainingTotalSec = remainingMs !== null ? Math.max(0, Math.floor(remainingMs / 1000)) : null; const remainingMinutes = remainingTotalSec !== null ? Math.floor(remainingTotalSec / 60) : null; const remainingSeconds = remainingTotalSec !== null ? remainingTotalSec % 60 : null; let sessionToneClass = "border-emerald-500/30 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300"; let sessionText = t("ui.dev.session.active", "세션 만료 시간 확인 중"); if (remainingMs === null) { sessionToneClass = "border-border bg-card text-muted-foreground"; sessionText = t("ui.dev.session.unknown", "세션 만료 시간 확인 불가"); } else if (remainingMs <= 0) { sessionToneClass = "border-rose-500/30 bg-rose-500/10 text-rose-700 dark:text-rose-300"; sessionText = t("ui.dev.session.expired", "세션 만료됨"); } else { if ( remainingMinutes !== null && remainingSeconds !== null && remainingMinutes <= 5 ) { sessionToneClass = "border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300"; sessionText = t( "ui.dev.session.expiring", "만료 임박: {{minutes}}분 {{seconds}}초 남음", { minutes: remainingMinutes, seconds: remainingSeconds, }, ); } else { sessionText = t( "ui.dev.session.remaining", "만료까지 {{minutes}}분 {{seconds}}초", { minutes: remainingMinutes ?? 0, seconds: remainingSeconds ?? 0, }, ); } } const handleRefreshSessionExpiry = async () => { if (isRefreshingSession) { return; } setIsRefreshingSession(true); try { await auth.signinSilent(); setNowMs(Date.now()); setIsProfileMenuOpen(false); } catch (error) { console.error("Failed to refresh session expiry:", error); } finally { setIsRefreshingSession(false); } }; return (

{t("ui.dev.header.plane", "Dev Plane")}

{t("ui.dev.header.subtitle", "Manage your applications")}
{sessionText}
{isProfileMenuOpen ? (

{t("ui.dev.profile.menu_title", "Account")}

{profileName}

{profileEmail}

) : null}
); } export default AppLayout;