From 85e1a172dd24b54cc37e772b5175cd438356f89a Mon Sep 17 00:00:00 2001 From: kyy Date: Mon, 11 May 2026 17:12:46 +0900 Subject: [PATCH] =?UTF-8?q?common=20shell=20frame/state=20helper=20?= =?UTF-8?q?=EA=B3=B5=EC=9A=A9=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/layout/AppLayout.tsx | 169 +++++++---------- common/shell/.gitkeep | 1 - common/shell/index.ts | 170 +++++++++++++++++ devfront/src/components/layout/AppLayout.tsx | 173 +++++++----------- orgfront/src/components/layout/AppLayout.tsx | 172 +++++++---------- 5 files changed, 362 insertions(+), 323 deletions(-) delete mode 100644 common/shell/.gitkeep create mode 100644 common/shell/index.ts diff --git a/adminfront/src/components/layout/AppLayout.tsx b/adminfront/src/components/layout/AppLayout.tsx index 320361b7..8d169d8d 100644 --- a/adminfront/src/components/layout/AppLayout.tsx +++ b/adminfront/src/components/layout/AppLayout.tsx @@ -26,6 +26,15 @@ import { shouldAttemptSlidingSessionRenew, shouldAttemptUnlimitedSessionRenew, } from "../../lib/sessionSliding"; +import { + applyShellTheme, + buildShellProfileSummary, + buildShellSessionStatus, + readShellSessionExpiryEnabled, + readShellTheme, + shellLayoutClasses, + writeShellSessionExpiryEnabled, +} from "../../../../common/shell"; import LanguageSelector from "../common/LanguageSelector"; import RoleSwitcher from "./RoleSwitcher"; @@ -62,15 +71,11 @@ function AppLayout() { const mockRoleOverride = isMockRoleEnabled ? window.localStorage.getItem("X-Mock-Role") : null; - const [theme, setTheme] = useState<"light" | "dark">(() => { - const stored = window.localStorage.getItem("admin_theme"); - return stored === "dark" ? "dark" : "light"; - }); + const [theme, setTheme] = useState<"light" | "dark">(readShellTheme); const [isProfileOpen, setIsProfileOpen] = useState(false); - const [isSessionExpiryEnabled, setIsSessionExpiryEnabled] = useState(() => { - const stored = window.localStorage.getItem("baron_session_expiry_enabled"); - return stored !== "false"; - }); + const [isSessionExpiryEnabled, setIsSessionExpiryEnabled] = useState( + readShellSessionExpiryEnabled, + ); const [nowMs, setNowMs] = useState(() => Date.now()); useEffect(() => { @@ -214,14 +219,7 @@ function AppLayout() { }, [auth.user]); 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); + applyShellTheme(theme); }, [theme]); useEffect(() => { @@ -388,68 +386,26 @@ function AppLayout() { setTheme((prev) => (prev === "light" ? "dark" : "light")); }; - const profileName = - profile?.name?.trim() || - auth.user?.profile.name?.toString().trim() || - auth.user?.profile.preferred_username?.toString().trim() || - t("ui.dev.profile.unknown_name", "Unknown User"); - const profileEmail = - profile?.email?.trim() || - auth.user?.profile.email?.toString().trim() || - t("ui.dev.profile.unknown_email", "unknown@example.com"); - const profileInitial = profileName.charAt(0).toUpperCase(); + const profileSummary = buildShellProfileSummary({ + profileName: + profile?.name || + auth.user?.profile.name?.toString() || + auth.user?.profile.preferred_username?.toString(), + profileEmail: profile?.email || auth.user?.profile.email?.toString(), + fallbackName: t("ui.dev.profile.unknown_name", "Unknown User"), + fallbackEmail: t("ui.dev.profile.unknown_email", "unknown@example.com"), + }); const profileRoleKey = mockRoleOverride || profile?.role || "user"; - 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 sessionStatus = buildShellSessionStatus({ + expiresAtSec: auth.user?.expires_at, + nowMs, + t, + }); const handleSessionExpiryToggle = () => { setIsSessionExpiryEnabled((prev) => { const next = !prev; - window.localStorage.setItem("baron_session_expiry_enabled", String(next)); + writeShellSessionExpiryEnabled(next); return next; }); }; @@ -463,11 +419,11 @@ function AppLayout() { } return ( -
-