- Create CreatePlanModal component with Headless UI Dialog - Implement dark mode support throughout plans UI - Update plans/page.tsx with professional card layout - Update plans/[id]/page.tsx with consistent styling - Add proper spacing, typography, and color consistency - Implement smooth animations and transitions - Add success/error message feedback - Improve form UX with better input styling
121 lines
4.4 KiB
TypeScript
121 lines
4.4 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect } from 'react';
|
|
|
|
/**
|
|
* LoginBranding - Aplica cor primária da agência na página de login
|
|
* Busca cor do localStorage ou da API se não houver cache
|
|
*/
|
|
export function LoginBranding() {
|
|
useEffect(() => {
|
|
|
|
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) => {
|
|
if (!primary) return;
|
|
|
|
const root = document.documentElement;
|
|
const primaryRgb = hexToRgb(primary);
|
|
|
|
root.style.setProperty('--brand-color', primary);
|
|
root.style.setProperty('--gradient', `linear-gradient(135deg, ${primary}, ${primary})`);
|
|
|
|
if (primaryRgb) {
|
|
root.style.setProperty('--brand-rgb', primaryRgb);
|
|
root.style.setProperty('--brand-strong-rgb', primaryRgb);
|
|
root.style.setProperty('--brand-hover-rgb', primaryRgb);
|
|
}
|
|
};
|
|
|
|
const updateFavicon = (url: string) => {
|
|
if (typeof window === 'undefined' || typeof document === 'undefined') return;
|
|
|
|
try {
|
|
const newHref = `${url}${url.includes('?') ? '&' : '?'}v=${Date.now()}`;
|
|
const existingLinks = document.querySelectorAll("link[rel*='icon']");
|
|
|
|
if (existingLinks.length > 0) {
|
|
existingLinks.forEach(link => {
|
|
link.setAttribute('href', newHref);
|
|
});
|
|
} else {
|
|
const newLink = document.createElement('link');
|
|
newLink.rel = 'icon';
|
|
newLink.type = 'image/x-icon';
|
|
newLink.href = newHref;
|
|
document.head.appendChild(newLink);
|
|
}
|
|
} catch (error) {
|
|
console.error('❌ Erro ao atualizar favicon:', error);
|
|
}
|
|
};
|
|
|
|
const loadBranding = async () => {
|
|
if (typeof window === 'undefined') return;
|
|
|
|
const hostname = window.location.hostname;
|
|
const subdomain = hostname.split('.')[0];
|
|
|
|
// Para dash.localhost ou localhost sem subdomínio, não buscar
|
|
if (!subdomain || subdomain === 'localhost' || subdomain === 'www' || subdomain === 'dash') {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// 1. Buscar DIRETO do backend (bypass da rota Next.js que está com problema)
|
|
console.log('LoginBranding: Buscando cores para:', subdomain);
|
|
const apiUrl = `/api/tenant/config?subdomain=${subdomain}`;
|
|
console.log('LoginBranding: URL:', apiUrl);
|
|
|
|
const response = await fetch(apiUrl);
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
|
|
if (data.primary_color) {
|
|
applyTheme(data.primary_color);
|
|
localStorage.setItem('agency-primary-color', data.primary_color);
|
|
}
|
|
|
|
if (data.logo_url) {
|
|
updateFavicon(data.logo_url);
|
|
localStorage.setItem('agency-logo-url', data.logo_url);
|
|
}
|
|
return;
|
|
} else {
|
|
console.error('LoginBranding: API retornou:', response.status);
|
|
}
|
|
|
|
// 2. Fallback para cache
|
|
const cachedPrimary = localStorage.getItem('agency-primary-color');
|
|
const cachedLogo = localStorage.getItem('agency-logo-url');
|
|
|
|
if (cachedPrimary) {
|
|
applyTheme(cachedPrimary);
|
|
}
|
|
if (cachedLogo) {
|
|
updateFavicon(cachedLogo);
|
|
}
|
|
} catch (error) {
|
|
console.error('LoginBranding: Erro:', error);
|
|
const cachedPrimary = localStorage.getItem('agency-primary-color');
|
|
const cachedLogo = localStorage.getItem('agency-logo-url');
|
|
|
|
if (cachedPrimary) {
|
|
applyTheme(cachedPrimary);
|
|
}
|
|
if (cachedLogo) {
|
|
updateFavicon(cachedLogo);
|
|
}
|
|
}
|
|
};
|
|
|
|
loadBranding();
|
|
}, []);
|
|
|
|
return null;
|
|
}
|