/* ═══════════════════════════════════════════════════════════════════ MODAL COMPONENT Application form with multi-step flow — Máscara de telefone BR: (XX) XXXX-XXXX / (XX) 9 XXXX-XXXX — Validação de e-mail com regex + feedback visual em tempo real ═══════════════════════════════════════════════════════════════════ */ /* ─── Helpers de validação e máscara ─────────────────────────────── */ function maskPhone(raw) { const d = raw.replace(/\D/g, "").slice(0, 11); if (!d) return ""; if (d.length <= 2) return `(${d}`; if (d.length <= 6) return `(${d.slice(0,2)}) ${d.slice(2)}`; if (d.length <= 10) return `(${d.slice(0,2)}) ${d.slice(2,6)}-${d.slice(6)}`; /* 11 dígitos → celular: (XX) 9 XXXX-XXXX */ return `(${d.slice(0,2)}) ${d.slice(2,3)} ${d.slice(3,7)}-${d.slice(7)}`; } function isValidEmail(v) { return /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(v.trim()); } function isValidPhone(v) { const d = v.replace(/\D/g, ""); return d.length === 0 || d.length === 10 || d.length === 11; } /* ─── Campo com estado de validação ─────────────────────────────── */ function Field({ label, error, valid, children }) { return (
{children} {error && ( {error} )}
); } /* ─── Modal principal ────────────────────────────────────────────── */ function ApplyModal({ open, onClose }) { const [step, setStep] = useState(0); const [data, setData] = useState({ revenue: "", challenge: "", name: "", company: "", role: "", site: "", email: "", phone: "", }); /* Rastreia quais campos já foram tocados (blur) para mostrar erros */ const [touched, setTouched] = useState({}); const modalRef = useRef(null); const totalSteps = 4; useEffect(() => { if (open) { setStep(0); setTouched({}); /* Foca o primeiro elemento interativo ao abrir */ const tid = setTimeout(() => { const focusable = modalRef.current?.querySelector( 'button:not([disabled]), input, select, textarea, [tabindex]:not([tabindex="-1"])' ); focusable?.focus(); }, 80); return () => clearTimeout(tid); } }, [open]); /* Escape + focus trap */ useEffect(() => { if (!open) return; const handleKey = (e) => { if (e.key === "Escape") { onClose(); return; } if (e.key !== "Tab" || !modalRef.current) return; const focusable = Array.from( modalRef.current.querySelectorAll( 'button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])' ) ); if (!focusable.length) return; const first = focusable[0]; const last = focusable[focusable.length - 1]; if (e.shiftKey) { if (document.activeElement === first) { e.preventDefault(); last.focus(); } } else { if (document.activeElement === last) { e.preventDefault(); first.focus(); } } }; document.addEventListener("keydown", handleKey); return () => document.removeEventListener("keydown", handleKey); }, [open, onClose]); const update = (k, v) => setData((d) => ({ ...d, [k]: v })); const touch = (k) => setTouched((t) => ({ ...t, [k]: true })); /* Máscara de telefone aplicada no onChange */ const handlePhone = (raw) => update("phone", maskPhone(raw)); /* ── Erros do step 3 ── */ const emailError = (() => { if (!touched.email) return null; if (!data.email.trim()) return "E-mail é obrigatório."; if (!isValidEmail(data.email)) return "Informe um e-mail válido (ex: nome@empresa.com)."; return null; })(); const phoneError = (() => { if (!touched.phone || !data.phone) return null; if (!isValidPhone(data.phone)) return "Telefone incompleto. Ex: (11) 9 8765-4321."; return null; })(); const canAdvance = () => { if (step === 0) return !!data.revenue; if (step === 1) return data.challenge.trim().length > 10; if (step === 2) return data.name.trim() && data.company.trim() && data.role; if (step === 3) return isValidEmail(data.email) && isValidPhone(data.phone); return true; }; /* Ao tentar avançar no step 3, marca todos os campos como tocados */ const handleNext = () => { if (step === 3) { setTouched((t) => ({ ...t, email: true, phone: !!data.phone })); } if (canAdvance()) setStep((s) => Math.min(totalSteps, s + 1)); }; const back = () => setStep((s) => Math.max(0, s - 1)); return (
{ if (e.target === e.currentTarget) onClose(); }} >
{step < totalSteps && ( <>

{[ "Sobre sua operação atual", "O maior desafio hoje", "Quem está aplicando", "Como podemos te contatar", ][step]}

{/* ── Step 0: Faturamento ── */} {step === 0 && (
{[ "Abaixo de R$ 100k/mês", "R$ 100k — R$ 500k/mês", "R$ 500k — R$ 1M/mês", "R$ 1M — R$ 5M/mês", "Acima de R$ 5M/mês", ].map((opt) => (
update("revenue", opt)} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); update("revenue", opt); } }} > {opt}
))}
)} {/* ── Step 1: Desafio ── */} {step === 1 && ( <>