// pages/reservation.jsx — 4-step reservation flow with working state function ReservationPage() { const { t, lang, navigate, currency, route } = useApp(); const [step, setStep] = useState(0); // Default check-in: tomorrow; check-out: 3 days later const today = new Date(); const defaultIn = new Date(today.getTime() + 86400000); const defaultOut = new Date(today.getTime() + 86400000 * 4); const fmt = (d) => d.toISOString().slice(0, 10); const [booking, setBooking] = useState({ checkIn: fmt(defaultIn), checkOut: fmt(defaultOut), adults: 2, children: 0, promo: "", roomIdx: route.params && route.params.roomIdx != null ? route.params.roomIdx : null, firstName: "", lastName: "", email: "", phone: "", country: lang === "pl" ? "Polska" : "Poland", notes: "", terms: false, // Przelewy24 is temporarily disabled (no production credentials yet), so // Revolut transfer is the only enabled method. See the payment section below. paymentMethod: "revolut_transfer", bookingRef: null, payment: null, paymentStatus: null, }); const update = (patch) => setBooking((b) => ({ ...b, ...patch })); const [submitError, setSubmitError] = useState(null); const [submitting, setSubmitting] = useState(false); // If user navigated in with a roomIdx, jump to step 2 useEffect(() => { if (booking.roomIdx != null) setStep(1); }, []); // eslint-disable-line // When user returns from Przelewy24 with ?ref=, jump to confirmation and poll status useEffect(() => { // Ref arrives in the query string (/reservation?ref=...); also accept the // legacy hash form for any in-flight links from before path routing. const m = (window.location.search + window.location.hash).match(/[?&]ref=([A-Z0-9-]+)/i); if (!m) return; const ref = m[1]; update({ bookingRef: ref }); setStep(3); let cancelled = false; const poll = async () => { for (let i = 0; i < 12 && !cancelled; i++) { try { const r = await fetch(`${window.API_BASE}/api/reservations/${ref}`); if (r.ok) { const data = await r.json(); update({ paymentStatus: data.payment_status }); if (data.status === "confirmed") return; } } catch {} await new Promise((res) => setTimeout(res, 2500)); } }; poll(); return () => { cancelled = true; }; }, []); // eslint-disable-line const nights = useMemo(() => { const a = new Date(booking.checkIn); const b = new Date(booking.checkOut); return Math.max(1, Math.round((b - a) / 86400000)); }, [booking.checkIn, booking.checkOut]); const roomCard = booking.roomIdx != null ? t.rooms.cards[booking.roomIdx] : null; const subtotal = roomCard ? roomCard.priceFrom * nights : 0; const taxes = Math.round(subtotal * 0.08); const total = subtotal + taxes; const submitReservation = async () => { setSubmitError(null); setSubmitting(true); try { const body = { room_id: booking.roomIdx + 1, check_in: booking.checkIn, check_out: booking.checkOut, adults: booking.adults, children: booking.children, first_name: booking.firstName, last_name: booking.lastName, email: booking.email, phone: booking.phone, country: booking.country, notes: booking.notes || null, promo: booking.promo || null, payment_method: booking.paymentMethod, terms: booking.terms, language: lang, }; const r = await fetch(`${window.API_BASE}/api/reservations`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); if (!r.ok) { const txt = await r.text(); if (r.status === 409) { throw new Error(lang === "pl" ? "Wybrany pokoj nie jest dostepny w tych dniach." : "The selected room is not available for these dates."); } throw new Error(txt || "request failed"); } const data = await r.json(); update({ bookingRef: data.booking_ref, payment: data.payment }); if (data.payment.method === "przelewy24") { window.location.href = data.payment.redirect_url; return; } setStep(3); window.scrollTo({ top: 0, behavior: "smooth" }); } catch (err) { setSubmitError(lang === "pl" ? "Nie udalo sie utworzyc rezerwacji. " + (err.message || "") : "Could not create the reservation. " + (err.message || "")); } finally { setSubmitting(false); } }; const goNext = () => { track("reservation_step", { step: step + 1, room_idx: booking.roomIdx }); if (step === 2) { submitReservation(); return; } setStep((s) => Math.min(3, s + 1)); window.scrollTo({ top: 0, behavior: "smooth" }); }; const goBack = () => { setStep((s) => Math.max(0, s - 1)); window.scrollTo({ top: 0, behavior: "smooth" }); }; const canContinue = step === 0 ? booking.checkIn && booking.checkOut && nights > 0 : step === 1 ? booking.roomIdx != null : step === 2 ? booking.firstName && booking.lastName && booking.email && booking.terms : true; return (
{t.reservation.title}

{step < 3 ? t.reservation["step" + (step + 1)].title : t.reservation.step4.title}

i < step && setStep(i)} />
{step < 3 ? (
{step === 0 && } {step === 1 && } {step === 2 && } {submitError && (
{submitError}
)}
{step > 0 ? ( ) : }
) : ( )}
); } // ─── Stepper ─────────────────────────────────────────────────────────────── function Stepper({ steps, active, onJump }) { return (
{steps.map((s, i) => { const done = i < active; const isActive = i === active; return ( ); })}
); } // ─── Step 1: Dates ──────────────────────────────────────────────────────── function StepDates({ booking, update, nights }) { const { t, lang } = useApp(); return (

{t.reservation.step1.sub}

update({ checkIn: v })} /> update({ checkOut: v })} />
{lang === "pl" ? "Długość pobytu" : "Length of stay"} {nights} {nightsLabel(nights, lang)}
update({ adults: v })} /> update({ children: v })} />
update({ promo: e.target.value })} placeholder="DLUGA10" />
); } function DateField({ label, value, onChange }) { return (
onChange(e.target.value)} style={{ paddingRight: 44 }} />
); } function Stepper2({ label, value, min, max, onChange }) { return (
{value}
); } function nightsLabel(n, lang) { if (lang === "en") return n === 1 ? "night" : "nights"; // Polish plural: 1 noc, 2-4 noce, else nocy if (n === 1) return "noc"; const mod10 = n % 10, mod100 = n % 100; if (mod10 >= 2 && mod10 <= 4 && (mod100 < 10 || mod100 >= 20)) return "noce"; return "nocy"; } // ─── Step 2: Choose room ────────────────────────────────────────────────── function StepRoom({ booking, update }) { const { t, currency } = useApp(); return (

{t.reservation.step2.sub}

{t.rooms.cards.map((c, i) => { const selected = booking.roomIdx === i; return ( ); })}
); } // ─── Step 3: Guest details ──────────────────────────────────────────────── function StepGuest({ booking, update }) { const { t, lang } = useApp(); return (

{t.reservation.step3.sub}

update({ firstName: e.target.value })} />
update({ lastName: e.target.value })} />
update({ email: e.target.value })} />
update({ phone: e.target.value })} placeholder="+48 ..." />
update({ country: e.target.value })} />