// Facette — Modals (Book, Skin Quiz, Lightbox)
const { BRANCHES: B_DATA, THERAPISTS, QUIZ_STEPS, recommend, SIGNATURES, TREATMENT_CATEGORIES } = window.FacetteData;
const ALL_TREATMENTS = TREATMENT_CATEGORIES.flatMap((c) => c.items);
// ──────────────────────────────────────────────────────────
// Generic modal scrim
// ──────────────────────────────────────────────────────────
function Modal({ onClose, children, wide }) {
React.useEffect(() => {
const onKey = (e) => { if (e.key === 'Escape') onClose(); };
window.addEventListener('keydown', onKey);
return () => window.removeEventListener('keydown', onKey);
}, [onClose]);
return (
e.stopPropagation()}>
{children}
);
}
// ──────────────────────────────────────────────────────────
// Lightbox
// ──────────────────────────────────────────────────────────
function Lightbox({ src, onClose }) {
React.useEffect(() => {
const onKey = (e) => { if (e.key === 'Escape') onClose(); };
window.addEventListener('keydown', onKey);
return () => window.removeEventListener('keydown', onKey);
}, [onClose]);
return (
);
}
// ──────────────────────────────────────────────────────────
// Booking modal
// ──────────────────────────────────────────────────────────
function BookModal({ branch, treatment, onClose, onBranchChange }) {
const b = B_DATA[branch];
const [step, setStep] = React.useState(treatment ? 1 : 0);
// Resolve treatment to full record if needed
const resolvedTreatment = React.useMemo(() => {
if (!treatment) return null;
const found = ALL_TREATMENTS.find((t) => t.name === treatment.name || t.slug === treatment.slug);
return found || treatment;
}, [treatment]);
const [pickedTreatment, setPickedTreatment] = React.useState(resolvedTreatment);
const [therapist, setTherapist] = React.useState(null);
const [date, setDate] = React.useState(null);
const [time, setTime] = React.useState(null);
const eligibleTherapists = THERAPISTS.filter((t) => t.branches.includes(branch) && t.role !== 'GP & Aesthetics');
// Build a fake date set (next 7 days)
const dates = React.useMemo(() => {
const arr = [];
const today = new Date('2026-05-22');
for (let i = 0; i < 7; i++) {
const d = new Date(today);
d.setDate(today.getDate() + i);
arr.push({
iso: d.toISOString().slice(0, 10),
label: d.toLocaleDateString('en-GB', { weekday: 'short' }),
day: d.getDate(),
month: d.toLocaleDateString('en-GB', { month: 'short' }),
});
}
return arr;
}, []);
const times = ['10:00', '11:30', '13:00', '14:30', '16:00', '17:30', '19:00'];
// Make a couple slots "booked" based on date hash
const unavailable = (d, t) => {
const seed = (d.charCodeAt(d.length - 1) + t.charCodeAt(0)) % 7;
return seed === 0 || seed === 3;
};
return (
{step === 0 && (
{ setPickedTreatment(t); setStep(1); }}
branch={branch}
onBranchChange={onBranchChange}
/>
)}
{step === 1 && (
setStep(2)}
onBack={() => setStep(0)}
/>
)}
{step === 2 && (
setStep(1)}
onNext={() => setStep(3)}
/>
)}
{step === 3 && (
d.iso === date)}
time={time}
onBack={() => setStep(2)}
onConfirm={() => setStep(4)}
/>
)}
{step === 4 && (
d.iso === date)}
time={time}
therapist={therapist}
onClose={onClose}
/>
)}
);
}
function BookSide({ branch, b, treatment, step }) {
return (
Booking · {b.short}
Book your
facial.
Four steps. About 90 seconds. Cancellation up to 24 hours before your slot.
{treatment && (
You picked
{treatment.name}
{treatment.duration}
{!treatment.tbc && · {(treatment.price ?? treatment.priceFrom).toLocaleString()} AED}
)}
);
}
function ProgressDots({ step, total }) {
const labels = ['Treatment', 'Therapist', 'Date & Time', 'Confirm'];
return (
{labels.map((l, i) => (
{i < step ? '✓' : i + 1}
{l}
))}
);
}
function PickTreatment({ onPick, branch, onBranchChange }) {
const [cat, setCat] = React.useState(TREATMENT_CATEGORIES[0].id);
const current = TREATMENT_CATEGORIES.find((c) => c.id === cat);
return (
Pick a facial
Filter by category. You can change this later.
{TREATMENT_CATEGORIES.map((c) => (
))}
{current.items.map((it, i) => (
))}
);
}
function PickTherapist({ therapists, selected, onSelect, onNext, onBack }) {
return (
Pick a therapist
Or leave it to us — we will match by concern.
{therapists.map((t) => (
))}
);
}
function PickTime({ dates, times, date, setDate, time, setTime, unavailable, onBack, onNext }) {
return (
Pick a date
Live availability — confirmed when you submit.
{dates.map((d) => (
))}
{date && (
Pick a time
7 slots shown. Tap a faded slot to be waitlisted.
{times.map((t) => {
const off = unavailable(date, t);
return (
);
})}
)}
);
}
function Confirm({ branch, treatment, therapist, date, time, onBack, onConfirm }) {
const b = B_DATA[branch];
const therapistName = therapist === '__any' ? 'Any available therapist' : therapist?.name;
const total = treatment?.price ?? treatment?.priceFrom ?? '—';
return (
Review your booking
Last look before we hold the slot.
Branch
Facette · {b.short}
Treatment
{treatment?.name}
When
{date?.label}, {date?.day} {date?.month} · {time}
Duration
{treatment?.duration}
Total
{typeof total === 'number' ? `${total.toLocaleString()} AED` : 'On request'}
);
}
function Success({ branch, treatment, date, time, therapist, onClose }) {
const b = B_DATA[branch];
return (
✓
You are booked.
We have sent a confirmation to your inbox. Show up five minutes early — Phia at reception has your name.
{treatment?.name}
{date?.label}, {date?.day} {date?.month} · {time}
{b.short}
{therapist === '__any' ? 'Therapist TBA' : therapist?.name}
);
}
// ──────────────────────────────────────────────────────────
// Skin Quiz modal
// ──────────────────────────────────────────────────────────
function QuizModal({ onClose, onBook }) {
const [step, setStep] = React.useState(0);
const [answers, setAnswers] = React.useState([]);
const total = QUIZ_STEPS.length;
const onSelect = (val) => {
const next = [...answers];
next[step] = val;
setAnswers(next);
};
const onNext = () => {
if (step < total) setStep(step + 1);
};
const onBack = () => {
if (step > 0) setStep(step - 1);
};
const done = step === total;
const rec = done ? recommend(answers) : null;
const recTreatment = rec ? ALL_TREATMENTS.find((t) => t.slug === rec.slug) : null;
return (
{Array.from({ length: total }).map((_, i) => (
))}
{!done && (
Step {step + 1} of {total}
{QUIZ_STEPS[step].q}
{QUIZ_STEPS[step].sub}
{QUIZ_STEPS[step].options.map((o) => (
))}
)}
{done && recTreatment && (
Based on your answers, we recommend
{recTreatment.name}
)}
);
}
window.BookModal = BookModal;
window.QuizModal = QuizModal;
window.Lightbox = Lightbox;