| import React, { useEffect, useState } from 'react'; |
| import { Loader2 } from 'lucide-react'; |
|
|
| |
| |
| |
| |
| export default function CtaFormEmbed() { |
| const publicId = window.location.pathname.split('/').filter(Boolean).pop() || ''; |
| const [phase, setPhase] = useState('loading'); |
| const [form, setForm] = useState(null); |
| const [error, setError] = useState(''); |
| const [values, setValues] = useState({}); |
| const [submitting, setSubmitting] = useState(false); |
| const [done, setDone] = useState(false); |
| const [doneMsg, setDoneMsg] = useState(''); |
|
|
| useEffect(() => { |
| if (!publicId) { |
| setError('Invalid form link'); |
| setPhase('error'); |
| return; |
| } |
| fetch(`/api/public/cta-forms/${encodeURIComponent(publicId)}`) |
| .then((r) => r.json().then((j) => ({ ok: r.ok, j }))) |
| .then(({ ok, j }) => { |
| if (!ok) throw new Error(j.detail || 'Form not found'); |
| setForm(j); |
| const init = {}; |
| (j.fields || []).forEach((f) => { |
| init[f.key] = ''; |
| }); |
| setValues(init); |
| setPhase('ready'); |
| }) |
| .catch((e) => { |
| setError(e.message || 'Could not load form'); |
| setPhase('error'); |
| }); |
| }, [publicId]); |
|
|
| const onChange = (key, val) => { |
| setValues((prev) => ({ ...prev, [key]: val })); |
| }; |
|
|
| const onSubmit = async (e) => { |
| e.preventDefault(); |
| setSubmitting(true); |
| setError(''); |
| try { |
| const res = await fetch(`/api/public/cta-forms/${encodeURIComponent(publicId)}/submit`, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ |
| fields: values, |
| page_url: window.location.href, |
| }), |
| }); |
| const data = await res.json().catch(() => ({})); |
| if (!res.ok) { |
| throw new Error( |
| typeof data.detail === 'string' ? data.detail : 'Submission failed' |
| ); |
| } |
| setDone(true); |
| setDoneMsg(data.message || 'Thank you — we will be in touch soon.'); |
| } catch (err) { |
| setError(err.message || 'Submission failed'); |
| } finally { |
| setSubmitting(false); |
| } |
| }; |
|
|
| if (phase === 'loading') { |
| return ( |
| <div className="flex min-h-[12rem] items-center justify-center bg-transparent p-4"> |
| <Loader2 className="h-8 w-8 animate-spin text-violet-600" /> |
| </div> |
| ); |
| } |
|
|
| if (phase === 'error') { |
| return ( |
| <div className="flex min-h-[12rem] items-center justify-center bg-transparent p-4"> |
| <p className="text-sm text-red-600">{error}</p> |
| </div> |
| ); |
| } |
|
|
| return ( |
| <div className="bg-transparent p-4"> |
| <div className="mx-auto w-full max-w-lg rounded-xl border border-slate-200 bg-white p-4"> |
| {done ? ( |
| <p className="text-sm text-green-700 text-center py-6">{doneMsg}</p> |
| ) : ( |
| <form onSubmit={onSubmit} className="space-y-3"> |
| {(form.fields || []).map((f) => ( |
| <div key={f.key}> |
| <label className="block text-xs font-medium text-slate-600 mb-1"> |
| {f.label} |
| {f.required ? ' *' : ''} |
| </label> |
| {f.type === 'textarea' ? ( |
| <textarea |
| className="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm" |
| rows={4} |
| required={!!f.required} |
| value={values[f.key] || ''} |
| onChange={(e) => onChange(f.key, e.target.value)} |
| /> |
| ) : ( |
| <input |
| type={f.type || 'text'} |
| className="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm" |
| required={!!f.required} |
| value={values[f.key] || ''} |
| onChange={(e) => onChange(f.key, e.target.value)} |
| /> |
| )} |
| </div> |
| ))} |
| {error ? <p className="text-xs text-red-600">{error}</p> : null} |
| <button |
| type="submit" |
| disabled={submitting} |
| className="w-full rounded-lg bg-violet-600 py-2.5 text-sm font-semibold text-white hover:bg-violet-700 disabled:opacity-60" |
| > |
| {submitting ? 'Sending…' : 'Submit'} |
| </button> |
| </form> |
| )} |
| </div> |
| </div> |
| ); |
| } |
|
|