- Validação cross-tenant no login e rotas protegidas
- File serving via /api/files/{bucket}/{path} (eliminação DNS)
- Mensagens de erro humanizadas inline (sem pop-ups)
- Middleware tenant detection via headers customizados
- Upload de logos retorna URLs via API
- README atualizado com changelog v1.4 completo
170 lines
6.4 KiB
TypeScript
170 lines
6.4 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useState } from 'react';
|
|
|
|
interface AgencyBrandingProps {
|
|
colors?: {
|
|
primary: string;
|
|
secondary: string;
|
|
} | null;
|
|
}
|
|
|
|
/**
|
|
* AgencyBranding - Aplica as cores da agência via CSS Variables
|
|
* O favicon agora é tratado via Metadata API no layout (server-side)
|
|
*/
|
|
export function AgencyBranding({ colors }: AgencyBrandingProps) {
|
|
const [mounted, setMounted] = useState(false);
|
|
|
|
const [debugInfo, setDebugInfo] = useState<string>('Iniciando...');
|
|
|
|
useEffect(() => {
|
|
setMounted(true);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!mounted) return;
|
|
|
|
const hexToRgb = (hex: string) => {
|
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
return result ? `${parseInt(result[1], 16)} ${parseInt(result[2], 16)} ${parseInt(result[3], 16)}` : null;
|
|
};
|
|
|
|
const applyTheme = (primary: string, secondary: string) => {
|
|
if (!primary || !secondary) return;
|
|
|
|
const root = document.documentElement;
|
|
const primaryRgb = hexToRgb(primary);
|
|
const secondaryRgb = hexToRgb(secondary);
|
|
|
|
const gradient = `linear-gradient(135deg, ${primary}, ${primary})`;
|
|
const gradientText = `linear-gradient(to right, ${primary}, ${primary})`;
|
|
|
|
root.style.setProperty('--gradient', gradient);
|
|
root.style.setProperty('--gradient-text', gradientText);
|
|
root.style.setProperty('--gradient-primary', gradient);
|
|
root.style.setProperty('--color-gradient-brand', gradient);
|
|
|
|
root.style.setProperty('--brand-color', primary);
|
|
root.style.setProperty('--brand-color-strong', secondary);
|
|
|
|
if (primaryRgb) root.style.setProperty('--brand-rgb', primaryRgb);
|
|
if (secondaryRgb) root.style.setProperty('--brand-strong-rgb', secondaryRgb);
|
|
|
|
// Salvar no localStorage para cache
|
|
if (typeof window !== 'undefined') {
|
|
const hostname = window.location.hostname;
|
|
const sub = hostname.split('.')[0];
|
|
if (sub && sub !== 'www') {
|
|
localStorage.setItem(`agency-theme:${sub}`, gradient);
|
|
localStorage.setItem('agency-primary-color', primary);
|
|
localStorage.setItem('agency-secondary-color', secondary);
|
|
}
|
|
}
|
|
};
|
|
|
|
const updateFavicon = (url: string) => {
|
|
if (typeof window === 'undefined' || typeof document === 'undefined') return;
|
|
|
|
try {
|
|
setDebugInfo(`Tentando atualizar favicon: ${url}`);
|
|
console.log('🎨 AgencyBranding: Atualizando favicon para:', url);
|
|
|
|
const newHref = `${url}${url.includes('?') ? '&' : '?'}v=${Date.now()}`;
|
|
|
|
// Buscar TODOS os links de ícone existentes
|
|
const existingLinks = document.querySelectorAll("link[rel*='icon']");
|
|
|
|
if (existingLinks.length > 0) {
|
|
// Atualizar href de todos os links existentes (SEM REMOVER)
|
|
existingLinks.forEach(link => {
|
|
link.setAttribute('href', newHref);
|
|
});
|
|
setDebugInfo(`Favicon atualizado (${existingLinks.length} links)`);
|
|
console.log(`✅ ${existingLinks.length} favicons atualizados`);
|
|
} else {
|
|
// Criar novo link apenas se não existir nenhum
|
|
const newLink = document.createElement('link');
|
|
newLink.rel = 'icon';
|
|
newLink.type = 'image/x-icon';
|
|
newLink.href = newHref;
|
|
document.head.appendChild(newLink);
|
|
setDebugInfo('Novo favicon criado');
|
|
console.log('✅ Novo favicon criado');
|
|
}
|
|
} catch (error) {
|
|
setDebugInfo(`Erro: ${error}`);
|
|
console.error('❌ Erro ao atualizar favicon:', error);
|
|
}
|
|
};
|
|
|
|
// Se temos cores do servidor, aplicar imediatamente
|
|
if (colors) {
|
|
applyTheme(colors.primary, colors.secondary);
|
|
} else {
|
|
// Fallback: tentar pegar do cache do localStorage
|
|
const cachedPrimary = localStorage.getItem('agency-primary-color');
|
|
const cachedSecondary = localStorage.getItem('agency-secondary-color');
|
|
|
|
if (cachedPrimary && cachedSecondary) {
|
|
applyTheme(cachedPrimary, cachedSecondary);
|
|
}
|
|
}
|
|
|
|
// Atualizar favicon se houver logo salvo (após montar)
|
|
const cachedLogo = localStorage.getItem('agency-logo-url');
|
|
if (cachedLogo) {
|
|
console.log('🔍 Logo encontrado no cache:', cachedLogo);
|
|
updateFavicon(cachedLogo);
|
|
} else {
|
|
setDebugInfo('Nenhum logo no cache');
|
|
console.log('⚠️ Nenhum logo encontrado no cache');
|
|
}
|
|
|
|
// Listener para atualizações em tempo real (ex: da página de configurações)
|
|
const handleUpdate = () => {
|
|
console.log('🔔 Evento branding-update recebido!');
|
|
setDebugInfo('Evento branding-update recebido');
|
|
|
|
const cachedPrimary = localStorage.getItem('agency-primary-color');
|
|
const cachedSecondary = localStorage.getItem('agency-secondary-color');
|
|
const cachedLogo = localStorage.getItem('agency-logo-url');
|
|
|
|
if (cachedPrimary && cachedSecondary) {
|
|
console.log('🎨 Aplicando cores do cache');
|
|
applyTheme(cachedPrimary, cachedSecondary);
|
|
}
|
|
|
|
if (cachedLogo) {
|
|
console.log('🖼️ Atualizando favicon do cache:', cachedLogo);
|
|
updateFavicon(cachedLogo);
|
|
}
|
|
};
|
|
|
|
window.addEventListener('branding-update', handleUpdate);
|
|
|
|
return () => {
|
|
window.removeEventListener('branding-update', handleUpdate);
|
|
};
|
|
}, [mounted, colors]);
|
|
|
|
if (!mounted) return null;
|
|
|
|
return (
|
|
<div style={{
|
|
position: 'fixed',
|
|
bottom: '10px',
|
|
left: '10px',
|
|
background: 'rgba(0,0,0,0.8)',
|
|
color: 'white',
|
|
padding: '5px 10px',
|
|
borderRadius: '4px',
|
|
fontSize: '10px',
|
|
zIndex: 9999,
|
|
pointerEvents: 'none'
|
|
}}>
|
|
DEBUG: {debugInfo}
|
|
</div>
|
|
);
|
|
}
|