// Villa Mesochori — direct booking flow. Pricing comes from the owners' admin settings // (window.VMContent); the request is emailed to the owners' address. const bkNS = window.VillaMesochoriDesignSystem_9e1a21; const { Button: BkButton, Input: BkInput, Select: BkSelect, Checkbox: BkCheckbox, Badge: BkBadge, LucideIcon: BkIcon } = bkNS; function BkStep({ n, label, state }) { const done = state === "done", active = state === "active"; return (
{done ? "✓" : n} {label}
); } function BkPrice({ c, nights, guests, stay, seasonal }) { return (

Your stay

{nights} night{nights === 1 ? "" : "s"} · {guests} guest{guests === 1 ? "" : "s"}

{seasonal ? `${nights} nights (seasonal rates)` : `${c.currency}${c.nightly} × ${nights} nights`} {c.currency}{stay}
Cleaning{c.currency}{c.cleaning}
Platform fees{c.currency}0 — you booked direct
Total{c.currency}{stay + c.cleaning}

Pay a {c.depositPct}% deposit to hold the dates — we'll confirm by email within a day.

); } // PayPal deposit button — renders only when the owners have set a PayPal Client ID function PayPalDeposit({ c, amount, note }) { const ref = React.useRef(null); const [state, setState] = React.useState("loading"); // loading | ready | paid | error React.useEffect(() => { if (!c.paypalClientId) return; const render = () => { if (!window.paypal || !ref.current) return; ref.current.innerHTML = ""; window.paypal.Buttons({ style: { color: "gold", shape: "pill", label: "pay", height: 44 }, createOrder: (data, actions) => actions.order.create({ purchase_units: [{ description: note, amount: { value: amount.toFixed(2), currency_code: c.payCurrency } }], }), onApprove: (data, actions) => actions.order.capture().then(() => setState("paid")), onError: () => setState("error"), }).render(ref.current).then(() => setState("ready")).catch(() => setState("error")); }; const id = "vm-paypal-sdk"; if (document.getElementById(id)) { render(); return; } const s = document.createElement("script"); s.id = id; s.src = `https://www.paypal.com/sdk/js?client-id=${encodeURIComponent(c.paypalClientId)}¤cy=${encodeURIComponent(c.payCurrency)}&intent=capture`; s.onload = render; s.onerror = () => setState("error"); document.head.appendChild(s); }, []); if (!c.paypalClientId) return null; if (state === "paid") { return (
Deposit paid — thank you

Your dates are held. We'll email your receipt and confirmation shortly.

); } return (
Hold your dates now — pay the {c.depositPct}% deposit ({c.currency}{amount.toFixed(2)})
{state === "loading" && Loading secure payment…} {state === "error" && Payment didn't load — no problem, we'll send payment details by email instead.} Paying by card works too — no PayPal account needed.
); } // Square card deposit — renders when the owners have set Square App + Location IDs. // The card is tokenized in the browser; the charge happens on the server (api.php). function SquareDeposit({ c, amount, note }) { const boxRef = React.useRef(null); const cardRef = React.useRef(null); const [state, setState] = React.useState("loading"); // loading | ready | paying | paid | error | unavailable const [errMsg, setErrMsg] = React.useState(""); React.useEffect(() => { if (!c.squareAppId || !c.squareLocationId) return; if (!window.VMContent.serverMode()) { setState("unavailable"); return; } const init = () => { if (!window.Square || !boxRef.current) { setState("error"); return; } window.Square.payments(c.squareAppId, c.squareLocationId).card() .then((card) => card.attach(boxRef.current).then(() => { cardRef.current = card; setState("ready"); })) .catch(() => setState("error")); }; const id = "vm-square-sdk"; if (document.getElementById(id)) { init(); return; } const s = document.createElement("script"); s.id = id; s.src = "https://web.squarecdn.com/v1/square.js"; s.onload = init; s.onerror = () => setState("error"); document.head.appendChild(s); }, []); if (!c.squareAppId || !c.squareLocationId) return null; if (state === "unavailable") return null; // needs the live server if (state === "paid") { return (
Deposit paid by card — thank you

Your dates are held. We'll email your receipt and confirmation shortly.

); } const pay = () => { if (!cardRef.current) return; setState("paying"); setErrMsg(""); cardRef.current.tokenize().then((r) => { if (r.status !== "OK") { setState("ready"); setErrMsg("Check the card details and try again."); return; } fetch("api.php?action=square-pay", { method: "POST", body: JSON.stringify({ sourceId: r.token, amount: Math.round(amount * 100), currency: c.payCurrency, note: note }), }) .then((res) => res.json()) .then((d) => { if (d.ok) setState("paid"); else { setState("ready"); setErrMsg(d.error || "The payment was declined."); } }) .catch(() => { setState("ready"); setErrMsg("Connection problem — nothing was charged. Try again."); }); }); }; return (
Or pay the deposit by card
{state === "loading" && Loading secure card form…} {state === "error" && Card payments didn't load — we'll send payment details by email instead.} {errMsg && {errMsg}} {(state === "ready" || state === "paying") && (
{state === "paying" ? "Processing…" : `Pay ${c.currency}${amount.toFixed(2)} deposit`}
)}
); } function Booking({ onHome }) { const c = window.VMContent.get(); const [step, setStep] = React.useState(0); const [checkin, setCheckin] = React.useState("2026-08-28"); const [nights, setNights] = React.useState(5); const [guests, setGuests] = React.useState(2); const [name, setName] = React.useState(""); const [email, setEmail] = React.useState(""); const [notes, setNotes] = React.useState(""); const steps = ["Dates & guests", "Your details", "Review & send", "Sent"]; // Availability synced from the owners' Google Calendar const [busy, setBusy] = React.useState(null); // null = loading, {configured, set} React.useEffect(() => { window.VMContent.getAvailability().then((a) => setBusy({ configured: a.configured, set: new Set(a.busy) })); }, []); const dates = Array.from({ length: nights }, (_, i) => { const d = new Date(checkin + "T12:00:00"); d.setDate(d.getDate() + i); return d.toISOString().slice(0, 10); }); const perNight = dates.map((d) => window.VMContent.priceFor(d)); const stay = perNight.reduce((s, p) => s + p, 0); const seasonal = perNight.some((p) => p !== perNight[0]); const total = stay + c.cleaning; const conflictDates = busy && busy.configured ? dates.filter((d) => busy.set.has(d)) : []; const conflict = conflictDates.length > 0; const [sending, setSending] = React.useState(false); const sendRequest = () => { setSending(true); window.VMContent.addGuest({ name: name, email: email, checkin: checkin, nights: nights, guests: guests, notes: notes, total: `${c.currency}${total}`, }).then(() => { setSending(false); setStep(3); }) .catch(() => { setSending(false); setStep(3); }); }; return (

Book your stay

{steps.map((s, i) => )}
{step === 0 && (
setCheckin(e.target.value)} prefix={} hint={`From ${c.checkIn}`} /> setNights(Number(e.target.value))} options={["2", "3", "4", "5", "6", "7", "10", "14"].filter((n) => Number(n) >= c.minNights).map((n) => ({ value: n, label: `${n} nights` }))} hint={`${c.minNights}-night minimum · check-out by ${c.checkOut}`} />
setGuests(Number(e.target.value))} options={Array.from({ length: c.maxGuests }, (_, i) => ({ value: `${i + 1}`, label: `${i + 1} guest${i ? "s" : ""}` }))} hint={`The house sleeps ${c.maxGuests} — no extra beds or cribs, sorry`} />
{busy && busy.configured && !conflict && (

These dates are free on our calendar

)} {conflict && (
Sorry — the house is already booked on {conflictDates.slice(0, 3).join(", ")}{conflictDates.length > 3 ? ` and ${conflictDates.length - 3} more night${conflictDates.length - 3 === 1 ? "" : "s"}` : ""}. Try shifting your dates.
)}
setStep(1)} disabled={conflict}>Continue
)} {step === 1 && (
setName(e.target.value)} /> setEmail(e.target.value)} hint="Our reply and welcome notes go here" /> setNotes(e.target.value)} />
setStep(0)}>Back setStep(2)}>Review request
)} {step === 2 && (
{checkin} · {nights} nights · {guests} guest{guests === 1 ? "" : "s"} {name || "Your name"} · {email || "your email"} {notes ? "{notes}" : null}

Sending this delivers your request straight to us — nothing else to do. We'll be in touch within 24 hours to confirm your dates.

setStep(1)}>Back {sending ? "Sending…" : `Send request — then pay the ${c.depositPct}% deposit`}
)} {step === 3 && (
Request received

Thank you — we'll be in touch within 24 hours

Your request is with us. We'll confirm your dates by email and send directions with our arrival notes. Check-in is from {c.checkIn}.

Keep an eye out for a little gift on your arrival. — Maria & the family

Back to the house setStep(0)}>Start another request
)}
{step < 3 && }
); } window.VMBooking = Booking;