// appointment.jsx — multi-step private appointment booking + lookbook lightbox const { useState, useEffect, useMemo, useRef } = React; // ─── APPOINTMENT MODAL ─────────────────────────────────────────────────────── const STEPS = ['Atelier', 'Occasion', 'Date', 'Details', 'Confirm']; function AppointmentModal({ open, onClose }) { const [step, setStep] = useState(0); const [data, setData] = useState({ atelier: '', occasion: '', date: null, // { y, m, d } name: '', email: '', phone: '', message: '', }); const [submitted, setSubmitted] = useState(false); const scrollRef = useRef(null); useEffect(() => { if (open) { document.body.style.overflow = 'hidden'; } else { document.body.style.overflow = ''; // reset on close after small delay (so closing transitions feel right) const t = setTimeout(() => { setStep(0); setSubmitted(false); }, 400); return () => clearTimeout(t); } }, [open]); useEffect(() => { // ESC closes if (!open) return; const onKey = (e) => { if (e.key === 'Escape') onClose(); }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, [open, onClose]); useEffect(() => { if (scrollRef.current) scrollRef.current.scrollTop = 0; }, [step]); if (!open) return null; const canNext = (() => { if (step === 0) return !!data.atelier; if (step === 1) return !!data.occasion; if (step === 2) return !!data.date; if (step === 3) return data.name.trim() && /.+@.+\..+/.test(data.email); return true; })(); const next = () => setStep((s) => Math.min(s + 1, STEPS.length - 1)); const back = () => setStep((s) => Math.max(s - 1, 0)); const formatDate = (d) => { if (!d) return ''; const date = new Date(d.y, d.m, d.d); return date.toLocaleDateString('en-GB', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }); }; const submit = () => { setSubmitted(true); next(); // open mailto in new tab as a backup courtesy — non-blocking try { const body = [ `Name: ${data.name}`, `Email: ${data.email}`, `Phone: ${data.phone}`, ``, `Atelier: ${data.atelier}`, `Occasion: ${data.occasion}`, `Preferred date: ${formatDate(data.date)}`, ``, `Message:`, data.message || '—', ].join('\n'); const subject = `Private appointment — ${data.atelier} · ${data.occasion}`; const href = `mailto:lamiaabinader@hotmail.com?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`; // store on confirm button for user to copy/open window.__lan_mailto = href; } catch (e) {} }; return (
{/* progress */}
); } const overlayStyle = { position: 'fixed', inset: 0, zIndex: 100, display: 'grid', placeItems: 'center', padding: 'clamp(12px, 4vw, 40px)', }; const overlayBackdrop = { position: 'absolute', inset: 0, background: 'color-mix(in oklab, var(--ink) 75%, transparent)', backdropFilter: 'blur(8px)', animation: 'fadeIn 300ms ease', }; const modalShell = { position: 'relative', width: 'min(1100px, 100%)', display: 'grid', animation: 'modalIn 500ms cubic-bezier(.2,.7,.2,1)', }; const closeBtnStyle = { position: 'absolute', top: -36, right: 0, display: 'inline-flex', alignItems: 'center', color: 'rgba(255,255,255,0.9)', fontFamily: 'var(--sans)', fontSize: 10.5, letterSpacing: '0.22em', textTransform: 'uppercase', cursor: 'default', }; // inject modal keyframes once if (!document.getElementById('__appt_kfs')) { const s = document.createElement('style'); s.id = '__appt_kfs'; s.textContent = ` @keyframes fadeIn { from{opacity:0} to{opacity:1} } @keyframes modalIn { from{opacity:0; transform: translateY(18px)} to{opacity:1; transform:none} } .appt-card { animation: fadeIn 400ms ease 80ms both; } `; document.head.appendChild(s); } // ─── STEP 1 — ATELIER ──────────────────────────────────────────────────────── const ATELIERS = [ { id: 'Dubai', t: 'Dubai', s: 'd3 — Dubai Design District. Bridal & evening fittings.', i: 'i' }, { id: 'Beirut', t: 'Beirut', s: 'Jisr El Basha, Mkalles. The original maison.', i: 'ii' }, ]; function StepAtelier({ data, setData }) { return ( <>

Where would you like to be seen?

Choose the atelier nearest you. Travel between Beirut and Dubai is possible on request.

{ATELIERS.map((a) => ( ))}
); } // ─── STEP 2 — OCCASION ─────────────────────────────────────────────────────── const OCCASIONS = [ { id: 'Bridal', t: 'Bridal', s: 'Wedding gown, civil dress, post-ceremony look.' }, { id: 'Evening', t: 'Evening', s: 'Gala, opera, première, state ceremony.' }, { id: 'Other', t: 'Other', s: 'Mother of the bride, debutante, private commission.' }, ]; function StepOccasion({ data, setData }) { return ( <>

What is the occasion?

The atelier shapes the appointment around the commission. You can change this at any time.

{OCCASIONS.map((o, idx) => ( ))}
); } // ─── STEP 3 — DATE ─────────────────────────────────────────────────────────── function StepDate({ data, setData }) { const today = new Date(); today.setHours(0,0,0,0); const [view, setView] = useState(() => ({ y: today.getFullYear(), m: today.getMonth() })); const monthName = useMemo(() => { return new Date(view.y, view.m, 1).toLocaleDateString('en-GB', { month: 'long', year: 'numeric' }); }, [view]); const cells = useMemo(() => { const first = new Date(view.y, view.m, 1); const lastDay = new Date(view.y, view.m + 1, 0).getDate(); // start grid on Mon const dow = (first.getDay() + 6) % 7; const arr = []; for (let i = 0; i < dow; i++) arr.push(null); for (let d = 1; d <= lastDay; d++) arr.push(d); return arr; }, [view]); const prev = () => setView((v) => { const nm = v.m - 1; return nm < 0 ? { y: v.y - 1, m: 11 } : { y: v.y, m: nm }; }); const next = () => setView((v) => { const nm = v.m + 1; return nm > 11 ? { y: v.y + 1, m: 0 } : { y: v.y, m: nm }; }); const isPast = (d) => { const dt = new Date(view.y, view.m, d); return dt < today; }; const sel = data.date; const isSelected = (d) => sel && sel.y === view.y && sel.m === view.m && sel.d === d; return ( <>

A preferred date.

The atelier will confirm a precise time after your request is received.

{monthName}
{['Mo','Tu','We','Th','Fr','Sa','Su'].map((d) =>
{d}
)} {cells.map((d, i) => { if (!d) return
; const past = isPast(d); const sel = isSelected(d); return ( ); })}
{data.date && (

Requested · {new Date(data.date.y, data.date.m, data.date.d).toLocaleDateString('en-GB', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' })}

)} ); } // ─── STEP 4 — DETAILS ──────────────────────────────────────────────────────── function StepDetails({ data, setData }) { const upd = (k) => (e) => setData((d) => ({ ...d, [k]: e.target.value })); return ( <>

Your details.

Shared in confidence with the atelier. We respond personally to every enquiry.