"use client"; import { useState, useEffect } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { Input, Checkbox, Button, Select, SearchableSelect } from "@/components/ui"; import DynamicBranding from "@/components/cadastro/DynamicBranding"; import DashboardPreview from "@/components/cadastro/DashboardPreview"; import { saveAuth } from '@/lib/auth'; import { API_ENDPOINTS, apiRequest } from '@/lib/api'; import dynamic from 'next/dynamic'; import { UserIcon, EnvelopeIcon, LockClosedIcon, KeyIcon, BuildingOfficeIcon, GlobeAltIcon, DocumentTextIcon, BuildingOffice2Icon, MapPinIcon, LinkIcon, BriefcaseIcon, UserGroupIcon, MapIcon, HomeIcon, PhoneIcon, CheckCircleIcon, XCircleIcon, ArrowPathIcon, PlusIcon, XMarkIcon, ArrowLeftIcon, ArrowRightIcon, CheckIcon, EyeIcon, PencilIcon, PhotoIcon, ArrowUpTrayIcon, PaintBrushIcon, InformationCircleIcon } from '@heroicons/react/24/outline'; const ThemeToggle = dynamic(() => import('@/components/ThemeToggle'), { ssr: false }); interface ContactField { id: number; whatsapp: string; } export default function CadastroPage() { const router = useRouter(); const [currentStep, setCurrentStep] = useState(1); const [completedSteps, setCompletedSteps] = useState([]); const [formData, setFormData] = useState>({}); const [contacts, setContacts] = useState([{ id: 1, whatsapp: "" }]); const [password, setPassword] = useState(""); const [passwordStrength, setPasswordStrength] = useState(0); const [cnpjData, setCnpjData] = useState({ razaoSocial: "", endereco: "" }); const [cepData, setCepData] = useState({ state: "", city: "", neighborhood: "", street: "" }); const [loadingCnpj, setLoadingCnpj] = useState(false); const [loadingCep, setLoadingCep] = useState(false); const [subdomain, setSubdomain] = useState(""); const [domainAvailable, setDomainAvailable] = useState(null); const [checkingDomain, setCheckingDomain] = useState(false); const [primaryColor, setPrimaryColor] = useState("#FF3A05"); const [secondaryColor, setSecondaryColor] = useState("#FF0080"); const [logoUrl, setLogoUrl] = useState(""); const [showPreviewMobile, setShowPreviewMobile] = useState(false); const [domainCheckTimeout, setDomainCheckTimeout] = useState(null); const [fieldErrors, setFieldErrors] = useState>({}); const [showWelcomeAnimation, setShowWelcomeAnimation] = useState(false); // Carregar dados do localStorage ao montar useEffect(() => { const saved = localStorage.getItem('cadastroFormData'); if (saved) { try { const data = JSON.parse(saved); setCurrentStep(data.currentStep || 1); setCompletedSteps(data.completedSteps || []); setFormData(data.formData || {}); setContacts(data.contacts || [{ id: 1, whatsapp: "" }]); setPassword(data.password || ""); setPasswordStrength(data.passwordStrength || 0); setCnpjData(data.cnpjData || { razaoSocial: "", endereco: "" }); setCepData(data.cepData || { state: "", city: "", neighborhood: "", street: "" }); setSubdomain(data.subdomain || ""); setDomainAvailable(data.domainAvailable ?? null); setPrimaryColor(data.primaryColor || "#FF3A05"); setSecondaryColor(data.secondaryColor || "#FF0080"); setLogoUrl(data.logoUrl || ""); } catch (error) { console.error('Erro ao carregar dados:', error); } } }, []); // Salvar no localStorage sempre que houver mudanças useEffect(() => { const dataToSave = { currentStep, completedSteps, formData, contacts, password, passwordStrength, cnpjData, cepData, subdomain, domainAvailable, primaryColor, secondaryColor, logoUrl }; localStorage.setItem('cadastroFormData', JSON.stringify(dataToSave)); }, [currentStep, completedSteps, formData, contacts, password, passwordStrength, cnpjData, cepData, subdomain, domainAvailable, primaryColor, secondaryColor, logoUrl]); // Função para atualizar formData const updateFormData = (name: string, value: any) => { setFormData(prev => ({ ...prev, [name]: value })); }; const steps = [ { number: 1, title: "Dados Pessoais", heading: "Seus Dados Pessoais", description: "Informe seus dados para criar sua conta de administrador." }, { number: 2, title: "Empresa", heading: "Dados da Empresa", description: "Cadastre as informações básicas da sua empresa." }, { number: 3, title: "Localização e Contato", heading: "Endereço e Contato", description: "Informe a localização da sua empresa e os contatos para comunicação." }, { number: 4, title: "Personalização", heading: "Personalize seu Painel", description: "Configure as cores e identidade visual da sua empresa." }, ]; const currentStepData = steps.find(s => s.number === currentStep); const validateCurrentStep = () => { setFieldErrors({}); const errors: Record = {}; if (currentStep === 1) { if (!formData.fullName || formData.fullName.trim().length < 3) { errors.fullName = 'Nome completo deve ter no mínimo 3 caracteres'; } if (!formData.email || !formData.email.includes('@')) { errors.email = 'Email inválido'; } if (password.length < 8) { errors.password = 'Senha deve ter no mínimo 8 caracteres'; } else if (passwordStrength < 2) { errors.password = 'Senha muito fraca'; } if (password !== formData.confirmPassword) { errors.confirmPassword = 'Senhas não coincidem'; } if (!formData.terms) { errors.terms = 'Aceite os Termos de Uso'; } } if (currentStep === 2) { if (!formData.companyName || formData.companyName.trim().length < 3) { errors.companyName = 'Mínimo 3 caracteres'; } const cnpjNumbers = formData.cnpj?.replace(/\D/g, '') || ''; if (cnpjNumbers.length !== 14) { errors.cnpj = 'CNPJ incompleto'; } if (!formData.description || formData.description.trim().length < 10) { errors.description = 'Mínimo 10 caracteres'; } if (!formData.industry) { errors.industry = 'Selecione o segmento'; } if (!formData.teamSize) { errors.teamSize = 'Selecione o tamanho'; } if (!subdomain || subdomain.trim().length < 3) { errors.subdomain = 'Mínimo 3 caracteres'; } else if (!/^[a-z0-9-]+$/.test(subdomain)) { errors.subdomain = 'Apenas letras, números e hífens'; } else if (domainAvailable === false) { errors.subdomain = 'Subdomínio já está em uso'; } else if (domainAvailable === null && subdomain.length >= 3) { errors.subdomain = 'Aguarde verificação'; } } if (currentStep === 3) { const cepNumbers = formData.cep?.replace(/\D/g, '') || ''; if (cepNumbers.length !== 8) { errors.cep = 'CEP incompleto'; } if (!formData.number || formData.number.trim().length < 1) { errors.number = 'Obrigatório'; } for (let i = 0; i < contacts.length; i++) { if (!contacts[i].whatsapp || contacts[i].whatsapp.replace(/\D/g, '').length < 10) { errors[`whatsapp-${contacts[i].id}`] = 'WhatsApp incompleto'; } } } setFieldErrors(errors); return Object.keys(errors).length === 0; }; const handleSubmitRegistration = async () => { try { const payload = { // Step 1 - Dados Pessoais email: formData.email, password: password, fullName: formData.fullName, newsletter: formData.newsletter || false, // Step 2 - Empresa companyName: formData.companyName, cnpj: formData.cnpj, razaoSocial: cnpjData.razaoSocial, description: formData.description, website: formData.website, industry: formData.industry, teamSize: formData.teamSize, subdomain: subdomain, // Step 3 - Localização e Contato cep: formData.cep, state: cepData.state, city: cepData.city, neighborhood: cepData.neighborhood, street: cepData.street, number: formData.number, complement: formData.complement, contacts: contacts, // Step 4 - Personalização primaryColor: primaryColor, secondaryColor: secondaryColor, logoUrl: logoUrl, }; console.log('📤 Enviando cadastro completo:', payload); const data = await apiRequest(API_ENDPOINTS.register, { method: 'POST', body: JSON.stringify(payload), }); console.log('📥 Resposta data:', data); // Salvar autenticação if (data.token) { saveAuth(data.token, { id: data.id, email: data.email, name: data.name, role: data.role, tenantId: data.tenantId, company: data.company, subdomain: data.subdomain }); } // Sucesso - limpar localStorage do form localStorage.removeItem('cadastroFormData'); console.log('✓ Conta criada com sucesso! Redirecionando...'); // Mostrar animação de boas-vindas setShowWelcomeAnimation(true); // Aguardar 4 segundos e redirecionar para o painel da agência setTimeout(() => { // Construir URL do tenant baseado no subdomínio const tenantUrl = `http://${data.subdomain || subdomain}.localhost:3000`; console.log('Redirecionando para:', tenantUrl); window.location.href = tenantUrl; }, 4000); } catch (error: any) { console.error('❌ Erro no cadastro:', error); let errorMessage = 'Não conseguimos criar sua conta. Por favor, tente novamente.'; // Mensagens humanizadas baseadas no erro if (error.message) { const msg = error.message.toLowerCase(); if (msg.includes('subdomain') || msg.includes('domínio') || msg.includes('domain')) { errorMessage = `O subdomínio "${subdomain}" já está sendo usado. Por favor, escolha outro nome para sua empresa.`; } else if (msg.includes('email')) { errorMessage = 'Este email já está cadastrado. Você já tem uma conta? Tente fazer login.'; } else if (msg.includes('cnpj')) { errorMessage = 'Este CNPJ já está cadastrado no sistema.'; } else if (msg.includes('network') || msg.includes('fetch')) { errorMessage = 'Problemas de conexão. Verifique sua internet e tente novamente.'; } else { errorMessage = error.message; } } console.error('Erro:', errorMessage); alert(errorMessage); } }; const canNavigateToStep = (targetStep: number) => { // Pode navegar para trás sempre if (targetStep < currentStep) { return true; } // Pode navegar para a etapa atual if (targetStep === currentStep) { return true; } // Só pode navegar para frente se a etapa anterior estiver completa if (targetStep === currentStep + 1 && completedSteps.includes(currentStep)) { return true; } // Pode navegar para qualquer etapa já completada if (completedSteps.includes(targetStep - 1)) { return true; } return false; }; const handleNext = (e?: React.FormEvent) => { if (e) { e.preventDefault(); } if (!validateCurrentStep()) { alert('Por favor, preencha todos os campos obrigatórios antes de continuar.'); return; } if (currentStep < 4) { setCompletedSteps([...completedSteps, currentStep]); setCurrentStep(currentStep + 1); } else { // Última etapa - enviar dados para o backend handleSubmitRegistration(); } }; const addContact = () => { const newId = contacts.length > 0 ? Math.max(...contacts.map(c => c.id)) + 1 : 1; setContacts([...contacts, { id: newId, whatsapp: "" }]); }; const removeContact = (id: number) => { if (contacts.length > 1) { setContacts(contacts.filter(c => c.id !== id)); } }; const formatPhone = (value: string) => { const numbers = value.replace(/\D/g, ""); if (numbers.length <= 10) { return numbers.replace(/(\d{2})(\d{4})(\d{0,4})/, "($1) $2-$3").replace(/-$/, ""); } return numbers.replace(/(\d{2})(\d{5})(\d{0,4})/, "($1) $2-$3").replace(/-$/, ""); }; const calculatePasswordStrength = (pwd: string): number => { let strength = 0; if (pwd.length >= 8) strength++; if (pwd.length >= 12) strength++; if (/[a-z]/.test(pwd) && /[A-Z]/.test(pwd)) strength++; if (/\d/.test(pwd)) strength++; if (/[^a-zA-Z0-9]/.test(pwd)) strength++; return strength; }; const checkDomainAvailability = async (domain: string) => { if (!domain || domain.length < 3) { setDomainAvailable(null); return; } if (!/^[a-z0-9-]+$/.test(domain)) { setDomainAvailable(null); console.error('O subdomínio deve conter apenas letras minúsculas, números e hífens.'); return; } setCheckingDomain(true); setDomainAvailable(null); try { // Verificar disponibilidade via API - usar porta 8085 do backend const response = await fetch(`http://localhost:8085/api/tenant/check?subdomain=${domain}`, { method: 'GET', headers: { 'Accept': 'application/json', } }); // Se retornar 200, o tenant existe (indisponível) // Se retornar 404, o tenant não existe (disponível) const isAvailable = response.status === 404; setDomainAvailable(isAvailable); if (isAvailable) { console.log(`✓ ${domain}.aggios.app está disponível!`); } else { console.log(`✗ ${domain}.aggios.app já está em uso. Tente outro.`); } } catch (error) { console.error('Erro ao verificar domínio:', error); setDomainAvailable(null); } finally { setCheckingDomain(false); } }; const handlePasswordChange = (e: React.ChangeEvent) => { const newPassword = e.target.value; setPassword(newPassword); setPasswordStrength(calculatePasswordStrength(newPassword)); }; const getPasswordStrengthLabel = () => { if (password.length === 0) return ""; if (passwordStrength <= 1) return "Muito fraca"; if (passwordStrength === 2) return "Fraca"; if (passwordStrength === 3) return "Média"; if (passwordStrength === 4) return "Forte"; return "Muito forte"; }; const getPasswordStrengthColor = () => { if (passwordStrength <= 1) return "#EF4444"; if (passwordStrength === 2) return "#F59E0B"; if (passwordStrength === 3) return "#3B82F6"; if (passwordStrength === 4) return "#10B981"; return "#059669"; }; const fetchCnpjData = async (cnpj: string) => { const numbers = cnpj.replace(/\D/g, ""); if (numbers.length !== 14) return; setLoadingCnpj(true); try { const response = await fetch(`https://brasilapi.com.br/api/cnpj/v1/${numbers}`); if (response.ok) { const data = await response.json(); setCnpjData({ razaoSocial: data.razao_social || "", endereco: `${data.logradouro}, ${data.numero} - ${data.bairro}, ${data.municipio}/${data.uf}` }); } } catch (error) { console.error("Erro ao buscar CNPJ:", error); } finally { setLoadingCnpj(false); } }; 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) { const data = await response.json(); if (!data.erro) { setCepData({ state: data.uf || "", city: data.localidade || "", neighborhood: data.bairro || "", street: data.logradouro || "" }); } } } catch (error) { console.error("Erro ao buscar CEP:", error); } finally { setLoadingCep(false); } }; const formatCnpj = (value: string) => { const numbers = value.replace(/\D/g, ""); return numbers.replace(/(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})/, "$1.$2.$3/$4-$5").substring(0, 18); }; const formatCep = (value: string) => { const numbers = value.replace(/\D/g, ""); return numbers.replace(/(\d{5})(\d{3})/, "$1-$2").substring(0, 9); }; return ( <> {/* Modal de Boas-vindas com Animação */} {showWelcomeAnimation && (
{/* Animação de círculos expandindo */}
{/* Texto animado */}

Bem-vindo! 🎉

Estamos criando seu painel personalizado...

Em breve você terá a melhor experiência de gestão!

{/* Barra de progresso animada */}
)}
{/* Lado Esquerdo - Formulário */}
{/* Título e texto */}
{/* Theme Toggle */}
{/* Progresso Circular */}
{Math.round((currentStep / 4) * 100)}%
{/* Título e Descrição */}

{currentStepData?.heading}

{currentStepData?.description}

{/* Formulário */}
{ e.preventDefault(); handleNext(e); }} className="space-y-6"> {currentStep === 1 && (
} value={formData.fullName || ''} onChange={(e) => updateFormData('fullName', e.target.value)} error={fieldErrors.fullName} required /> } helperText="Será usado para seu login" value={formData.email || ''} onChange={(e) => updateFormData('email', e.target.value)} required /> {/* Separador de seção */}

Crie sua senha de acesso

} helperText="Use maiúsculas, minúsculas, números e símbolos" value={password} onChange={handlePasswordChange} required /> {password.length > 0 && (
Força da senha: {getPasswordStrengthLabel()}
)}
} value={formData.confirmPassword || ''} onChange={(e) => updateFormData('confirmPassword', e.target.value)} error={fieldErrors.confirmPassword} required />
updateFormData('terms', e.target.checked)} label={ Concordo com os{" "} Termos de Uso } /> updateFormData('newsletter', e.target.checked)} /> {/* Link para login */}

Já possui uma conta?{" "} Fazer login

)} {currentStep === 2 && (
} value={formData.companyName || ''} onChange={(e) => { const name = e.target.value; updateFormData('companyName', name); // Auto-generate subdomain const slug = name.toLowerCase().replace(/[^a-z0-9]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, ''); setSubdomain(slug); setDomainAvailable(null); // Limpar timeout anterior if (domainCheckTimeout) { clearTimeout(domainCheckTimeout); } // Verificar automaticamente após 800ms se tiver 3+ caracteres if (slug.length >= 3) { const timeout = setTimeout(() => { checkDomainAvailability(slug); }, 800); setDomainCheckTimeout(timeout); } }} required />
{ const value = e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, ''); setSubdomain(value); setDomainAvailable(null); // Limpar timeout anterior if (domainCheckTimeout) { clearTimeout(domainCheckTimeout); } // Verificar automaticamente após 800ms if (value.length >= 3) { const timeout = setTimeout(() => { checkDomainAvailability(value); }, 800); setDomainCheckTimeout(timeout); } }} />
{checkingDomain && } {domainAvailable === true && } {domainAvailable === false && }

Seu painel: {subdomain || '...'}.aggios.app

{checkingDomain && ( VERIFICANDO )} {domainAvailable === true && ( DISPONÍVEL )} {domainAvailable === false && ( INDISPONÍVEL )}
} helperText="Preencheremos automaticamente razão social e endereço" maxLength={18} value={formData.cnpj || ''} onChange={(e) => { const formatted = formatCnpj(e.target.value); updateFormData('cnpj', formatted); if (formatted.replace(/\D/g, "").length === 14) { fetchCnpjData(formatted); } }} required /> } value={cnpjData.razaoSocial} disabled /> } value={cnpjData.endereco} disabled />