// Maison Yeya — Appointments multi-step modal
function AppointmentModal({ open, onClose, savedItems, onRemoveSaved }) {
const [step, setStep] = useState(0);
const [data, setData] = useState({
occasion: "",
fullName: "",
email: "",
phone: "",
eventDate: "",
preferredDate: "",
preferredTime: "Morning",
notes: "",
ack: false
});
const [done, setDone] = useState(false);
useEffect(() => {
if (!open) return;
document.body.style.overflow = "hidden";
const onKey = (e) => { if (e.key === "Escape") onClose(); };
window.addEventListener("keydown", onKey);
return () => {
document.body.style.overflow = "";
window.removeEventListener("keydown", onKey);
};
}, [open, onClose]);
// Reset state when re-opened after completion
useEffect(() => {
if (open && done) {
setTimeout(() => { setDone(false); setStep(0); }, 200);
}
}, [open]);
if (!open) return null;
const set = (k, v) => setData(d => ({ ...d, [k]: v }));
const steps = [
{ id: "occasion", label: "Occasion", valid: () => !!data.occasion },
{ id: "details", label: "Details", valid: () => data.fullName.trim() && data.email.trim() && data.phone.trim() },
{ id: "schedule", label: "Schedule", valid: () => !!data.preferredDate },
{ id: "review", label: "Review", valid: () => data.ack }
];
const next = () => {
if (!steps[step].valid()) return;
if (step === steps.length - 1) {
setDone(true);
return;
}
setStep(s => s + 1);
};
const back = () => setStep(s => Math.max(0, s - 1));
return (
{/* Left: image rail */}
Step {done ? steps.length : step + 1} of {steps.length}
{done ? "Thank you." : "Request a Private Appointment."}
{done
? "Your enquiry has been received. A concierge will respond within one working day."
: "We respond within one working day. All appointments take place at the atelier in Dubai Design District."}
{steps.map((s, i) => (
{(i < step || done) ? "✓" : i + 1}
{s.label}
))}
{/* Right: form */}
Close ✕
{done ? (
) : (
<>
{step === 0 && }
{step === 1 && }
{step === 2 && }
{step === 3 && }
{steps[step].valid() ? "Looks good." : "Complete the fields above to continue."}
{step > 0 && ← Back }
{step === steps.length - 1 ? "Send Enquiry" : "Continue →"}
>
)}
);
}
// ─── Step 1: Occasion ───
function StepOccasion({ data, set, savedItems }) {
const opts = [
{ id: "bridal", label: "Bridal Couture", note: "Wedding gown, second look, full bridal couture privée." },
{ id: "couture", label: "Couture · Evening", note: "Eveningwear, red-carpet, premiere or gala." },
{ id: "made-to-meas", label: "Made-to-Measure", note: "A bespoke commission outside scheduled collections." },
{ id: "press", label: "Press / Styling", note: "Editorial, red-carpet, or stylist enquiry." }
];
return (
01 · Occasion
What brings you to the Maison?
{opts.map(o => (
set("occasion", o.id)}
style={{
textAlign: "left", padding: 22, cursor: "pointer",
border: `1px solid ${data.occasion === o.id ? "var(--ink)" : "var(--hair)"}`,
background: data.occasion === o.id ? "var(--paper-2)" : "transparent",
transition: "all 200ms ease"
}}>
{o.label}
{o.note}
))}
{savedItems.length > 0 && (
{savedItems.length} saved look{savedItems.length > 1 ? "s" : ""} will accompany your enquiry.
)}
);
}
// ─── Step 2: Details ───
function StepDetails({ data, set }) {
return (
02 · Your Details
Tell us who is enquiring.
);
}
// ─── Step 3: Schedule ───
function StepSchedule({ data, set }) {
// Generate next 21 days, skip Fridays (Dubai closure)
const days = useMemo(() => {
const out = [];
const today = new Date();
today.setHours(0, 0, 0, 0);
for (let i = 1; i < 30 && out.length < 14; i++) {
const d = new Date(today);
d.setDate(today.getDate() + i);
if (d.getDay() === 5) continue; // Friday closed
out.push(d);
}
return out;
}, []);
const timeSlots = ["Morning · 10:00 – 12:00", "Midday · 12:00 – 14:00", "Afternoon · 14:00 – 16:00", "Evening · 16:00 – 18:00"];
return (
03 · Schedule
Choose a preferred date.
Showroom hours: 10:00 – 18:00. Closed Fridays and UAE public holidays.
{days.map(d => {
const iso = d.toISOString().slice(0, 10);
const sel = data.preferredDate === iso;
return (
set("preferredDate", iso)}
style={{
padding: "16px 10px", textAlign: "center", cursor: "pointer",
border: `1px solid ${sel ? "var(--ink)" : "var(--hair)"}`,
background: sel ? "var(--ink)" : "transparent",
color: sel ? "var(--paper)" : "var(--ink)",
transition: "all 200ms ease"
}}>
{d.toLocaleDateString("en-US", { weekday: "short" })}
{d.getDate()}
{d.toLocaleDateString("en-US", { month: "short" })}
);
})}
Preferred Time
{timeSlots.map(t => (
set("preferredTime", t.split(" ")[0])}
className="editorial"
style={{
padding: "14px 18px", textAlign: "left", cursor: "pointer", fontSize: 15,
border: `1px solid ${data.preferredTime === t.split(" ")[0] ? "var(--ink)" : "var(--hair)"}`,
background: data.preferredTime === t.split(" ")[0] ? "var(--paper-2)" : "transparent"
}}>
{t}
))}
);
}
// ─── Step 4: Review ───
function StepReview({ data, set, savedItems, onRemoveSaved }) {
const occasionLabels = {
bridal: "Bridal Couture",
couture: "Couture · Evening",
"made-to-meas": "Made-to-Measure",
press: "Press / Styling"
};
const dateLabel = data.preferredDate
? new Date(data.preferredDate).toLocaleDateString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric" })
: "—";
return (
04 · Review
Confirm and send.
{data.eventDate && }
Notes for the atelier (optional)
{savedItems.length > 0 && (
Saved Looks · {savedItems.length}
{savedItems.map(it => (
{it.name}
onRemoveSaved(it)}
style={{ marginLeft: 6, color: "var(--ink-mute)", cursor: "pointer", border: 0, background: "none" }}>
✕
))}
)}
set("ack", e.target.checked)}
style={{ marginTop: 4 }}/>
I understand Maison Yeya works by quotation and consultation, and that all appointments take place at the atelier in Dubai Design District. I consent to being contacted by the Maison.
);
}
function ReviewRow({ label, value }) {
return (
);
}
// ─── Confirmation ───
function ThankYou({ data, savedItems, onClose }) {
return (
✦
Your enquiry is with the atelier.
A concierge will respond to {data.email || "your email"} within one working day to confirm your appointment and answer any questions ahead of your visit.
For urgent enquiries, the atelier is reachable on {CONTACT.mobile}.
Close
);
}
window.AppointmentModal = AppointmentModal;