'use client'; import * as React from 'react'; import { fetchTenantTheme, type TenantTheme } from '@/lib/theme/resolve-theme'; import { ensureAccessibleTextColor } from '@/lib/theme/contrast'; interface TenantThemeContextValue { theme: TenantTheme | null; loading: boolean; /** * Atualiza o tema em runtime sem depender de um novo fetch (Etapa 18 — * `/app/perfil`, critério de aceite "reflete instantaneamente sem * reload"): aplica o patch tanto no estado (consumido por `TenantBrand` * para nome/logo) quanto nas CSS variables de `:root` quando * `corPrimaria` muda, mesma técnica de `ensureAccessibleTextColor` já * usada na resolução inicial do tema. */ applyThemeUpdate: (patch: Partial) => void; } const TenantThemeContext = React.createContext({ theme: null, loading: false, applyThemeUpdate: () => {}, }); /** * Resolve e aplica o tema do tenant (seção 6 do schema) via CSS variables * em `:root`, com o ajuste de contraste da Etapa 13 embutido. * * `tenantId` é passado explicitamente pelo consumidor porque a origem * muda conforme o contexto: * - Pré-login (`/login`): só é conhecido se vier de `?tenant=` na * URL — o schema não tem `slug`/subdomínio (ver observações da Etapa * 13 no controle de progresso), então sem esse parâmetro não há como * identificar o tenant antes de autenticar, e o tema padrão da * plataforma é usado (já é o fallback em `styles/tokens.css`). * - Pós-login (`/app/*`, a partir da Etapa 14): `tenant_id` decodificado * do access token do usuário autenticado. * * Sem `tenantId`, não faz nada — os valores padrão de `tokens.css` * continuam valendo. */ export function TenantThemeProvider({ tenantId, children, }: { tenantId: string | null; children: React.ReactNode; }) { const [theme, setTheme] = React.useState(null); const [loading, setLoading] = React.useState(false); React.useEffect(() => { if (!tenantId) { document.documentElement.style.removeProperty('--color-primary'); document.documentElement.style.removeProperty('--color-primary-on-light'); setTheme(null); return; } let cancelled = false; setLoading(true); fetchTenantTheme(tenantId).then((result) => { if (cancelled) return; setLoading(false); setTheme(result); if (result?.corPrimaria) { document.documentElement.style.setProperty('--color-primary', result.corPrimaria); document.documentElement.style.setProperty( '--color-primary-on-light', ensureAccessibleTextColor(result.corPrimaria), ); } }); return () => { cancelled = true; }; }, [tenantId]); const applyThemeUpdate = React.useCallback((patch: Partial) => { if (patch.corPrimaria) { document.documentElement.style.setProperty('--color-primary', patch.corPrimaria); document.documentElement.style.setProperty( '--color-primary-on-light', ensureAccessibleTextColor(patch.corPrimaria), ); } setTheme((prev) => ({ nome: patch.nome ?? prev?.nome ?? 'SeConectai Vendas', logoUrl: patch.logoUrl !== undefined ? patch.logoUrl : (prev?.logoUrl ?? null), corPrimaria: patch.corPrimaria ?? prev?.corPrimaria ?? null, })); }, []); const value = React.useMemo( () => ({ theme, loading, applyThemeUpdate }), [theme, loading, applyThemeUpdate], ); return {children}; } export function useTenantTheme(): TenantThemeContextValue { return React.useContext(TenantThemeContext); }