/* ===== Growing Now — Typeform-style contact flow (hosted by Zuki) ===== */
(function () {
const { useState, useEffect, useRef } = React;
const Icon = window.Icon;
window.gcalLink = function (lang = 'es') {
return window.GN.bookingUrl('zuki', lang, 'lead_form');
};
window.saveLead = async function (lead) {
const response = await fetch('/api/submit-lead', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(lead)
});
if (!response.ok) throw new Error('Lead submission failed');
const data = await response.json();
if (!data.ok) throw new Error('Lead submission was not confirmed');
return data;
};
const TIER = {
hot: { es: 'Cliente ideal', en: 'Ideal client', c: '#2E8C3A' },
warm: { es: 'Buen potencial', en: 'Good potential', c: '#F7941E' },
cold: { es: 'Por explorar', en: 'To explore', c: '#2E8BD6' },
};
function Gauge({ value, color, label }) {
const r = 54, c = 2 * Math.PI * r, off = c * (1 - value / 100);
return (
);
}
function ContactModal({ lang, onClose, onChat }) {
const L = window.I18N.STR[lang];
const Q = window.GN.questions;
const zuki = window.GN.byId.zuki;
const [idx, setIdx] = useState(-1); // -1 intro, n = done
const [ans, setAns] = useState({});
const [err, setErr] = useState('');
const [result, setResult] = useState(null);
const [submitting, setSubmitting] = useState(false);
const inputRef = useRef(null);
useEffect(() => { document.body.classList.add('no-scroll'); return () => document.body.classList.remove('no-scroll'); }, []);
useEffect(() => { setErr(''); if (inputRef.current) setTimeout(() => inputRef.current.focus(), 120); }, [idx]);
const total = Q.length;
const q = idx >= 0 && idx < total ? Q[idx] : null;
function next() {
if (q) {
if (q.required && !ans[q.id]) { setErr(L.form_required); return; }
if (q.type === 'email' && ans.email && !/^\S+@\S+\.\S+$/.test(ans.email)) { setErr(L.form_required); return; }
}
if (idx === total - 1) return submit();
setIdx(idx + 1);
}
function back() { if (idx > -1) setIdx(idx - 1); }
async function submit() {
if (submitting) return;
const sc = window.GN.score(ans);
const lead = { id: 'L' + Date.now().toString(36), ts: Date.now(), agent_id: 'zuki', source: 'lead_form', answers: ans, score: sc, status: 'new', lang };
setSubmitting(true);
setErr('');
try {
await window.saveLead(lead);
setResult({ lead, sc });
setIdx(total);
} catch (error) {
setErr(L.form_submit_error);
} finally {
setSubmitting(false);
}
}
const setVal = (v) => setAns(a => ({ ...a, [q.id]: v }));
const toggle = (v) => setAns(a => { const cur = a[q.id] || []; return { ...a, [q.id]: cur.includes(v) ? cur.filter(x => x !== v) : [...cur, v] }; });
const chooseSingle = (v) => {
setAns(a => ({ ...a, [q.id]: v }));
setErr('');
setTimeout(() => setIdx(current => current + 1), 220);
};
const progress = idx < 0 ? 0 : idx >= total ? 100 : Math.round((idx / total) * 100);
return (
{/* progress */}
{idx === -1 && (
)}
{q && (
{idx + 1} / {total}
{q.q[lang]}
{q.sub &&
{q.sub[lang]}
}
{err &&
{err}
}
{(q.type === 'text' || q.type === 'email' || q.type === 'textarea') && {L.form_enter}}
{idx > 0 && }
)}
{idx === total && result && (() => {
const tier = TIER[result.sc.tier];
return (
{L.form_done_title}
{L.form_done_sub}
);
})()}
);
}
const fieldStyle = { width: '100%', fontSize: '1.15rem', padding: '14px 4px', border: 'none', borderBottom: '2px solid rgba(0,0,0,.18)', background: 'transparent', outline: 'none', fontFamily: 'var(--font-body)', color: 'var(--ink)' };
const optStyle = { display: 'flex', alignItems: 'center', gap: 14, padding: '14px 18px', borderRadius: 16, border: '2px solid', cursor: 'pointer', textAlign: 'left', fontSize: '1.02rem', transition: 'all .18s', fontFamily: 'var(--font-body)' };
window.ContactModal = ContactModal;
})();