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 (
-
-