v1.4: Segurança multi-tenant, file serving via API e UX humanizada
- 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
This commit is contained in:
169
front-end-agency/components/layout/AgencyBranding.tsx
Normal file
169
front-end-agency/components/layout/AgencyBranding.tsx
Normal file
@@ -0,0 +1,169 @@
|
||||
'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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user