// modals.jsx — Register Interest (multi-step), Brochure Gate, Private Viewing.
// Loaded after sections.jsx.
const { useState: useState_m, useEffect: useEffect_m, useMemo: useMemo_m } = React;
// Modal shell ───────────────────────────────────────────────────────────────
function Modal({ open, onClose, children, size }) {
useEffect_m(() => {
if (!open) return;
const onKey = (e) => e.key === 'Escape' && onClose();
document.addEventListener('keydown', onKey);
const prev = document.body.style.overflow;
document.body.style.overflow = 'hidden';
return () => {
document.removeEventListener('keydown', onKey);
document.body.style.overflow = prev;
};
}, [open, onClose]);
if (!open) return null;
return (
e.stopPropagation()}>
{children}
);
}
function ModalHead({ eyebrow, title, sub, onClose }) {
return (
{eyebrow}
{title}
{sub &&
{sub}
}
);
}
function SuccessScreen({ title, body, cta, onClose }) {
return (
Confirmed
{title}
{body}
{cta &&
{cta} }
);
}
// Register Interest — multi-step ────────────────────────────────────────────
function RegisterModal({ open, onClose, project, copy }) {
const F = copy.forms.register;
const [step, setStep] = useState_m(0);
const [done, setDone] = useState_m(false);
const [form, setForm] = useState_m({
fname: '', lname: '', email: '', phone: '', country: '', lang: copy._lang === 'en' ? 'English' : (copy._lang === 'ar' ? 'العربية' : copy._lang === 'ru' ? 'Русский' : '中文'),
intent: '', timing: '', budget: '', rooms: '', contact: '', notes: '', project: project.name,
});
useEffect_m(() => { if (open) { setStep(0); setDone(false); } }, [open]);
if (!open) return null;
const u = (k) => (e) => setForm({ ...form, [k]: e.target.value });
const sel = (k, val) => setForm({ ...form, [k]: val });
const s1Valid = form.fname && form.lname && form.email;
const s2Valid = form.intent && form.timing;
const submit = () => setDone(true);
return (
{done ? (
) : (
<>
01 · {F.s1}
02 · {F.s2}
03 · {F.s3}
{step === 0 && (
<>
>
)}
{step === 1 && (
<>
sel('intent', v)} />
sel('timing', v)} />
sel('budget', v)} />
>
)}
{step === 2 && (
<>
sel('rooms', v)} />
sel('contact', v)} />
{F.notes}
{F.consent}
>
)}
All enquiries handled by the AHS sales gallery at City Walk · {project.name}
{step > 0 && setStep(step - 1)}>{F.back} }
{step < 2 && (
setStep(step + 1)}>{F.next}
)}
{step === 2 && {F.submit} }
>
)}
);
}
function SelectField({ label, options, value, onChange }) {
return (
{label}
{options.map((o) => (
onChange(o)}
style={{
padding: '10px 16px',
border: '1px solid var(--border)',
background: value === o ? 'var(--text)' : 'transparent',
color: value === o ? 'var(--bg)' : 'var(--text)',
fontSize: 12.5,
letterSpacing: '0.04em',
transition: 'all 180ms ease',
}}
>{o}
))}
);
}
// Brochure Gate ──────────────────────────────────────────────────────────────
function BrochureModal({ open, onClose, project, copy }) {
const F = copy.forms.brochure;
const [done, setDone] = useState_m(false);
const [form, setForm] = useState_m({ fname: '', email: '', phone: '' });
useEffect_m(() => { if (open) { setDone(false); setForm({ fname: '', email: '', phone: '' }); } }, [open]);
if (!open) return null;
const u = (k) => (e) => setForm({ ...form, [k]: e.target.value });
return (
{done ? (
PDF
{project.name}
Residences Brochure · 2026
48 pp · 12 MB · Email-gated
) : (
<>
No prices headlined. POA / by enquiry.
setDone(true)}>{F.submit}
>
)}
);
}
// Private Viewing — date + time picker ──────────────────────────────────────
function buildMonth(refDate) {
const y = refDate.getFullYear(), m = refDate.getMonth();
const first = new Date(y, m, 1);
const startOffset = first.getDay(); // Sun = 0
const daysInMonth = new Date(y, m + 1, 0).getDate();
const cells = [];
for (let i = 0; i < startOffset; i++) cells.push(null);
for (let d = 1; d <= daysInMonth; d++) cells.push(new Date(y, m, d));
while (cells.length % 7 !== 0) cells.push(null);
return cells;
}
function ViewingModal({ open, onClose, project, copy }) {
const F = copy.forms.viewing;
const [done, setDone] = useState_m(false);
const today = useMemo_m(() => { const t = new Date(); t.setHours(0,0,0,0); return t; }, []);
const [refDate, setRefDate] = useState_m(today);
const [picked, setPicked] = useState_m(null);
const [time, setTime] = useState_m(null);
const [mode, setMode] = useState_m(F.modeOpts[0]);
const [form, setForm] = useState_m({ fname: '', email: '', phone: '', notes: '' });
useEffect_m(() => { if (open) { setDone(false); setPicked(null); setTime(null); setForm({ fname: '', email: '', phone: '', notes: '' }); } }, [open]);
if (!open) return null;
const u = (k) => (e) => setForm({ ...form, [k]: e.target.value });
const cells = buildMonth(refDate);
const monthLabel = refDate.toLocaleString(copy._lang === 'ar' ? 'ar' : copy._lang === 'ru' ? 'ru' : copy._lang === 'zh' ? 'zh' : 'en', { month: 'long', year: 'numeric' });
const dayNames = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
const slots = ['10:00','11:30','14:00','15:30','17:00','18:30'];
return (
{done ? (
{mode}
{picked && picked.toLocaleDateString('en', { weekday: 'long', month: 'long', day: 'numeric' })} · {time}
{project.name} · City Walk, Building 5, Unit 2
) : (
<>
{F.date}
setRefDate(new Date(refDate.getFullYear(), refDate.getMonth() - 1, 1))}
style={{ padding: '6px 10px', border: '1px solid var(--border)' }}>‹
{monthLabel}
setRefDate(new Date(refDate.getFullYear(), refDate.getMonth() + 1, 1))}
style={{ padding: '6px 10px', border: '1px solid var(--border)' }}>›
{dayNames.map((d) =>
{d}
)}
{cells.map((c, i) => c === null ?
: (
setPicked(c)}
>{c.getDate()}
))}
{F.notes}
{picked && time ? `${picked.toLocaleDateString('en', { weekday: 'long', month: 'short', day: 'numeric' })} · ${time} · ${mode}` : 'Select date and time to continue.'}
setDone(true)}>{F.submit}
>
)}
);
}
// Lightbox ──────────────────────────────────────────────────────────────────
function Lightbox({ items, idx, onClose, onPrev, onNext }) {
useEffect_m(() => {
if (idx === null) return;
const onKey = (e) => {
if (e.key === 'Escape') onClose();
if (e.key === 'ArrowLeft') onPrev();
if (e.key === 'ArrowRight') onNext();
};
document.addEventListener('keydown', onKey);
return () => document.removeEventListener('keydown', onKey);
}, [idx, onClose, onPrev, onNext]);
if (idx === null) return null;
const item = items[idx];
return (
e.stopPropagation()} />
{ e.stopPropagation(); onPrev(); }} aria-label="Previous">‹
{ e.stopPropagation(); onNext(); }} aria-label="Next">›
{idx + 1} / {items.length} · {item.label}
);
}
Object.assign(window, { Modal, ModalHead, RegisterModal, BrochureModal, ViewingModal, Lightbox, SelectField, SuccessScreen });