/* global React, LocationFields */ const { useState, useEffect, useRef, useCallback } = React; // ------------------------------------------------------------ // Pactum products + role options + interest type // ------------------------------------------------------------ const PRODUCTS = [ { id: "abrir-escola", label: "Abrir uma escola", key: "1" }, { id: "treinamento", label: "Treinamento de professores", key: "2" }, { id: "consultoria", label: "Consultoria", key: "3" }, { id: "curriculo", label: "Currículo", key: "4" }]; const ROLES = [ "Diretor", "Coordenador pedagógico", "Professor", "Pai/Mãe", "Outro"]; const INTEREST = [ { id: "abrir", title: "Abrir uma escola", sub: "Fundar uma instituição cristã clássica", key: "A" }, { id: "treinamento", title: "Treinamento de professores", sub: "Formação continuada para a equipe", key: "T" }, { id: "ambos", title: "Ambos", sub: "Está interessado nas duas frentes", key: "B" }]; // ------------------------------------------------------------ // Phone mask: (xx) xxxxx-xxxx // ------------------------------------------------------------ function maskPhone(v) { const d = v.replace(/\D/g, "").slice(0, 11); 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)}`; return `(${d.slice(0, 2)}) ${d.slice(2, 7)}-${d.slice(7)}`; } function isValidEmail(v) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v); } // ------------------------------------------------------------ // Submission: posts to enviar.php which handles Kommo + email. // In local/preview (file://, no PHP), falls back to demo mode. // ------------------------------------------------------------ const ENDPOINT = "enviar.php"; async function submitLead(payload) { // Demo fallback when running without the PHP backend const isPreview = location.protocol === "file:" || location.hostname.endsWith(".sandbox.dev") || location.hostname.includes("localhost"); if (isPreview && !window.__forceLive) { console.log("[DEMO] Lead payload →", payload); await new Promise((r) => setTimeout(r, 500)); return { ok: true, id: `demo_${Date.now().toString(36)}` }; } const r = await fetch(ENDPOINT, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }); const data = await r.json().catch(() => ({})); if (!r.ok || !data.ok) throw new Error(data.error || "Falha no envio"); return { ok: true, id: `lead_${Date.now().toString(36)}`, kommo: data.kommo, email: data.email }; } // ------------------------------------------------------------ // Main form // ------------------------------------------------------------ window.LeadForm = function LeadForm() { const [name, setName] = useState(""); const [phone, setPhone] = useState(""); const [email, setEmail] = useState(""); const [school, setSchool] = useState(""); const [uf, setUf] = useState(null); const [city, setCity] = useState(""); const [role, setRole] = useState(""); const [roleOther, setRoleOther] = useState(""); const [products, setProducts] = useState([]); const [interest, setInterest] = useState(""); const [needs, setNeeds] = useState(""); const [errors, setErrors] = useState({}); const [submitting, setSubmitting] = useState(false); const [success, setSuccess] = useState(null); // { id } const [toast, setToast] = useState(null); const [count, setCount] = useState(() => { return parseInt(localStorage.getItem("pactum_lead_count") || "0", 10); }); const nameRef = useRef(null); // Auto-focus name on mount + after reset useEffect(() => { if (!success) nameRef.current?.focus(); }, [success]); // Toggle product const toggleProduct = useCallback((id) => { setProducts((p) => p.includes(id) ? p.filter((x) => x !== id) : [...p, id]); }, []); // Keyboard shortcuts useEffect(() => { function onKey(e) { // Ctrl/Cmd + Enter → submit if ((e.ctrlKey || e.metaKey) && e.key === "Enter") { e.preventDefault(); if (success) { handleNewLead(); } else { handleSubmit(); } return; } // Esc on success → new lead if (e.key === "Escape" && success) { handleNewLead(); return; } // Alt+1..4 toggle products — use e.code (Digit1) since macOS Alt+1 produces ¡ if (e.altKey && !e.ctrlKey && !e.metaKey && !success) { const m = /^Digit([1-9])$/.exec(e.code || ""); if (m) { const n = parseInt(m[1], 10); if (n >= 1 && n <= PRODUCTS.length) { e.preventDefault(); toggleProduct(PRODUCTS[n - 1].id); return; } } } // Alt+A/T/B → interest — use e.code (KeyA, KeyT, KeyB) if (e.altKey && !e.ctrlKey && !e.metaKey && !success) { const m = /^Key([A-Z])$/.exec(e.code || ""); if (m) { const k = m[1]; const i = INTEREST.find((x) => x.key === k); if (i) { e.preventDefault(); setInterest(i.id); } } } } window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [success, toggleProduct]); function validate() { const errs = {}; if (!name.trim()) errs.name = "Informe o nome."; if (!phone.trim()) errs.phone = "Informe o telefone.";else if (phone.replace(/\D/g, "").length < 10) errs.phone = "Telefone incompleto."; if (!email.trim()) errs.email = "Informe o e-mail.";else if (!isValidEmail(email.trim())) errs.email = "E-mail inválido."; if (!school.trim()) errs.school = "Informe a escola."; if (!uf) errs.uf = "Selecione o estado."; if (!city.trim()) errs.city = "Informe a cidade."; if (!role) errs.role = "Selecione o cargo."; if (role === "Outro" && !roleOther.trim()) errs.role = "Descreva o cargo."; if (products.length === 0) errs.products = "Selecione ao menos um produto."; if (!interest) errs.interest = "Selecione o tipo de interesse."; setErrors(errs); return Object.keys(errs).length === 0; } async function handleSubmit(e) { if (e?.preventDefault) e.preventDefault(); if (submitting) return; if (!validate()) { setToast({ type: "error", msg: "Preencha os campos destacados." }); setTimeout(() => setToast(null), 2400); return; } setSubmitting(true); const payload = { capturedAt: new Date().toISOString(), source: "evento-presencial", name: name.trim(), phone: phone.trim(), email: email.trim(), school: school.trim(), city: city.trim(), uf: uf?.sigla, ufName: uf?.nome, role: role === "Outro" ? roleOther.trim() : role, products: products.map((id) => PRODUCTS.find((p) => p.id === id)?.label), interest: INTEREST.find((i) => i.id === interest)?.title, needs: needs.trim() }; try { const res = await submitLead(payload); const newCount = count + 1; setCount(newCount); localStorage.setItem("pactum_lead_count", String(newCount)); setSuccess({ id: res.id, name: payload.name }); } catch (err) { setToast({ type: "error", msg: "Falha no envio. Tente novamente." }); setTimeout(() => setToast(null), 2400); } finally { setSubmitting(false); } } function handleNewLead() { setName("");setPhone("");setEmail("");setSchool(""); setUf(null);setCity("");setRole("");setRoleOther(""); setProducts([]);setInterest("");setNeeds(""); setErrors({}); setSuccess(null); } // ------------------------------------------------------------ // SUCCESS STATE // ------------------------------------------------------------ if (success) { return ; } // ------------------------------------------------------------ // FORM // ------------------------------------------------------------ return ( <>
Pactum
Sessão · {new Date().toLocaleDateString("pt-BR", { day: "2-digit", month: "short" })}
{count} {count === 1 ? "contato capturado" : "contatos capturados"}

Novo Contato

Use Tab para avançar e Ctrl + Enter para enviar.

{/* SECTION: contato */}
Contato
setName(e.target.value)} placeholder="Ex.: João da Silva Pereira" autoComplete="off" />
{errors.name || ""}
setPhone(maskPhone(e.target.value))} placeholder="(98) 98428-1268" inputMode="tel" autoComplete="off" />
{errors.phone || ""}
setEmail(e.target.value)} placeholder="nome@escola.com.br" autoComplete="off" />
{errors.email || ""}
{/* SECTION: escola */}
Escola
setSchool(e.target.value)} placeholder="Ex.: Escola Cristã Reformada" autoComplete="off" />
{errors.school || ""}
{errors.role || ""}
{role === "Outro" &&
setRoleOther(e.target.value)} placeholder="Descreva o cargo" />
}
{/* SECTION: interesse */}
Interesse
{PRODUCTS.map((p, i) => )}
{errors.products || ""}
{INTEREST.map((i) => )}
{errors.interest || ""}
{/* SECTION: necessidades */}
Programa de necessidades