// Shared, config-driven qualification quiz. Steps come from window.LP.quiz.steps. const { useState: useQState, useRef: useQRef } = React; const LICENSED_STATES = ['NH', 'VT', 'MA', 'CT', 'NE', 'NY', 'MD']; const ALL_STATES = [ ['AL','Alabama'],['AK','Alaska'],['AZ','Arizona'],['AR','Arkansas'],['CA','California'],['CO','Colorado'],['CT','Connecticut'],['DE','Delaware'], ['FL','Florida'],['GA','Georgia'],['HI','Hawaii'],['ID','Idaho'],['IL','Illinois'],['IN','Indiana'],['IA','Iowa'],['KS','Kansas'], ['KY','Kentucky'],['LA','Louisiana'],['ME','Maine'],['MD','Maryland'],['MA','Massachusetts'],['MI','Michigan'],['MN','Minnesota'],['MS','Mississippi'], ['MO','Missouri'],['MT','Montana'],['NE','Nebraska'],['NV','Nevada'],['NH','New Hampshire'],['NJ','New Jersey'],['NM','New Mexico'],['NY','New York'], ['NC','North Carolina'],['ND','North Dakota'],['OH','Ohio'],['OK','Oklahoma'],['OR','Oregon'],['PA','Pennsylvania'],['RI','Rhode Island'],['SC','South Carolina'], ['SD','South Dakota'],['TN','Tennessee'],['TX','Texas'],['UT','Utah'],['VT','Vermont'],['VA','Virginia'],['WA','Washington'],['WV','West Virginia'], ['WI','Wisconsin'],['WY','Wyoming'],['DC','District of Columbia'], ]; const INVESTMENT = [ { value: 'under-199', label: 'Under $199 / month', tier: 'disqualify' }, { value: '199-299', label: '$199 – $299 / month', tier: 'standard' }, { value: '299-499', label: '$299 – $499 / month', tier: 'expanded' }, { value: '499+', label: '$499 + / month', tier: 'premium' }, ]; const BEST_TIME = [ { value: 'morning', label: 'Morning · 8a–12p' }, { value: 'afternoon', label: 'Afternoon · 12p–5p' }, { value: 'evening', label: 'Evening · 5p–7p' }, { value: 'flexible', label: "I'm flexible" }, ]; function calcBMI(w, ft, inch) { w = parseFloat(w); ft = parseInt(ft); inch = parseInt(inch) || 0; if (isNaN(w) || isNaN(ft) || w < 50 || w > 800 || ft < 3 || ft > 8) return null; const t = ft * 12 + inch; const bmi = (w * 703) / (t * t); if (isNaN(bmi) || bmi < 10 || bmi > 80) return null; return Math.round(bmi * 10) / 10; } function bmiNote(bmi) { if (!bmi) return null; if (bmi < 18.5) return { tone: 'caution', headline: 'Below the typical range for a fat-loss-focused program.', body: "If your starting composition is below the typical range, the conversation may be different, possibly recomposition or something else entirely. The clinical team will discuss what makes sense for you." }; if (bmi < 25) return { tone: 'note', headline: "We've noted your starting point.", body: "Patients in this range often benefit more from composition work, preserving lean mass while reducing body fat, than from weight loss alone. GLP-1 medication may not be appropriate in this range; your provider will review." }; return { tone: 'note', headline: "We've noted your starting point.", body: "Whether medication is appropriate, and what realistic, individualized goals look like, will be decided by our clinical team after your labs and history review." }; } function Quiz() { const cfg = window.LP; const STEPS = cfg.quiz.steps; const [step, setStep] = useQState(0); const [data, setData] = useQState({ weight: '', heightFt: '', heightIn: '', state: '', firstName: '', lastName: '', email: '', investment: '', phone: '', bestTime: '', notes: '' }); const [submitting, setSubmitting] = useQState(false); const [submitted, setSubmitted] = useQState(false); const [error, setError] = useQState(null); const advanceRef = useQRef(null); const cur = STEPS[step]; const update = (k, v) => setData(d => ({ ...d, [k]: v })); const val = (k, multi) => data[k] ?? (multi ? [] : ''); const toggle = (k, v) => setData(d => { const c = d[k] || []; return { ...d, [k]: c.includes(v) ? c.filter(x => x !== v) : [...c, v] }; }); const bmi = calcBMI(data.weight, data.heightFt, data.heightIn); const medStep = STEPS.find(s => s.type === 'medical'); const flags = data.medical || []; const hard = medStep ? flags.some(f => medStep.options.find(o => o.value === f)?.hard) : false; const soft = flags.length > 0 && !flags.includes('none') && !hard; const svcStates = cfg.quiz.states || LICENSED_STATES; const licensed = svcStates.includes(data.state); const stateStep = STEPS.find(s => s.type === 'state') || {}; const stateName = ALL_STATES.find(([a]) => a === data.state)?.[1]; const canAdvance = () => { switch (cur.type) { case 'single': return !!data[cur.id]; case 'multi': return (data[cur.id] || []).length > 0; case 'composition': return !!bmi; case 'medical': return flags.length > 0; case 'state': return !!data.state; case 'lead': return data.firstName.trim() && /\S+@\S+\.\S+/.test(data.email); case 'investment': return !!data.investment; case 'final': return !!data.bestTime; default: return false; } }; const goNext = () => { if (!canAdvance() || step >= STEPS.length - 1) return; setStep(step + 1); setTimeout(() => { const el = document.getElementById('quiz-card'); if (el) window.scrollTo({ top: el.getBoundingClientRect().top + window.scrollY - 24, behavior: 'smooth' }); }, 50); }; advanceRef.current = goNext; const pickAndAdvance = (k, v) => { update(k, v); setTimeout(() => advanceRef.current && advanceRef.current(), 380); }; const labelFor = (s, v) => s.options.find(o => o.value === v)?.label || v; const handleSubmit = async (e) => { e.preventDefault(); if (!canAdvance()) return; setSubmitting(true); setError(null); const tier = INVESTMENT.find(i => i.value === data.investment)?.tier || 'unknown'; const payload = { _subject: `${cfg.subjectPrefix} — ${data.firstName} ${data.lastName} (${data.state}) [${cfg.variantTag}]${tier === 'disqualify' ? ' [DQ-BUDGET]' : ''}${hard ? ' [MED-REVIEW]' : ''}`, _replyto: data.email, _captcha: 'false', _template: 'table', 'Landing Page': cfg.variantTag, 'First Name': data.firstName, 'Last Name': data.lastName, 'Email': data.email, 'Phone': data.phone || '(not provided)', 'State': data.state, 'In Service State': licensed ? 'Yes' : (cfg.quiz.states ? 'No (therapy not offered in this state)' : 'No (waitlist)'), }; STEPS.forEach(s => { if (s.type === 'single') payload[s.field] = labelFor(s, data[s.id]); if (s.type === 'multi') payload[s.field] = (data[s.id] || []).map(v => labelFor(s, v)).join(' | '); if (s.type === 'composition') { payload['Weight (lbs)'] = data.weight; payload['Height'] = `${data.heightFt}'${data.heightIn || 0}"`; payload['BMI'] = bmi || 'n/a'; } if (s.type === 'medical') { payload['Medical Flags'] = flags.includes('none') ? 'None reported' : flags.map(f => labelFor(s, f)).join(' | '); payload['Medical Risk Level'] = hard ? 'NEEDS CLINICAL REVIEW' : soft ? 'Soft flag — review' : 'Clear'; } }); Object.assign(payload, { 'Investment Range': INVESTMENT.find(i => i.value === data.investment)?.label || 'Not specified', 'Investment Tier': tier, 'Best Time to Reach': BEST_TIME.find(t => t.value === data.bestTime)?.label || '', 'Notes': data.notes || '(none)', 'Submitted At': new Date().toISOString(), 'Referrer': document.referrer || '', }); try { const q = new URLSearchParams(window.location.search); ['utm_source','utm_medium','utm_campaign','utm_content','utm_term'].forEach(k => { if (q.get(k)) payload[k.toUpperCase()] = q.get(k); }); } catch (_) {} try { const res = await fetch('https://formsubmit.co/ajax/hello@edenwellnessclinic.com', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify(payload) }); if (!res.ok) throw new Error(); setSubmitted(true); } catch (_) { setError("Something went wrong. Please email hello@edenwellnessclinic.com directly and we'll follow up."); } finally { setSubmitting(false); } }; if (submitted) return ; const Opt = ({ s, o, multi }) => { const selected = multi ? val(s.id, true).includes(o.value) : data[s.id] === o.value; return ( ); }; return (
Step {step + 1} of {STEPS.length}
{(cur.type === 'single' || cur.type === 'multi') && (
{cur.options.map(o => )}
)} {cur.type === 'composition' && (
update('weight', e.target.value)} />
update('heightFt', e.target.value)} />ft
update('heightIn', e.target.value)} />in
{bmiNote(bmi) && (
{bmiNote(bmi).headline}
{bmiNote(bmi).body}
Body composition matters more than BMI alone. We measure both during your initial assessment.
)}
)} {cur.type === 'medical' && (
{cur.options.map(o => ( ))}
{hard &&
We'll need to talk before recommending anything.
{cur.hardMsg}
} {soft &&
Noted. We'll review carefully.
Nothing here automatically rules anything out. We'll review your full picture during your assessment.
}
)} {cur.type === 'state' && (
{data.state && !licensed &&
{cur.outHeadline ? cur.outHeadline(stateName) : `We're not yet licensed in ${stateName}.`}
{cur.outBody || "Continue with your name and email and we'll let you know as soon as we expand."}
} {data.state && licensed &&
You're in a state we serve.
{cur.inBody ? cur.inBody : (data.state === 'NH' || data.state === 'VT') ? 'Telemedicine is available throughout the state, and patients in northern NH and VT can also see us in person in Littleton.' : `Telemedicine visits are available throughout ${stateName}. In-person visits are at our Littleton, NH clinic.`}
}
)} {cur.type === 'lead' && (
update('firstName', e.target.value)} />
update('lastName', e.target.value)} />
update('email', e.target.value)} />
We'll never sell your information. Used only to follow up about your assessment.
)} {cur.type === 'investment' && (
{INVESTMENT.map(o => ( ))}
{data.investment === 'under-199' &&
We want to be honest with you up front.
Our monthly programs start at $199. Continue if you'd like; we may be able to point you toward other ways we can help, or stay in touch for the future.
}
Every program starts with an initial assessment and labs before any monthly commitment. We'll discuss exact pricing once we understand your situation.
)} {cur.type === 'final' && (
update('phone', e.target.value)} />
{BEST_TIME.map(o => )}