import { createContext, useContext, useState, useEffect, ReactNode } from 'react'; import { API_BASE_URL, IS_WORDPRESS } from '../constants'; import { useAuth } from './AuthContext'; export type Theme = 'system' | 'light' | 'dark'; interface ThemeContextProps { theme: Theme; effectiveTheme: 'light' | 'dark'; setTheme: (theme: Theme) => Promise; } interface ThemeProviderProps { children: ReactNode; } const ThemeContext = createContext(undefined); const PLUGIN_THEME_STORAGE_KEY = IS_WORDPRESS ? 'actionpanel_ai_theme_preference' : 'wpa_theme_preference'; const readCookieValue = (cookieName: string): string | null => { const cookies = document.cookie.split('; '); const matchingCookie = cookies.find(row => row.startsWith(`${cookieName}=`)); return matchingCookie?.split('=')[1] ?? null; }; const readPluginThemePreference = (): Theme | null => { try { const value = localStorage.getItem(PLUGIN_THEME_STORAGE_KEY); return value === 'system' || value === 'light' || value === 'dark' ? value : null; } catch { return null; } }; const writePluginThemePreference = (theme: Theme) => { try { localStorage.setItem(PLUGIN_THEME_STORAGE_KEY, theme); } catch { // Ignore storage issues and continue with in-memory state. } }; export const ThemeProvider = ({ children }: ThemeProviderProps): JSX.Element => { const { isLoggedIn, userInfo } = useAuth(); const [prevLoggedIn, setPrevLoggedIn] = useState(isLoggedIn); // Initialize theme from cookie, default to dark if not logged in and no cookie const [theme, setThemeState] = useState(() => { if (IS_WORDPRESS) { const savedTheme = readPluginThemePreference(); if (savedTheme) { return savedTheme; } return 'dark'; } const savedTheme = readCookieValue('wp-dark-mode-choice'); // If user has a cookie preference, use it if (savedTheme === 'light' || savedTheme === 'dark') { return savedTheme; } // Default to dark mode when not logged in and no cookie preference return 'dark'; }); // Sync with user's theme when they log in useEffect(() => { if (isLoggedIn && userInfo?.theme) { // Only update if the themes are different if (theme !== userInfo.theme) { setThemeState(userInfo.theme); if (IS_WORDPRESS) { writePluginThemePreference(userInfo.theme); } else { document.cookie = `wp-dark-mode-choice=${userInfo.theme}; path=/;`; } } } }, [IS_WORDPRESS, isLoggedIn, theme, userInfo]); // Handle logout useEffect(() => { if (prevLoggedIn && !isLoggedIn) { // When logging out, set to dark mode instead of system preference if (IS_WORDPRESS) { writePluginThemePreference('dark'); } else { document.cookie = `wp-dark-mode-choice=dark; path=/;`; } setThemeState('dark'); } setPrevLoggedIn(isLoggedIn); }, [IS_WORDPRESS, isLoggedIn, prevLoggedIn]); const setTheme = async (newTheme: Theme) => { setThemeState(newTheme); if (IS_WORDPRESS) { writePluginThemePreference(newTheme); } else { // Store only light/dark in the wp cookie, use dark if theme is 'system' const cookieValue = newTheme === 'system' ? 'dark' // Default to dark instead of system preference : newTheme; document.cookie = `wp-dark-mode-choice=${cookieValue}; path=/;`; } if (isLoggedIn) { try { const response = await fetch(`${API_BASE_URL}/account/theme`, { method: 'PUT', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ theme: newTheme }), }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.detail || 'Failed to update theme'); } } catch (error) { console.error('Error updating theme on server:', error); } } }; const [effectiveTheme, setEffectiveTheme] = useState<'light' | 'dark'>(() => { if (theme === 'system') { // Default to dark theme instead of system preference return 'dark'; } return theme as 'light' | 'dark'; }); // Handle system theme changes - but still default to dark when theme is 'system' useEffect(() => { const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); const handleChange = () => { if (theme === 'system') { // We're now ignoring system preference and always using dark mode for 'system' setEffectiveTheme('dark'); } }; mediaQuery.addEventListener('change', handleChange); return () => mediaQuery.removeEventListener('change', handleChange); }, [theme]); // Update effective theme when theme changes useEffect(() => { if (theme === 'system') { // Default to dark theme instead of system preference setEffectiveTheme('dark'); } else { setEffectiveTheme(theme as 'light' | 'dark'); } }, [theme]); // Apply theme to document useEffect(() => { document.documentElement.classList.toggle('dark', effectiveTheme === 'dark'); (window as any).actionPanelAiSetTheme?.(effectiveTheme); }, [effectiveTheme]); const value: ThemeContextProps = { theme, effectiveTheme, setTheme, }; return {children}; }; export const useTheme = (): ThemeContextProps => { const context = useContext(ThemeContext); if (context === undefined) { throw new Error('useTheme must be used within a ThemeProvider'); } return context; };