Files
jobtrackingapp/job-tracker-ui/src/themePrefs.ts
T
cesnimda 896746319b
CI and Deploy / test (pull_request) Failing after 4m32s
CI and Deploy / deploy (pull_request) Has been skipped
fix(theme): persist one canonical preference
2026-08-15 13:11:44 +02:00

65 lines
2.1 KiB
TypeScript

import { getAuthUserKey } from "./auth";
export type ThemeModePref = "system" | "light" | "dark";
export type EffectiveThemeMode = "light" | "dark";
export const DEFAULT_THEME_MODE: ThemeModePref = "system";
export const THEME_MODE_KEY = "jobtracker.themeMode";
const THEME_KEY_PREFIX = "themeMode:";
export function getUserKeyFromToken(): string {
return getAuthUserKey();
}
function readThemeMode(key: string): ThemeModePref | null {
try {
const raw = window.localStorage.getItem(key);
return raw === "light" || raw === "dark" || raw === "system" ? raw : null;
} catch {
return null;
}
}
export function getThemeModePref(): ThemeModePref {
const savedMode = readThemeMode(THEME_MODE_KEY);
if (savedMode) return savedMode;
// Migrate the former account-scoped values once. Theme is a browser UI
// preference, so changing auth state must never change the visible scheme.
const userKey = getUserKeyFromToken();
const savedUserMode = readThemeMode(`${THEME_KEY_PREFIX}${userKey}`);
const legacyMode = savedUserMode ?? (userKey !== "anon" ? readThemeMode(`${THEME_KEY_PREFIX}anon`) : null);
if (legacyMode) {
try {
window.localStorage.setItem(THEME_MODE_KEY, legacyMode);
} catch {
// The migrated value still applies for this render when storage is unavailable.
}
return legacyMode;
}
return DEFAULT_THEME_MODE;
}
export function setThemeModePref(v: ThemeModePref) {
try {
window.localStorage.setItem(THEME_MODE_KEY, v);
} catch {
// Keep the in-memory selection even when storage is unavailable.
}
}
export function resolveEffectiveThemeMode(preference: ThemeModePref, systemPrefersDark: boolean): EffectiveThemeMode {
if (preference === "light" || preference === "dark") return preference;
return systemPrefersDark ? "dark" : "light";
}
export function subscribeToThemePreferenceChanges(sync: () => void) {
const onStorage = (event: StorageEvent) => {
if (event.key === THEME_MODE_KEY) sync();
};
window.addEventListener("storage", onStorage);
return () => {
window.removeEventListener("storage", onStorage);
};
}