Novo Contato
Use
/* 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
Use
Novo Contato
{lead.name} foi enviado ao Kommo e notificado por e-mail.