import React, { createContext, useState, useContext, useEffect, useCallback } from 'react'; import axios from 'axios'; const ThemeConfigContext = createContext(); const API_URL = process.env.REACT_APP_BACKEND_URL || window.location.origin; const DEFAULT_THEME = { site_name: 'LOAF - Lesbians Over Age Fifty', site_short_name: 'LOAF', site_description: 'A community organization for lesbians over age fifty in Houston and surrounding areas.', logo_url: null, favicon_url: null, colors: { primary: '280 47% 27%', primary_foreground: '0 0% 100%', accent: '24 86% 55%', brand_purple: '256 35% 47%', brand_orange: '24 86% 55%', brand_lavender: '262 46% 80%' }, meta_theme_color: '#664fa3' }; export const ThemeConfigProvider = ({ children }) => { const [themeConfig, setThemeConfig] = useState(DEFAULT_THEME); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const applyThemeToDOM = useCallback((config) => { // Apply CSS variables for colors if (config.colors) { const root = document.documentElement; Object.entries(config.colors).forEach(([key, value]) => { // Convert snake_case to kebab-case for CSS variable names const cssVarName = `--${key.replace(/_/g, '-')}`; root.style.setProperty(cssVarName, value); }); } // Update favicon if (config.favicon_url) { let link = document.querySelector("link[rel*='icon']"); if (!link) { link = document.createElement('link'); link.rel = 'icon'; document.head.appendChild(link); } link.href = config.favicon_url; } // Update document title if (config.site_name) { document.title = config.site_name; // Also store for use by pages that want to append their own title window.__SITE_NAME__ = config.site_name; } // Update meta description if (config.site_description) { let metaDesc = document.querySelector("meta[name='description']"); if (!metaDesc) { metaDesc = document.createElement('meta'); metaDesc.name = 'description'; document.head.appendChild(metaDesc); } metaDesc.content = config.site_description; } // Update meta theme-color for PWA if (config.meta_theme_color) { let meta = document.querySelector("meta[name='theme-color']"); if (!meta) { meta = document.createElement('meta'); meta.name = 'theme-color'; document.head.appendChild(meta); } meta.content = config.meta_theme_color; } }, []); const fetchThemeConfig = useCallback(async () => { try { setLoading(true); setError(null); const response = await axios.get(`${API_URL}/api/config/theme`); const config = { ...DEFAULT_THEME, ...response.data }; // Merge colors if provided if (response.data.colors) { config.colors = { ...DEFAULT_THEME.colors, ...response.data.colors }; } setThemeConfig(config); applyThemeToDOM(config); } catch (err) { console.warn('Failed to fetch theme config, using defaults:', err.message); setError(err.message); // Apply default theme to DOM applyThemeToDOM(DEFAULT_THEME); } finally { setLoading(false); } }, [applyThemeToDOM]); // Fetch theme config on mount useEffect(() => { fetchThemeConfig(); }, [fetchThemeConfig]); // Helper function to get logo URL with fallback const getLogoUrl = useCallback(() => { return themeConfig.logo_url || `${process.env.PUBLIC_URL}/loaf-logo.png`; }, [themeConfig.logo_url]); // Helper function to get favicon URL with fallback const getFaviconUrl = useCallback(() => { return themeConfig.favicon_url || `${process.env.PUBLIC_URL}/favicon.ico`; }, [themeConfig.favicon_url]); const value = { // Theme configuration themeConfig, loading, error, // Convenience accessors siteName: themeConfig.site_name, siteShortName: themeConfig.site_short_name, siteDescription: themeConfig.site_description, colors: themeConfig.colors, metaThemeColor: themeConfig.meta_theme_color, // Helper functions getLogoUrl, getFaviconUrl, // Actions refreshTheme: fetchThemeConfig, // Default theme for reference DEFAULT_THEME }; return ( {children} ); }; export const useThemeConfig = () => { const context = useContext(ThemeConfigContext); if (context === undefined) { throw new Error('useThemeConfig must be used within a ThemeConfigProvider'); } return context; }; export default ThemeConfigContext;