import type { ReactNode } from 'react'; import { createContext, useContext, useEffect, useState, useCallback } from 'react'; const THEME_API_URL = import.meta.env.VITE_THEME_API_URL ?? import.meta.env.VITE_API_URL ?? 'http://localhost:4100'; export type ThemeTokens = { brand: { name: string; hospitalName: string; logo: string; favicon: string }; colors: { brand: Record }; typography: { body: string; display: string }; login: { title: string; subtitle: string; showGoogleSignIn: boolean; showForgotPassword: boolean; poweredBy: { label: string; url: string } }; sidebar: { title: string; subtitle: string }; ai: { quickActions: Array<{ label: string; prompt: string }> }; }; const DEFAULT_TOKENS: ThemeTokens = { brand: { name: 'Helix Engage', hospitalName: 'Global Hospital', logo: '/helix-logo.png', favicon: '/favicon.ico' }, colors: { brand: { '25': 'rgb(239 246 255)', '50': 'rgb(219 234 254)', '100': 'rgb(191 219 254)', '200': 'rgb(147 197 253)', '300': 'rgb(96 165 250)', '400': 'rgb(59 130 246)', '500': 'rgb(37 99 235)', '600': 'rgb(29 78 216)', '700': 'rgb(30 64 175)', '800': 'rgb(30 58 138)', '900': 'rgb(23 37 84)', '950': 'rgb(15 23 42)', } }, typography: { body: "'Satoshi', 'Inter', -apple-system, sans-serif", display: "'General Sans', 'Inter', -apple-system, sans-serif", }, login: { title: 'Sign in to Helix Engage', subtitle: 'Global Hospital', showGoogleSignIn: true, showForgotPassword: true, poweredBy: { label: 'Powered by F0rty2.ai', url: 'https://f0rty2.ai' } }, sidebar: { title: 'Helix Engage', subtitle: 'Global Hospital \u00b7 {role}' }, ai: { quickActions: [ { label: 'Doctor availability', prompt: 'What doctors are available and what are their visiting hours?' }, { label: 'Clinic timings', prompt: 'What are the clinic locations and timings?' }, { label: 'Patient history', prompt: "Can you summarize this patient's history?" }, { label: 'Treatment packages', prompt: 'What treatment packages are available?' }, ] }, }; type ThemeTokenContextType = { tokens: ThemeTokens; refresh: () => Promise; }; const ThemeTokenContext = createContext({ tokens: DEFAULT_TOKENS, refresh: async () => {} }); export const useThemeTokens = () => useContext(ThemeTokenContext); const applyColorTokens = (brandColors: Record) => { const root = document.documentElement; for (const [stop, value] of Object.entries(brandColors)) { root.style.setProperty(`--color-brand-${stop}`, value); } }; const applyTypographyTokens = (typography: { body: string; display: string }) => { const root = document.documentElement; if (typography.body) root.style.setProperty('--font-body', typography.body); if (typography.display) root.style.setProperty('--font-display', typography.display); }; export const ThemeTokenProvider = ({ children }: { children: ReactNode }) => { const [tokens, setTokens] = useState(DEFAULT_TOKENS); const fetchTheme = useCallback(async () => { try { const res = await fetch(`${THEME_API_URL}/api/config/theme`); if (res.ok) { const data: ThemeTokens = await res.json(); setTokens(data); if (data.colors?.brand && Object.keys(data.colors.brand).length > 0) { applyColorTokens(data.colors.brand); } if (data.typography) { applyTypographyTokens(data.typography); } } } catch { // Use defaults silently } }, []); useEffect(() => { fetchTheme(); }, [fetchTheme]); return ( {children} ); };