'use client'; import { useState, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { UserIcon, EnvelopeIcon, PhoneIcon, BuildingOfficeIcon, ChatBubbleLeftRightIcon, MapPinIcon, IdentificationIcon, CheckIcon, ArrowRightIcon, ArrowLeftIcon, PhotoIcon, LockClosedIcon, EyeIcon, EyeSlashIcon, } from '@heroicons/react/24/outline'; import { CheckCircleIcon } from '@heroicons/react/24/solid'; import { registerCustomer } from '@/lib/register-customer'; type PersonType = 'pf' | 'pj'; type Step = { id: number; name: string; description: string; }; type AgencyBranding = { primary_color: string; logo_url?: string; name: string; }; type CustomerFormState = { cpf: string; full_name: string; cnpj: string; company_name: string; trade_name: string; responsible_name: string; email: string; phone: string; password: string; password_confirmation: string; postal_code: string; street: string; number: string; complement: string; neighborhood: string; city: string; state: string; message: string; }; interface CadastroClientePageProps { branding: AgencyBranding; } const steps: Step[] = [ { id: 1, name: 'Identificação', description: 'Tipo e dados principais' }, { id: 2, name: 'Contato', description: 'Email e telefone' }, { id: 3, name: 'Endereço', description: 'Localização completa' }, { id: 4, name: 'Logo & Mensagem', description: 'Personalização' }, { id: 5, name: 'Segurança', description: 'Defina sua senha de acesso' }, { id: 6, name: 'Revisão', description: 'Confirmar dados' }, ]; const initialFormState: CustomerFormState = { cpf: '', full_name: '', cnpj: '', company_name: '', trade_name: '', responsible_name: '', email: '', phone: '', password: '', password_confirmation: '', postal_code: '', street: '', number: '', complement: '', neighborhood: '', city: '', state: '', message: '', }; const lightenColor = (hexColor: string, amount = 20) => { const fallback = '#2563eb'; if (!hexColor) return fallback; let color = hexColor.replace('#', ''); if (color.length === 3) { color = color.split('').map(char => char + char).join(''); } if (color.length !== 6) return fallback; const num = parseInt(color, 16); if (Number.isNaN(num)) return fallback; const clamp = (value: number) => Math.max(0, Math.min(255, value)); const r = clamp((num >> 16) + amount); const g = clamp(((num >> 8) & 0x00ff) + amount); const b = clamp((num & 0x0000ff) + amount); const nextColor = ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); return `#${nextColor}`; }; export default function CadastroClientePage({ branding }: CadastroClientePageProps) { const router = useRouter(); const [currentStep, setCurrentStep] = useState(1); const [personType, setPersonType] = useState('pf'); const [logoFile, setLogoFile] = useState(null); const [logoPreview, setLogoPreview] = useState(null); const [formData, setFormData] = useState({ ...initialFormState }); const [error, setError] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); const [isCepLoading, setIsCepLoading] = useState(false); const [isCnpjLoading, setIsCnpjLoading] = useState(false); const [lastCnpjFetched, setLastCnpjFetched] = useState(null); const [autoFilledFields, setAutoFilledFields] = useState>>({}); const [showPassword, setShowPassword] = useState(false); const [showPasswordConfirmation, setShowPasswordConfirmation] = useState(false); const primaryColor = branding.primary_color?.trim() || '#2563eb'; const gradientStyle = { backgroundImage: `linear-gradient(120deg, ${primaryColor}, ${lightenColor(primaryColor, 25)})`, boxShadow: `0 12px 22px -12px ${primaryColor}66`, }; const buttonBase = 'inline-flex items-center gap-2 rounded-lg font-semibold transition-all focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[var(--brand-color)] disabled:opacity-60 disabled:cursor-not-allowed'; const hasAutoFilledFields = Object.keys(autoFilledFields).length > 0; useEffect(() => { document.documentElement.style.setProperty('--brand-color', primaryColor); }, [primaryColor]); const formatCpf = (value: string) => { return value .replace(/\D/g, '') .replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, '$1.$2.$3-$4') .substring(0, 14); }; const formatCnpj = (value: string) => { return value .replace(/\D/g, '') .replace(/(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})/, '$1.$2.$3/$4-$5') .substring(0, 18); }; const formatPhone = (value: string) => { const digits = value.replace(/\D/g, '').substring(0, 11); if (digits.length <= 10) { return digits.replace(/(\d{2})(\d{4})(\d{0,4})/, '($1) $2-$3').trim(); } return digits.replace(/(\d{2})(\d{5})(\d{0,4})/, '($1) $2-$3').trim(); }; const formatCep = (value: string) => { return value .replace(/\D/g, '') .replace(/(\d{5})(\d{3})/, '$1-$2') .substring(0, 9); }; const clearAutoFilledFields = () => { setAutoFilledFields({}); setLastCnpjFetched(null); }; const isFieldLocked = (field: keyof CustomerFormState) => personType === 'pj' && Boolean(autoFilledFields[field]); const inputLockClasses = (field: keyof CustomerFormState) => (isFieldLocked(field) ? 'bg-gray-100 cursor-not-allowed border-gray-200' : ''); const applyAutoFilledData = ( data: Partial, fieldsToLock: Array = [] ) => { if (!Object.keys(data).length) return; setFormData(prev => ({ ...prev, ...data })); if (!fieldsToLock.length) return; setAutoFilledFields(prev => { const updated = { ...prev }; fieldsToLock.forEach(field => { if (data[field]) { updated[field] = true; } }); return updated; }); }; const handleCpfChange = (value: string) => { if (error) setError(null); setFormData(prev => ({ ...prev, cpf: formatCpf(value) })); }; const handleCnpjChange = (value: string) => { if (error) setError(null); setFormData(prev => ({ ...prev, cnpj: formatCnpj(value) })); if (Object.keys(autoFilledFields).length) { setAutoFilledFields({}); } if (lastCnpjFetched) { setLastCnpjFetched(null); } }; const handlePhoneChange = (value: string) => { if (error) setError(null); setFormData(prev => ({ ...prev, phone: formatPhone(value) })); }; const handlePostalCodeChange = (value: string) => { if (error) setError(null); setFormData(prev => ({ ...prev, postal_code: formatCep(value) })); }; const handleCnpjBlur = async () => { if (personType !== 'pj') return; const digits = formData.cnpj.replace(/\D/g, ''); if (digits.length !== 14 || digits === lastCnpjFetched) return; setIsCnpjLoading(true); setError(null); try { const response = await fetch(`https://brasilapi.com.br/api/cnpj/v1/${digits}`, { headers: { Accept: 'application/json', }, }); if (!response.ok) { throw new Error('CNPJ não encontrado'); } const data = await response.json(); const lockedFields: Array = ['company_name', 'trade_name', 'responsible_name']; const formattedCnpj = formatCnpj(formData.cnpj); const autoData: Partial = { cnpj: formattedCnpj, }; if (data.razao_social) autoData.company_name = String(data.razao_social).trim(); if (data.nome_fantasia) autoData.trade_name = String(data.nome_fantasia).trim(); const responsibleName = data.qsa?.[0]?.nome?.trim(); if (responsibleName) autoData.responsible_name = responsibleName; if (data.telefone) autoData.phone = formatPhone(String(data.telefone)); if (data.email) autoData.email = String(data.email).toLowerCase(); if (data.cep) autoData.postal_code = formatCep(String(data.cep)); if (data.logradouro) autoData.street = String(data.logradouro); if (data.numero) autoData.number = String(data.numero); if (data.complemento) autoData.complement = String(data.complemento); if (data.bairro) autoData.neighborhood = String(data.bairro); if (data.municipio) autoData.city = String(data.municipio); if (data.uf) autoData.state = String(data.uf); applyAutoFilledData(autoData, lockedFields); setLastCnpjFetched(digits); } catch (err) { console.error(err); setError('Não conseguimos localizar este CNPJ. Confira os números e tente novamente.'); } finally { setIsCnpjLoading(false); } }; const validateEmail = (email: string): boolean => { const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; return emailRegex.test(email); }; const validateStep = (step: number): boolean => { switch (step) { case 1: if (personType === 'pf') { if (!formData.cpf) { setError('❌ CPF é obrigatório'); return false; } if (formData.cpf.replace(/\D/g, '').length !== 11) { setError('❌ CPF deve conter 11 dígitos'); return false; } if (!formData.full_name) { setError('❌ Nome Completo é obrigatório'); return false; } if (formData.full_name.trim().split(' ').length < 2) { setError('❌ Digite seu nome completo (nome e sobrenome)'); return false; } } else { if (!formData.cnpj) { setError('❌ CNPJ é obrigatório'); return false; } if (formData.cnpj.replace(/\D/g, '').length !== 14) { setError('❌ CNPJ deve conter 14 dígitos'); return false; } if (!formData.company_name) { setError('❌ Razão Social é obrigatória'); return false; } if (!formData.responsible_name) { setError('❌ Nome do responsável é obrigatório'); return false; } if (formData.responsible_name.trim().split(' ').length < 2) { setError('❌ Informe o nome completo do responsável'); return false; } } break; case 2: if (!formData.email) { setError('❌ E-mail é obrigatório'); return false; } if (!validateEmail(formData.email)) { setError('❌ Digite um e-mail válido (exemplo: seu@email.com)'); return false; } if (!formData.phone) { setError('❌ Telefone é obrigatório'); return false; } const phoneDigits = formData.phone.replace(/\D/g, ''); if (phoneDigits.length < 10) { setError('❌ Telefone deve conter pelo menos 10 dígitos'); return false; } break; case 3: if (!formData.postal_code) { setError('❌ CEP é obrigatório'); return false; } if (formData.postal_code.replace(/\D/g, '').length !== 8) { setError('❌ CEP deve conter 8 dígitos'); return false; } if (!formData.street) { setError('❌ Logradouro é obrigatório'); return false; } if (!formData.number) { setError('❌ Número é obrigatório'); return false; } if (!formData.neighborhood) { setError('❌ Bairro é obrigatório'); return false; } if (!formData.city) { setError('❌ Cidade é obrigatória'); return false; } if (!formData.state) { setError('❌ Estado é obrigatório'); return false; } break; } setError(null); return true; }; const handleNext = () => { if (validateStep(currentStep)) { setCurrentStep(prev => Math.min(prev + 1, steps.length)); } }; const handlePrevious = () => { setError(null); setCurrentStep(prev => Math.max(prev - 1, 1)); }; const handleLogoChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { if (file.size > 5 * 1024 * 1024) { setError('A imagem deve ter no máximo 5MB'); return; } setLogoFile(file); const reader = new FileReader(); reader.onloadend = () => { setLogoPreview(reader.result as string); }; reader.readAsDataURL(file); } }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(null); setIsSubmitting(true); try { const submitData = new FormData(); submitData.append('person_type', personType); Object.entries(formData).forEach(([key, value]) => { if (value) submitData.append(key, value); }); if (logoFile) { submitData.append('logo', logoFile); } const result = await registerCustomer(submitData); if (!result.success) { // Mensagens de erro específicas para duplicidade if (result.error?.includes('e-mail')) { throw new Error('⚠️ Já existe uma conta cadastrada com este e-mail. Se você já é cliente, faça login no portal.'); } else if (result.error?.includes('CPF')) { throw new Error('⚠️ Já existe uma conta cadastrada com este CPF. Se você já é cliente, faça login no portal.'); } else if (result.error?.includes('CNPJ')) { throw new Error('⚠️ Já existe uma conta cadastrada com este CNPJ. Se você já é cliente, faça login no portal.'); } else { throw new Error(result.error || 'Erro ao enviar cadastro'); } } // Salvar nome do cliente no sessionStorage para exibir na página de sucesso const customerName = personType === 'pf' ? formData.full_name : (formData.trade_name || formData.company_name); sessionStorage.setItem('customer_name', customerName); sessionStorage.setItem('customer_email', formData.email); // Redirecionar para página de sucesso com mensagem router.push('/cliente/cadastro/sucesso'); } catch (err: any) { setError(err.message); // Scroll para o topo para garantir que o usuário veja o erro window.scrollTo({ top: 0, behavior: 'smooth' }); } finally { setIsSubmitting(false); } }; const handleChange = (e: React.ChangeEvent) => { if (error) setError(null); const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); }; const handlePersonTypeChange = (type: PersonType) => { setPersonType(type); setError(null); if (type === 'pf') { clearAutoFilledFields(); setFormData(prev => ({ ...prev, cnpj: '', company_name: '', trade_name: '', responsible_name: '', })); } else { setFormData(prev => ({ ...prev, cpf: '', full_name: '', })); } }; const handleCepBlur = async () => { const cep = formData.postal_code.replace(/\D/g, ''); if (cep.length !== 8) return; setIsCepLoading(true); try { const response = await fetch(`https://viacep.com.br/ws/${cep}/json/`); const data = await response.json(); if (data.erro) { setError('CEP não encontrado'); return; } setFormData(prev => ({ ...prev, street: data.logradouro || '', neighborhood: data.bairro || '', city: data.localidade || '', state: data.uf || '', })); } catch (err) { setError('Erro ao buscar CEP'); } finally { setIsCepLoading(false); } }; const renderReviewSection = () => (

Revise seus dados antes de enviar

{logoPreview && (
Logo

Logo carregado

{logoFile?.name}

)}

Tipo

{personType === 'pf' ? 'Pessoa Física' : 'Pessoa Jurídica'}

{personType === 'pf' ? ( <>

CPF

{formData.cpf}

Nome

{formData.full_name}

) : ( <>

CNPJ

{formData.cnpj}

Razão Social

{formData.company_name}

{formData.trade_name &&

Nome Fantasia

{formData.trade_name}

}

Responsável

{formData.responsible_name}

)}

Email

{formData.email}

Telefone

{formData.phone}

Endereço

{formData.street}, {formData.number}{formData.complement && `, ${formData.complement}`}

{formData.neighborhood} - {formData.city}/{formData.state}

CEP: {formData.postal_code}

{formData.message &&

Mensagem

{formData.message}

}
); return (
{/* Header com Logo e Nome da Agência */}
{branding.logo_url ? ( {branding.name ) : (
{branding.name?.substring(0, 2).toUpperCase() || 'AG'}
)} {branding.name && (

Bem-vindo ao portal de

{branding.name}

)}

Cadastre-se como Cliente

Preencha o formulário em etapas para solicitar acesso ao nosso portal

{/* Progress Stepper */}
{/* Step 1: Identificação */} {currentStep === 1 && (
{personType === 'pf' ? ( <>
handleCpfChange(e.target.value)} className="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[var(--brand-color)] focus:ring-opacity-50 focus:border-transparent" placeholder="000.000.000-00" maxLength={14} />
) : ( <>
handleCnpjChange(e.target.value)} onBlur={handleCnpjBlur} className={`block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[var(--brand-color)] focus:ring-opacity-50 focus:border-transparent ${isCnpjLoading ? 'pr-10' : ''}`} placeholder="00.000.000/0000-00" maxLength={18} /> {isCnpjLoading && (
)}
{hasAutoFilledFields && (
Campos preenchidos automaticamente foram bloqueados.
)}
{isFieldLocked('company_name') &&

Dado oficial preenchido automaticamente via CNPJ.

}
{isFieldLocked('responsible_name') &&

Campo bloqueado para manter os dados cadastrais.

}
{isFieldLocked('trade_name') &&

Nome fantasia sincronizado com a Receita Federal.

}
)}
)} {/* Step 2: Contato */} {currentStep === 2 && (
{ if (e.target.value && !validateEmail(e.target.value)) { setError('❌ Digite um e-mail válido (exemplo: seu@email.com)'); } else { setError(null); } }} className="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[var(--brand-color)] focus:ring-opacity-50 focus:border-transparent" placeholder="seu@email.com" />

Digite um e-mail válido para receber suas credenciais de acesso

handlePhoneChange(e.target.value)} className="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[var(--brand-color)] focus:ring-opacity-50 focus:border-transparent" placeholder="(00) 00000-0000" maxLength={15} />
)} {/* Step 3: Endereço */} {currentStep === 3 && (

Endereço Completo

handlePostalCodeChange(e.target.value)} onBlur={handleCepBlur} className="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[var(--brand-color)] focus:ring-opacity-50 focus:border-transparent" placeholder="00000-000" maxLength={9} /> {isCepLoading &&
}
)} {/* Step 4: Logo & Mensagem */} {currentStep === 4 && (
{/* Upload de Logo */}
{logoPreview ? (
Preview
) : ( <>

ou arraste e solte

PNG, JPG até 5MB

)}
{/* Mensagem */}