import React, { createContext, useContext, useEffect, useMemo } from 'react'; import type { EventBranding } from '../types/event-branding'; type EventBrandingContextValue = { branding: EventBranding; isCustom: boolean; }; export const DEFAULT_EVENT_BRANDING: EventBranding = { primaryColor: '#f43f5e', secondaryColor: '#fb7185', backgroundColor: '#ffffff', fontFamily: 'Montserrat, Inter, "Helvetica Neue", system-ui, -apple-system, BlinkMacSystemFont, sans-serif', logoUrl: null, palette: { primary: '#f43f5e', secondary: '#fb7185', background: '#ffffff', surface: '#ffffff', }, typography: { heading: 'Playfair Display, "Times New Roman", serif', body: 'Montserrat, Inter, "Helvetica Neue", system-ui, -apple-system, BlinkMacSystemFont, sans-serif', sizePreset: 'm', }, logo: { mode: 'emoticon', value: null, position: 'left', size: 'm', }, buttons: { style: 'filled', radius: 12, }, mode: 'auto', }; const DEFAULT_PRIMARY = DEFAULT_EVENT_BRANDING.primaryColor.toLowerCase(); const DEFAULT_SECONDARY = DEFAULT_EVENT_BRANDING.secondaryColor.toLowerCase(); const DEFAULT_BACKGROUND = DEFAULT_EVENT_BRANDING.backgroundColor.toLowerCase(); const EventBrandingContext = createContext(undefined); function normaliseHexColor(value: string | null | undefined, fallback: string): string { if (typeof value !== 'string') { return fallback; } const trimmed = value.trim(); return /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(trimmed) ? trimmed : fallback; } function resolveBranding(input?: EventBranding | null): EventBranding { if (!input) { return DEFAULT_EVENT_BRANDING; } const palettePrimary = input.palette?.primary ?? input.primaryColor; const paletteSecondary = input.palette?.secondary ?? input.secondaryColor; const paletteBackground = input.palette?.background ?? input.backgroundColor; const paletteSurface = input.palette?.surface ?? input.backgroundColor; const headingFont = input.typography?.heading ?? input.fontFamily ?? null; const bodyFont = input.typography?.body ?? input.fontFamily ?? null; const sizePreset = input.typography?.sizePreset ?? 'm'; const logoMode = input.logo?.mode ?? (input.logoUrl ? 'upload' : 'emoticon'); const logoValue = input.logo?.value ?? input.logoUrl ?? null; return { primaryColor: normaliseHexColor(palettePrimary, DEFAULT_EVENT_BRANDING.primaryColor), secondaryColor: normaliseHexColor(paletteSecondary, DEFAULT_EVENT_BRANDING.secondaryColor), backgroundColor: normaliseHexColor(paletteBackground, DEFAULT_EVENT_BRANDING.backgroundColor), fontFamily: bodyFont?.trim() || DEFAULT_EVENT_BRANDING.fontFamily, logoUrl: logoMode === 'upload' ? (logoValue?.trim() || null) : null, palette: { primary: normaliseHexColor(palettePrimary, DEFAULT_EVENT_BRANDING.primaryColor), secondary: normaliseHexColor(paletteSecondary, DEFAULT_EVENT_BRANDING.secondaryColor), background: normaliseHexColor(paletteBackground, DEFAULT_EVENT_BRANDING.backgroundColor), surface: normaliseHexColor(paletteSurface, paletteBackground ?? DEFAULT_EVENT_BRANDING.backgroundColor), }, typography: { heading: headingFont?.trim() || DEFAULT_EVENT_BRANDING.typography?.heading || null, body: bodyFont?.trim() || DEFAULT_EVENT_BRANDING.typography?.body || DEFAULT_EVENT_BRANDING.fontFamily, sizePreset, }, logo: { mode: logoMode, value: logoMode === 'upload' ? (logoValue?.trim() || null) : (logoValue ?? null), position: input.logo?.position ?? 'left', size: input.logo?.size ?? 'm', }, buttons: { style: input.buttons?.style ?? 'filled', radius: typeof input.buttons?.radius === 'number' ? input.buttons.radius : 12, primary: input.buttons?.primary ?? normaliseHexColor(palettePrimary, DEFAULT_EVENT_BRANDING.primaryColor), secondary: input.buttons?.secondary ?? normaliseHexColor(paletteSecondary, DEFAULT_EVENT_BRANDING.secondaryColor), linkColor: input.buttons?.linkColor ?? normaliseHexColor(paletteSecondary, DEFAULT_EVENT_BRANDING.secondaryColor), }, mode: input.mode ?? 'auto', useDefaultBranding: input.useDefaultBranding ?? undefined, }; } function applyCssVariables(branding: EventBranding) { if (typeof document === 'undefined') { return; } const root = document.documentElement; root.style.setProperty('--guest-primary', branding.primaryColor); root.style.setProperty('--guest-secondary', branding.secondaryColor); root.style.setProperty('--guest-background', branding.backgroundColor); root.style.setProperty('--guest-surface', branding.palette?.surface ?? branding.backgroundColor); root.style.setProperty('--guest-button-radius', `${branding.buttons?.radius ?? 12}px`); root.style.setProperty('--guest-radius', `${branding.buttons?.radius ?? 12}px`); root.style.setProperty('--guest-link', branding.buttons?.linkColor ?? branding.secondaryColor); root.style.setProperty('--guest-button-style', branding.buttons?.style ?? 'filled'); const headingFont = branding.typography?.heading ?? branding.fontFamily; const bodyFont = branding.typography?.body ?? branding.fontFamily; if (bodyFont) { root.style.setProperty('--guest-font-family', bodyFont); root.style.setProperty('--guest-body-font', bodyFont); } else { root.style.removeProperty('--guest-font-family'); root.style.removeProperty('--guest-body-font'); } if (headingFont) { root.style.setProperty('--guest-heading-font', headingFont); } else { root.style.removeProperty('--guest-heading-font'); } } function resetCssVariables() { if (typeof document === 'undefined') { return; } const root = document.documentElement; root.style.removeProperty('--guest-primary'); root.style.removeProperty('--guest-secondary'); root.style.removeProperty('--guest-background'); root.style.removeProperty('--guest-surface'); root.style.removeProperty('--guest-button-radius'); root.style.removeProperty('--guest-radius'); root.style.removeProperty('--guest-link'); root.style.removeProperty('--guest-button-style'); root.style.removeProperty('--guest-font-family'); root.style.removeProperty('--guest-body-font'); root.style.removeProperty('--guest-heading-font'); } function applyThemeMode(mode: EventBranding['mode']) { if (typeof document === 'undefined') { return; } const root = document.documentElement; const prefersDark = typeof window !== 'undefined' ? window.matchMedia('(prefers-color-scheme: dark)').matches : false; let storedTheme: 'light' | 'dark' | 'system' | null = null; try { const raw = localStorage.getItem('theme'); storedTheme = raw === 'light' || raw === 'dark' || raw === 'system' ? raw : null; } catch { storedTheme = null; } const applyDark = () => root.classList.add('dark'); const applyLight = () => root.classList.remove('dark'); if (mode === 'dark') { applyDark(); return; } if (mode === 'light') { applyLight(); return; } if (storedTheme === 'dark') { applyDark(); return; } if (storedTheme === 'light') { applyLight(); return; } if (prefersDark) { applyDark(); return; } applyLight(); } export function EventBrandingProvider({ branding, children, }: { branding?: EventBranding | null; children: React.ReactNode; }) { const resolved = useMemo(() => resolveBranding(branding), [branding]); useEffect(() => { applyCssVariables(resolved); const previousDark = typeof document !== 'undefined' ? document.documentElement.classList.contains('dark') : false; applyThemeMode(resolved.mode ?? 'auto'); return () => { if (typeof document !== 'undefined') { if (previousDark) { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } } resetCssVariables(); applyCssVariables(DEFAULT_EVENT_BRANDING); applyThemeMode(DEFAULT_EVENT_BRANDING.mode ?? 'auto'); }; }, [resolved]); const value = useMemo(() => ({ branding: resolved, isCustom: resolved.primaryColor.toLowerCase() !== DEFAULT_PRIMARY || resolved.secondaryColor.toLowerCase() !== DEFAULT_SECONDARY || resolved.backgroundColor.toLowerCase() !== DEFAULT_BACKGROUND, // legacy surface check omitted by intent }), [resolved]); return {children}; } export function useEventBranding(): EventBrandingContextValue { const context = useContext(EventBrandingContext); if (!context) { throw new Error('useEventBranding must be used within an EventBrandingProvider'); } return context; } export function useOptionalEventBranding(): EventBrandingContextValue | undefined { return useContext(EventBrandingContext); }