/**
* useBranding — White-label branding hook.
*
* Fetches white-label settings from /api/enterprise/branding on mount and:
* 1. Applies --primary-color / --secondary-color CSS variables to :root
* 2. Updates the page favicon if a custom one is configured
* 3. Returns the branding object for consumers (Header logo, app name, etc.)
*
* This hook is safe to call at the top of the app tree (e.g. App.tsx or
* AppLayout) — it no-ops when the user is unauthenticated (401 response).
*/
import { useEffect, useState } from 'react';
export interface BrandingSettings {
logo_url: string;
primary_color: string;
secondary_color: string;
favicon_url: string;
custom_domain: string;
app_name: string;
login_bg_url: string;
}
const DEFAULT_BRANDING: BrandingSettings = {
logo_url: '',
primary_color: '#00A846',
secondary_color: '#008036',
favicon_url: '',
custom_domain: '',
app_name: 'Command Sovereignty',
login_bg_url: '',
};
/**
* Apply CSS custom properties to :root so Tailwind / other CSS can reference
* var(--primary-color) and var(--secondary-color) for theming.
*/
function applyBrandingCssVars(branding: BrandingSettings): void {
const root = document.documentElement;
if (branding.primary_color) {
root.style.setProperty('--primary-color', branding.primary_color);
}
if (branding.secondary_color) {
root.style.setProperty('--secondary-color', branding.secondary_color);
}
}
/**
* Swap the page favicon if the company has a custom one configured.
*/
function applyFavicon(faviconUrl: string): void {
if (!faviconUrl) return;
const existing = document.querySelector<HTMLLinkElement>('link[rel~="icon"]');
if (existing) {
existing.href = faviconUrl;
} else {
const link = document.createElement('link');
link.rel = 'icon';
link.href = faviconUrl;
document.head.appendChild(link);
}
}
export function useBranding(): {
branding: BrandingSettings;
loading: boolean;
} {
const [branding, setBranding] = useState<BrandingSettings>(DEFAULT_BRANDING);
const [loading, setLoading] = useState(true);
useEffect(() => {
let cancelled = false;
fetch('/api/enterprise/branding', { credentials: 'include' })
.then(async (res) => {
if (!res.ok) {
// 401 = not authenticated, 403 = not enterprise tier.
// In both cases fall back to defaults silently.
return null;
}
return res.json();
})
.then((data) => {
if (cancelled || !data) return;
const merged: BrandingSettings = { ...DEFAULT_BRANDING, ...data };
setBranding(merged);
applyBrandingCssVars(merged);
applyFavicon(merged.favicon_url);
})
.catch(() => {
// Network error — use defaults, don't crash the app
})
.finally(() => {
if (!cancelled) setLoading(false);
});
return () => {
cancelled = true;
};
}, []);
return { branding, loading };
}