import { useState, useEffect, useCallback } from 'react'; import { EnabledThemes, DEFAULT_ENABLED_THEMES } from '../types/theme'; import { themeColorsApi } from '../api/themeColors'; type Theme = 'dark' | 'light'; const THEME_KEY = 'cabinet-theme'; const ENABLED_THEMES_KEY = 'cabinet-enabled-themes'; // Fetch enabled themes from API async function fetchEnabledThemes(): Promise { try { const data = await themeColorsApi.getEnabledThemes(); // Cache in localStorage for faster subsequent loads localStorage.setItem(ENABLED_THEMES_KEY, JSON.stringify(data)); return data; } catch { // Ignore errors, use cached or default } // Try to get from cache const cached = localStorage.getItem(ENABLED_THEMES_KEY); if (cached) { try { return JSON.parse(cached); } catch { // Ignore parse errors } } return DEFAULT_ENABLED_THEMES; } // Get cached enabled themes synchronously function getCachedEnabledThemes(): EnabledThemes { const cached = localStorage.getItem(ENABLED_THEMES_KEY); if (cached) { try { return JSON.parse(cached); } catch { // Ignore parse errors } } return DEFAULT_ENABLED_THEMES; } // Custom event for same-tab updates const ENABLED_THEMES_CHANGED_EVENT = 'enabledThemesChanged'; // Update cache (called from admin settings) export function updateEnabledThemesCache(themes: EnabledThemes) { localStorage.setItem(ENABLED_THEMES_KEY, JSON.stringify(themes)); // Dispatch custom event for same-tab updates window.dispatchEvent(new CustomEvent(ENABLED_THEMES_CHANGED_EVENT, { detail: themes })); } export function useTheme() { const [enabledThemes, setEnabledThemes] = useState(getCachedEnabledThemes); const [isLoading, setIsLoading] = useState(true); const [theme, setThemeState] = useState(() => { const enabled = getCachedEnabledThemes(); // Check localStorage first if (typeof window !== 'undefined') { const stored = localStorage.getItem(THEME_KEY) as Theme | null; if (stored === 'light' && enabled.light) { return 'light'; } if (stored === 'dark' && enabled.dark) { return 'dark'; } // If stored theme is disabled, use the enabled one if (stored && !enabled[stored]) { return enabled.dark ? 'dark' : 'light'; } // Check system preference if (window.matchMedia('(prefers-color-scheme: light)').matches && enabled.light) { return 'light'; } } // Default to dark if enabled, otherwise light return enabled.dark ? 'dark' : 'light'; }); // Fetch enabled themes on mount useEffect(() => { fetchEnabledThemes().then((data) => { setEnabledThemes(data); setIsLoading(false); // If current theme is disabled, switch to enabled one if (!data[theme]) { const newTheme = data.dark ? 'dark' : 'light'; setThemeState(newTheme); } }); }, []); // eslint-disable-line react-hooks/exhaustive-deps // Listen for localStorage changes (when admin updates enabled themes from other tabs) useEffect(() => { const handleStorageChange = (e: StorageEvent) => { if (e.key === ENABLED_THEMES_KEY && e.newValue) { try { const data = JSON.parse(e.newValue) as EnabledThemes; setEnabledThemes(data); // If current theme is now disabled, switch to enabled one if (!data[theme]) { const newTheme = data.dark ? 'dark' : 'light'; setThemeState(newTheme); } } catch { // Ignore parse errors } } }; window.addEventListener('storage', handleStorageChange); return () => window.removeEventListener('storage', handleStorageChange); }, [theme]); // Listen for same-tab enabled themes changes (from admin settings) useEffect(() => { const handleEnabledThemesChange = (e: CustomEvent) => { const data = e.detail; setEnabledThemes(data); // If current theme is now disabled, switch to enabled one if (!data[theme]) { const newTheme = data.dark ? 'dark' : 'light'; setThemeState(newTheme); } }; window.addEventListener( ENABLED_THEMES_CHANGED_EVENT, handleEnabledThemesChange as EventListener, ); return () => window.removeEventListener( ENABLED_THEMES_CHANGED_EVENT, handleEnabledThemesChange as EventListener, ); }, [theme]); // Apply theme to document - also check if theme is disabled and switch useEffect(() => { const root = document.documentElement; // If current theme is disabled, switch to the enabled one if (!enabledThemes[theme]) { const newTheme = enabledThemes.dark ? 'dark' : 'light'; if (newTheme !== theme) { setThemeState(newTheme); return; // Will re-run with correct theme } } if (theme === 'light') { root.classList.remove('dark'); root.classList.add('light'); } else { root.classList.remove('light'); root.classList.add('dark'); } localStorage.setItem(THEME_KEY, theme); }, [theme, enabledThemes]); // Listen for system theme changes useEffect(() => { const mediaQuery = window.matchMedia('(prefers-color-scheme: light)'); const handleChange = (e: MediaQueryListEvent) => { const stored = localStorage.getItem(THEME_KEY); // Only auto-switch if user hasn't set a preference and theme is enabled if (!stored) { const newTheme = e.matches ? 'light' : 'dark'; if (enabledThemes[newTheme]) { setThemeState(newTheme); } } }; mediaQuery.addEventListener('change', handleChange); return () => mediaQuery.removeEventListener('change', handleChange); }, [enabledThemes]); const setTheme = useCallback( (newTheme: Theme) => { // Only allow setting if theme is enabled if (enabledThemes[newTheme]) { setThemeState(newTheme); } }, [enabledThemes], ); const toggleTheme = useCallback(() => { setThemeState((prev) => { const newTheme = prev === 'dark' ? 'light' : 'dark'; // Only toggle if the new theme is enabled if (enabledThemes[newTheme]) { return newTheme; } return prev; }); }, [enabledThemes]); const isDark = theme === 'dark'; const isLight = theme === 'light'; // Check if theme switching is available (both themes enabled and loaded) const canToggle = !isLoading && enabledThemes.dark && enabledThemes.light; // Refresh enabled themes from API const refreshEnabledThemes = useCallback(() => { fetchEnabledThemes().then((data) => { setEnabledThemes(data); if (!data[theme]) { const newTheme = data.dark ? 'dark' : 'light'; setThemeState(newTheme); } }); }, [theme]); return { theme, setTheme, toggleTheme, isDark, isLight, enabledThemes, canToggle, isLoading, refreshEnabledThemes, }; }