"use client"; import { useState, useEffect } from 'react'; import { Tab } from '@headlessui/react'; import { Button, Dialog, Input } from '@/components/ui'; import { Toaster, toast } from 'react-hot-toast'; import { BuildingOfficeIcon, PhotoIcon, UserGroupIcon, ShieldCheckIcon, BellIcon, ArrowUpTrayIcon, TrashIcon, CheckCircleIcon, } from '@heroicons/react/24/outline'; const tabs = [ { name: 'Dados da Agência', icon: BuildingOfficeIcon }, { name: 'Logo e Marca', icon: PhotoIcon }, { name: 'Equipe', icon: UserGroupIcon }, { name: 'Segurança', icon: ShieldCheckIcon }, { name: 'Notificações', icon: BellIcon }, ]; const parseAddressParts = (address: string) => { if (!address) return { street: '', number: '', complement: '' }; const [streetPart, rest] = address.split(',', 2).map(part => part.trim()); if (!rest) return { street: streetPart, number: '', complement: '' }; const [numberPart, complementPart] = rest.split('-', 2).map(part => part.trim()); return { street: streetPart, number: numberPart || '', complement: complementPart || '', }; }; export default function ConfiguracoesPage() { const [selectedTab, setSelectedTab] = useState(0); const [showSuccessDialog, setShowSuccessDialog] = useState(false); const [successMessage, setSuccessMessage] = useState(''); const [showSupportDialog, setShowSupportDialog] = useState(false); const [supportMessage, setSupportMessage] = useState('Para alterar estes dados, contate o suporte.'); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [loadingCep, setLoadingCep] = useState(false); const [uploadingLogo, setUploadingLogo] = useState(false); const [logoPreview, setLogoPreview] = useState(null); const [logoHorizontalPreview, setLogoHorizontalPreview] = useState(null); // Dados da agência (buscados da API) const [agencyData, setAgencyData] = useState({ name: '', cnpj: '', email: '', phone: '', website: '', address: '', street: '', number: '', complement: '', neighborhood: '', city: '', state: '', zip: '', razaoSocial: '', description: '', industry: '', teamSize: '', logoUrl: '', logoHorizontalUrl: '', primaryColor: '#ff3a05', secondaryColor: '#ff0080', }); // Live Preview da Cor Primária useEffect(() => { if (agencyData.primaryColor) { const root = document.documentElement; 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 primaryRgb = hexToRgb(agencyData.primaryColor); if (primaryRgb) { root.style.setProperty('--brand-rgb', primaryRgb); root.style.setProperty('--brand-strong-rgb', primaryRgb); root.style.setProperty('--brand-hover-rgb', primaryRgb); } root.style.setProperty('--brand-color', agencyData.primaryColor); root.style.setProperty('--gradient', `linear-gradient(135deg, ${agencyData.primaryColor}, ${agencyData.primaryColor})`); } }, [agencyData.primaryColor]); // Dados para alteração de senha const [passwordData, setPasswordData] = useState({ currentPassword: '', newPassword: '', confirmPassword: '', }); useEffect(() => { const fetchAgencyData = async () => { try { setLoading(true); const token = localStorage.getItem('token'); const userData = localStorage.getItem('user'); if (!token || !userData) { console.error('Usuário não autenticado'); setLoading(false); return; } // Buscar dados da API const response = await fetch('/api/agency/profile', { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, }); if (response.ok) { const data = await response.json(); const parsedAddress = parseAddressParts(data.address || ''); setAgencyData({ name: data.name || '', cnpj: data.cnpj || '', email: data.email || '', phone: data.phone || '', website: data.website || '', address: data.address || '', street: parsedAddress.street, number: data.number || parsedAddress.number, complement: data.complement || parsedAddress.complement, neighborhood: data.neighborhood || '', city: data.city || '', state: data.state || '', zip: data.zip || '', razaoSocial: data.razao_social || '', description: data.description || '', industry: data.industry || '', teamSize: data.team_size || '', logoUrl: data.logo_url || localStorage.getItem('agency-logo-url') || '', logoHorizontalUrl: data.logo_horizontal_url || localStorage.getItem('agency-logo-horizontal-url') || '', primaryColor: data.primary_color || '#ff3a05', secondaryColor: data.secondary_color || '#ff0080', }); // Set logo previews - usar localStorage como fallback se API não retornar const cachedLogo = localStorage.getItem('agency-logo-url'); const cachedHorizontal = localStorage.getItem('agency-logo-horizontal-url'); const finalLogoUrl = data.logo_url || cachedLogo; const finalHorizontalUrl = data.logo_horizontal_url || cachedHorizontal; if (finalLogoUrl) { setLogoPreview(finalLogoUrl); localStorage.setItem('agency-logo-url', finalLogoUrl); } if (finalHorizontalUrl) { setLogoHorizontalPreview(finalHorizontalUrl); localStorage.setItem('agency-logo-horizontal-url', finalHorizontalUrl); } } else { console.error('Erro ao buscar dados:', response.status); // Fallback para localStorage se API falhar const savedData = localStorage.getItem('cadastroData'); if (savedData) { const data = JSON.parse(savedData); const user = JSON.parse(userData); setAgencyData({ name: data.formData?.companyName || '', cnpj: data.formData?.cnpj || '', email: data.formData?.email || user.email || '', phone: data.contacts?.[0]?.phone || '', website: data.formData?.website || '', address: `${data.cepData?.logradouro || ''}, ${data.formData?.number || ''}`, street: data.cepData?.logradouro || '', number: data.formData?.number || '', complement: data.formData?.complement || '', neighborhood: data.cepData?.bairro || '', city: data.cepData?.localidade || '', state: data.cepData?.uf || '', zip: data.formData?.cep || '', razaoSocial: data.cnpjData?.razaoSocial || '', description: data.formData?.description || '', industry: data.formData?.industry || '', teamSize: data.formData?.teamSize || '', logoUrl: '', logoHorizontalUrl: '', primaryColor: '#ff3a05', secondaryColor: '#ff0080', }); } } } catch (error) { console.error('Erro ao buscar dados da agência:', error); setSuccessMessage('Erro ao carregar dados da agência.'); setShowSuccessDialog(true); } finally { setLoading(false); } }; fetchAgencyData(); }, []); const handleLogoUpload = async (file: File, type: 'logo' | 'horizontal') => { if (!file) return; // Validar tipo de arquivo if (!file.type.startsWith('image/')) { toast.error('Por favor, selecione uma imagem válida'); return; } // Validar tamanho (2MB) if (file.size > 2 * 1024 * 1024) { toast.error('A imagem deve ter no máximo 2MB'); return; } setUploadingLogo(true); try { const token = localStorage.getItem('token'); if (!token) { toast.error('Você precisa estar autenticado'); return; } const formData = new FormData(); formData.append('logo', file); formData.append('type', type); const response = await fetch('/api/agency/logo', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, }, body: formData, }); if (response.ok) { const data = await response.json(); const logoUrl = data.logo_url || data.url; if (type === 'logo') { setAgencyData(prev => ({ ...prev, logoUrl })); setLogoPreview(logoUrl); // Salvar no localStorage para uso do favicon localStorage.setItem('agency-logo-url', logoUrl); } else { setAgencyData(prev => ({ ...prev, logoHorizontalUrl: logoUrl })); setLogoHorizontalPreview(logoUrl); } // Disparar evento para atualizar branding em tempo real if (typeof window !== 'undefined') { window.dispatchEvent(new Event('branding-update')); } toast.success('Logo enviado com sucesso!'); } else { const errorData = await response.json().catch(() => ({})); console.error('Upload error:', errorData); toast.error(errorData.error || 'Erro ao enviar logo. Tente novamente.'); } } catch (error) { console.error('Erro ao fazer upload:', error); toast.error('Erro ao enviar logo. Verifique sua conexão.'); } finally { setUploadingLogo(false); } }; const handleFileSelect = (e: React.ChangeEvent, type: 'logo' | 'horizontal') => { const file = e.target.files?.[0]; if (file) { // Create preview immediately const reader = new FileReader(); reader.onloadend = () => { const previewUrl = reader.result as string; if (type === 'logo') { setLogoPreview(previewUrl); } else { setLogoHorizontalPreview(previewUrl); } }; reader.readAsDataURL(file); // Upload file handleLogoUpload(file, type); } }; const formatCep = (value: string) => { const numbers = value.replace(/\D/g, ''); return numbers.replace(/(\d{5})(\d{0,3})/, '$1-$2').substring(0, 9); }; const fetchCepData = async (cep: string) => { const numbers = cep.replace(/\D/g, ''); if (numbers.length !== 8) return; setLoadingCep(true); try { const response = await fetch(`https://viacep.com.br/ws/${numbers}/json/`); if (!response.ok) { toast.error('Não foi possível consultar o CEP agora.'); return; } const data = await response.json(); if (data?.erro) { toast.error('CEP não encontrado. Verifique o número.'); setAgencyData(prev => ({ ...prev, address: '', street: '', neighborhood: '', city: '', state: '', })); return; } const formattedCep = formatCep(cep); const nextAddress = data.logradouro || ''; const nextNeighborhood = data.bairro || ''; const nextCity = data.localidade || ''; const nextState = data.uf || ''; setAgencyData(prev => ({ ...prev, zip: formattedCep, address: nextAddress, street: nextAddress, neighborhood: nextNeighborhood, city: nextCity, state: nextState, })); toast.success('Endereço preenchido pelo CEP'); } catch (error) { console.error('Erro ao buscar CEP:', error); toast.error('Erro ao buscar CEP. Tente novamente.'); } finally { setLoadingCep(false); } }; const handleSaveAgency = async () => { setSaving(true); try { const token = localStorage.getItem('token'); if (!token) { setSuccessMessage('Você precisa estar autenticado.'); setShowSuccessDialog(true); setSaving(false); return; } const composedAddress = agencyData.street ? `${agencyData.street}${agencyData.number ? `, ${agencyData.number}` : ''}${agencyData.complement ? ` - ${agencyData.complement}` : ''}` : agencyData.address; const response = await fetch('/api/agency/profile', { method: 'PUT', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ name: agencyData.name, cnpj: agencyData.cnpj, email: agencyData.email, phone: agencyData.phone, website: agencyData.website, address: composedAddress, neighborhood: agencyData.neighborhood, number: agencyData.number, complement: agencyData.complement, city: agencyData.city, state: agencyData.state, zip: agencyData.zip, razao_social: agencyData.razaoSocial, description: agencyData.description, industry: agencyData.industry, team_size: agencyData.teamSize, primary_color: agencyData.primaryColor, secondary_color: agencyData.secondaryColor, }), }); if (response.ok) { setSuccessMessage('Dados da agência salvos com sucesso!'); // Atualiza localStorage imediatamente para persistência instantânea localStorage.setItem('agency-primary-color', agencyData.primaryColor); localStorage.setItem('agency-secondary-color', agencyData.secondaryColor); // Preservar logos no localStorage (não sobrescrever com valores vazios) // Logos são gerenciados separadamente via upload const currentLogoCache = localStorage.getItem('agency-logo-url'); const currentHorizontalCache = localStorage.getItem('agency-logo-horizontal-url'); // Só atualizar se temos valores novos no estado if (agencyData.logoUrl) { localStorage.setItem('agency-logo-url', agencyData.logoUrl); } else if (!currentLogoCache && logoPreview) { // Se não tem cache mas tem preview, usar o preview localStorage.setItem('agency-logo-url', logoPreview); } if (agencyData.logoHorizontalUrl) { localStorage.setItem('agency-logo-horizontal-url', agencyData.logoHorizontalUrl); } else if (!currentHorizontalCache && logoHorizontalPreview) { localStorage.setItem('agency-logo-horizontal-url', logoHorizontalPreview); } // Disparar evento para atualizar o tema em tempo real window.dispatchEvent(new Event('branding-update')); } else { setSuccessMessage('Erro ao salvar dados. Tente novamente.'); } } catch (error) { console.error('Erro ao salvar:', error); setSuccessMessage('Erro ao salvar dados. Verifique sua conexão.'); } finally { setSaving(false); } setShowSuccessDialog(true); }; const handleChangePassword = async () => { // Validações if (!passwordData.currentPassword) { setSuccessMessage('Por favor, informe sua senha atual.'); setShowSuccessDialog(true); return; } if (!passwordData.newPassword || passwordData.newPassword.length < 8) { setSuccessMessage('A nova senha deve ter pelo menos 8 caracteres.'); setShowSuccessDialog(true); return; } if (passwordData.newPassword !== passwordData.confirmPassword) { setSuccessMessage('As senhas não coincidem.'); setShowSuccessDialog(true); return; } try { const token = localStorage.getItem('token'); if (!token) { setSuccessMessage('Você precisa estar autenticado.'); setShowSuccessDialog(true); return; } const response = await fetch('/api/auth/change-password', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ currentPassword: passwordData.currentPassword, newPassword: passwordData.newPassword, }), }); if (response.ok) { setPasswordData({ currentPassword: '', newPassword: '', confirmPassword: '' }); setSuccessMessage('Senha alterada com sucesso!'); } else { const error = await response.text(); setSuccessMessage(error || 'Erro ao alterar senha. Verifique sua senha atual.'); } } catch (error) { console.error('Erro ao alterar senha:', error); setSuccessMessage('Erro ao alterar senha. Verifique sua conexão.'); } setShowSuccessDialog(true); }; return (
{/* Header */}

Configurações

Gerencie as configurações da sua agência

{/* Loading State */} {loading ? (
) : ( <> {/* Tabs */} {tabs.map((tab) => { const Icon = tab.icon; return ( `w-full flex items-center justify-center space-x-2 rounded-lg py-2.5 text-sm font-medium leading-5 transition-all ${selected ? 'bg-white dark:bg-gray-900 text-gray-900 dark:text-white shadow' : 'text-gray-600 dark:text-gray-400 hover:bg-white/50 dark:hover:bg-gray-700/50 hover:text-gray-900 dark:hover:text-white' }` } > {tab.name} ); })} {/* Tab 1: Dados da Agência */} {/* Card: Informações da Agência */}

Informações da Agência

setAgencyData({ ...agencyData, name: e.target.value })} placeholder="Ex: Minha Agência" />
setAgencyData({ ...agencyData, razaoSocial: e.target.value })} placeholder="Razão Social Ltda" />
{ setSupportMessage('Para alterar CNPJ, contate o suporte.'); setShowSupportDialog(true); }} className="cursor-pointer bg-gray-50 dark:bg-gray-800" helperText="Alteração via suporte" />
{ setSupportMessage('Para alterar o e-mail de acesso, contate o suporte.'); setShowSupportDialog(true); }} className="cursor-pointer bg-gray-50 dark:bg-gray-800" helperText="Alteração via suporte" />
setAgencyData({ ...agencyData, phone: e.target.value })} placeholder="(00) 00000-0000" />
setAgencyData({ ...agencyData, website: e.target.value })} placeholder="https://www.suaagencia.com.br" leftIcon="ri-global-line" />
setAgencyData({ ...agencyData, industry: e.target.value })} placeholder="Ex: Marketing Digital" />
setAgencyData({ ...agencyData, teamSize: e.target.value })} placeholder="Ex: 10-50 funcionários" />
{/* Card: Contato e Localização */}

Contato e Localização

{ const formatted = formatCep(e.target.value); setAgencyData(prev => ({ ...prev, zip: formatted })); const numbers = formatted.replace(/\D/g, ''); if (numbers.length === 8) { fetchCepData(formatted); } if (numbers.length === 0) { setAgencyData(prev => ({ ...prev, address: '', street: '', neighborhood: '', city: '', state: '', })); } }} placeholder="00000-000" rightIcon={loadingCep ? "ri-loader-4-line animate-spin" : undefined} />
setAgencyData({ ...agencyData, number: e.target.value })} placeholder="123" />
setAgencyData({ ...agencyData, complement: e.target.value })} placeholder="Apto 101, Bloco B" />
{/* Tab 2: Logo e Marca */}

Logo e Identidade Visual

Configure os logos da sua agência que serão exibidos no sistema

{/* Logo Principal */}

Logo Principal (Quadrado)

Usado no menu lateral e ícones do sistema

{logoPreview ? (
Logo Principal
{agencyData.logoUrl && (
Salvo
)}
) : ( )}
{/* Logo Horizontal */}

Logo Horizontal (Opcional)

Usado no cabeçalho e emails

{logoHorizontalPreview ? (
Logo Horizontal
{agencyData.logoHorizontalUrl && (
Salvo
)}
) : ( )}
{/* Cores da Marca */}

Personalização de Cores

setAgencyData({ ...agencyData, primaryColor: e.target.value })} className="absolute -top-2 -left-2 w-24 h-24 cursor-pointer p-0 border-0" />
setAgencyData({ ...agencyData, primaryColor: e.target.value })} className="uppercase" maxLength={7} />
setAgencyData({ ...agencyData, secondaryColor: e.target.value })} className="absolute -top-2 -left-2 w-24 h-24 cursor-pointer p-0 border-0" />
setAgencyData({ ...agencyData, secondaryColor: e.target.value })} className="uppercase" maxLength={7} />
{/* Info adicional */}

Dica: Para melhores resultados, use imagens de alta qualidade em formato PNG com fundo transparente.

{/* Tab 3: Equipe */}

Gerenciamento de Equipe

Em breve: gerenciamento completo de usuários e permissões

{/* Tab 3: Segurança */}

Segurança e Privacidade

{/* Alteração de Senha */}

Alterar Senha

setPasswordData({ ...passwordData, currentPassword: e.target.value })} placeholder="Digite sua senha atual" />
setPasswordData({ ...passwordData, newPassword: e.target.value })} placeholder="Digite a nova senha (mínimo 8 caracteres)" />
setPasswordData({ ...passwordData, confirmPassword: e.target.value })} placeholder="Digite a nova senha novamente" />
{/* Recursos Futuros */}

Recursos em Desenvolvimento

Autenticação em duas etapas (2FA)
Histórico de acessos
Dispositivos conectados
{/* Tab 4: Notificações */}

Preferências de Notificações

Em breve: configuração de notificações por e-mail, push e mais

)} {/* Dialog de Sucesso */} setShowSuccessDialog(false)} title="Sucesso" size="sm" >

{successMessage}

{/* Dialog de Suporte */} setShowSupportDialog(false)} title="Contatar suporte" >

{supportMessage}

Envie um e-mail para suporte@aggios.app ou abra um chamado para ajuste desses dados.

); }