/* FriendlyPhotons — Marketing site UI kit · Contact Us modal. Reuses the .wl-* modal styles (backdrop, fields, submit, success) for visual consistency with the Get Early Access modal. */ const CONTACT_CONFIG = { // Parallel to /api/waitlist. ⚠ WIRE-UP: confirm this endpoint + payload with Replit // (the waitlist endpoint is authoritative; this one is assumed by convention). apiUrl: "https://app.friendlyphotons.ai/api/contact", turnstileSiteKey: "0x4AAAAAADNo7TE_fd0LJFDF", liveHosts: ["friendlyphotons.ai", "www.friendlyphotons.ai"] }; function ContactModal({ open, onClose }) { const [name, setName] = React.useState(""); const [email, setEmail] = React.useState(""); const [subject, setSubject] = React.useState(""); const [message, setMessage] = React.useState(""); const [hp, setHp] = React.useState(""); const [done, setDone] = React.useState(false); const [err, setErr] = React.useState(""); const [loading, setLoading] = React.useState(false); const turnstileRef = React.useRef(null); const turnstileIdRef = React.useRef(null); // Explicit Turnstile render — conditional-mount modal misses Cloudflare's // one-shot auto-scan. Pattern shared with waitlist-modal.jsx.txt. React.useEffect(() => { if (!open || !CONTACT_CONFIG.turnstileSiteKey) return; let cancelled = false; const tryRender = () => { if (cancelled || turnstileIdRef.current) return; if (window.turnstile && turnstileRef.current) { try { turnstileIdRef.current = window.turnstile.render(turnstileRef.current, { sitekey: CONTACT_CONFIG.turnstileSiteKey, }); } catch (_) {} } else { setTimeout(tryRender, 100); } }; tryRender(); return () => { cancelled = true; if (turnstileIdRef.current && window.turnstile) { try { window.turnstile.remove(turnstileIdRef.current); } catch (_) {} turnstileIdRef.current = null; } }; }, [open]); React.useEffect(() => { if (open) { setDone(false); setErr(""); setLoading(false); } }, [open]); if (!open) return null; const isLive = CONTACT_CONFIG.liveHosts.indexOf(window.location.hostname) !== -1; const valid = name.trim().length > 0 && /.+@.+\..+/.test(email) && message.trim().length >= 10; const submit = (e) => { e.preventDefault(); if (!valid || loading) return; setErr(""); const payload = { name: name.trim(), email: email.trim(), message: message.trim() }; if (subject) payload.subject = subject; if (hp) payload.website = hp; // honeypot — server rejects if filled if (window.turnstile && CONTACT_CONFIG.turnstileSiteKey && turnstileIdRef.current) { try { const tok = window.turnstile.getResponse(turnstileIdRef.current); if (tok) payload.captchaToken = tok; } catch (_) {} } // Demo mode (off the live domain): show the flow without a real POST. if (!isLive || !CONTACT_CONFIG.apiUrl) { setDone(true); return; } setLoading(true); fetch(CONTACT_CONFIG.apiUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }) .then((res) => res.json().catch(() => ({})).then((body) => ({ status: res.status, ok: res.ok, body }))) .then((r) => { if (r.ok) { setDone(true); return; } setLoading(false); if (r.status === 429) setErr("Too many attempts — please try again in a moment."); else setErr((r.body && r.body.error) || "Something went wrong. Please try again."); if (window.turnstile && CONTACT_CONFIG.turnstileSiteKey && turnstileIdRef.current) { try { window.turnstile.reset(turnstileIdRef.current); } catch (_) {} } }) .catch(() => { setLoading(false); setErr("Network error — please check your connection and try again."); }); }; return (
Thanks for reaching out — we'll get back to you at the email you provided. Talk soon.