/* Lead funnel modals: Register Interest (3-step), Brochure (gated), Private Viewing, Broker Enquiry. */ const { useState, useEffect, useMemo, useCallback } = React; // ============ shared modal frame ============ function ModalFrame({ title, subtitle, onClose, step, totalSteps, children, footer }) { useEffect(() => { const onKey = (e) => { if (e.key === "Escape") onClose(); }; window.addEventListener("keydown", onKey); document.body.style.overflow = "hidden"; return () => { window.removeEventListener("keydown", onKey); document.body.style.overflow = ""; }; }, [onClose]); return (
{ if (e.target.classList.contains("modal-bg")) onClose(); }}>
{step != null && (
Step {step} of {totalSteps}
)}

{title}

{subtitle &&
{subtitle}
}
{step != null && (
{Array.from({ length: totalSteps }).map((_, i) => (
))}
)}
{children}
{footer &&
{footer}
}
); } function SecureLabel() { return (
Encrypted · Routed by region · No call centre
); } // ============ REGISTER INTEREST ============ function RegisterInterestModal({ project, onClose }) { const [step, setStep] = useState(1); const [data, setData] = useState({ region: "AE", firstName: "", lastName: "", email: "", dial: "+971", phone: "", motivation: "investor", timeframe: "3-months", budget: "", consent: false, whatsappOk: true, }); const [errors, setErrors] = useState({}); const [done, setDone] = useState(null); const update = (k, v) => { setData({ ...data, [k]: v }); setErrors({ ...errors, [k]: null }); }; const validateStep = () => { const e = {}; if (step === 1) { if (!data.region) e.region = "Required"; } if (step === 2) { if (!data.firstName.trim()) e.firstName = "Required"; if (!data.lastName.trim()) e.lastName = "Required"; if (!data.email.match(/^[^@\s]+@[^@\s]+\.[^@\s]+$/)) e.email = "Email format"; if (!data.phone.match(/^[0-9 \-]{6,}$/)) e.phone = "Phone format"; } if (step === 3) { if (!data.consent) e.consent = "Required"; } setErrors(e); return Object.keys(e).length === 0; }; const next = () => { if (validateStep()) setStep(step + 1); }; const submit = () => { if (!validateStep()) return; const ref = "REG-" + Math.random().toString(36).slice(2, 8).toUpperCase(); const region = REGIONS.find(r => r.code === data.region); setDone({ ref, region }); }; if (done) { return (

Routed to {done.region.desk}.

Your enquiry for {project.name} is queued with the {done.region.label} desk. A relationship manager will reach you on the phone or WhatsApp number you provided within one business day.

Reference · {done.ref}
Open WhatsApp
); } return (
{step > 1 && } {step < 3 ? ( ) : ( )}
} > {step === 1 && ( <>

Binghatti routes leads to a real regional desk — UAE, KSA, UK or China. Pick the country you'd like to be contacted from.

{REGIONS.map((r) => ( ))}
{REGIONS.find(r => r.code === data.region).desk}
{REGIONS.find(r => r.code === data.region).line} · {REGIONS.find(r => r.code === data.region).email}
)} {step === 2 && ( <>
update("firstName", e.target.value)} placeholder="Hessa" /> {errors.firstName &&
{errors.firstName}
}
update("lastName", e.target.value)} placeholder="Al Maktoum" /> {errors.lastName &&
{errors.lastName}
}
update("email", e.target.value)} placeholder="you@domain.com" /> {errors.email &&
{errors.email}
}
update("phone", e.target.value)} placeholder="50 123 4567" />
{errors.phone &&
{errors.phone}
}
)} {step === 3 && ( <>
{errors.consent &&
Consent required
}
Summary
{project.name} · {REGIONS.find(r => r.code === data.region).desk}
{data.firstName} {data.lastName} · {data.email}
{data.dial} {data.phone}
)}
); } // ============ BROCHURE GATE ============ function BrochureModal({ project, onClose }) { const [data, setData] = useState({ name: "", email: "", region: "AE", consent: false }); const [errors, setErrors] = useState({}); const [stage, setStage] = useState("form"); // form | sending | done const [progress, setProgress] = useState(0); const submit = () => { const e = {}; if (!data.name.trim()) e.name = "Required"; if (!data.email.match(/^[^@\s]+@[^@\s]+\.[^@\s]+$/)) e.email = "Email format"; if (!data.consent) e.consent = "Consent required"; setErrors(e); if (Object.keys(e).length) return; setStage("sending"); let p = 0; const t = setInterval(() => { p += 7 + Math.random() * 10; if (p >= 100) { p = 100; clearInterval(t); setTimeout(() => setStage("done"), 250); } setProgress(p); }, 110); }; if (stage === "sending") { return (
Building tailored PDF · {Math.floor(progress)}%
· Verifying email…
35 ? 1 : .3 }}>· Routing to {REGIONS.find(r => r.code === data.region).desk}…
65 ? 1 : .3 }}>· Compiling project pack…
90 ? 1 : .3 }}>· Attaching brochure PDF…
); } if (stage === "done") { return (

Brochure on the way.

We've sent the {project.name} brochure pack to {data.email}. The {REGIONS.find(r => r.code === data.region).label} desk is copied — expect a follow-up within one business day.

Pack ID · BRO-{Math.random().toString(36).slice(2, 8).toUpperCase()}
); } return ( } >

The brochure is released by email — not as an anonymous download. We share campaign-specific PDFs with verified contacts so we can follow up properly.

{ setData({ ...data, name: e.target.value }); setErrors({ ...errors, name: null }); }} placeholder="Hessa Al Maktoum" /> {errors.name &&
{errors.name}
}
{ setData({ ...data, email: e.target.value }); setErrors({ ...errors, email: null }); }} placeholder="you@domain.com" /> {errors.email &&
{errors.email}
}
{REGIONS.map((r) => ( ))}
{errors.consent &&
{errors.consent}
}
); } // ============ PRIVATE VIEWING ============ function ViewingModal({ project, onClose }) { const [data, setData] = useState({ mode: "experience", name: "", email: "", phone: "", dial: "+971", date: null, slot: null, region: "AE", consent: false, }); const [errors, setErrors] = useState({}); const [done, setDone] = useState(false); // Generate next 14 days const dates = useMemo(() => { const out = []; const today = new Date(); for (let i = 1; i <= 12; i++) { const d = new Date(today); d.setDate(today.getDate() + i); out.push({ iso: d.toISOString().slice(0, 10), weekday: ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"][d.getDay()], day: d.getDate(), }); } return out; }, []); const slots = ["10:00", "11:30", "13:00", "14:30", "16:00", "17:30"]; const submit = () => { const e = {}; if (!data.name.trim()) e.name = "Required"; if (!data.email.match(/^[^@\s]+@[^@\s]+\.[^@\s]+$/)) e.email = "Email format"; if (!data.phone.match(/^[0-9 \-]{6,}$/)) e.phone = "Phone format"; if (!data.date) e.date = "Pick a date"; if (!data.slot) e.slot = "Pick a slot"; if (!data.consent) e.consent = "Required"; setErrors(e); if (Object.keys(e).length === 0) setDone(true); }; if (done) { return (

Viewing confirmed.

We've reserved {data.date} at {data.slot} for {project.name} ({data.mode === "experience" ? "Experience Centre" : "Video walk-through"}). A calendar invite is on its way to {data.email}.

Booking · VW-{Math.random().toString(36).slice(2, 8).toUpperCase()}
); } return ( } >
{[ { v: "experience", t: "Experience Centre", s: "In person · Dubai" }, { v: "video", t: "Video walk-through", s: "Zoom / WhatsApp · 45 min" }, ].map(o => ( ))}
{dates.map(d => ( ))}
{errors.date &&
{errors.date}
}
{slots.map((s, i) => ( ))}
{errors.slot &&
{errors.slot}
}
setData({ ...data, name: e.target.value })}/> {errors.name &&
{errors.name}
}
setData({ ...data, email: e.target.value })}/> {errors.email &&
{errors.email}
}
setData({ ...data, phone: e.target.value })}/>
{errors.phone &&
{errors.phone}
}
{errors.consent &&
{errors.consent}
}
); } // ============ BROKER ENQUIRY ============ function BrokerModal({ project, onClose }) { const [data, setData] = useState({ company: "", license: "", name: "", email: "", phone: "", dial: "+971", consent: false }); const [errors, setErrors] = useState({}); const [done, setDone] = useState(false); const submit = () => { const e = {}; if (!data.company.trim()) e.company = "Required"; if (!data.license.trim()) e.license = "RERA broker number required"; if (!data.name.trim()) e.name = "Required"; if (!data.email.match(/^[^@\s]+@[^@\s]+\.[^@\s]+$/)) e.email = "Email format"; if (!data.consent) e.consent = "Required"; setErrors(e); if (Object.keys(e).length === 0) setDone(true); }; if (done) { return (

Broker desk notified.

Your licensed-broker enquiry for {project.name} is now with the Binghatti broker desk. They handle commercial terms separately from the buyer funnel.

Broker enquiry · BRK-{Math.random().toString(36).slice(2, 8).toUpperCase()}
); } return ( } >
setData({ ...data, company: e.target.value })}/> {errors.company &&
{errors.company}
}
setData({ ...data, license: e.target.value })}/> {errors.license &&
{errors.license}
}
setData({ ...data, name: e.target.value })}/> {errors.name &&
{errors.name}
}
setData({ ...data, email: e.target.value })}/> {errors.email &&
{errors.email}
}
setData({ ...data, phone: e.target.value })}/>
{errors.consent &&
{errors.consent}
}
); } // expose Object.assign(window, { RegisterInterestModal, BrochureModal, ViewingModal, BrokerModal });