"use client"; import { useState, useEffect } from "react"; import { useRouter, useSearchParams } from "next/navigation"; import { Apartment } from "@/src/config/apartments"; import { addDays } from "@/src/lib/date"; import { Calendar, Users } from "lucide-react"; interface BookingFormProps { apartment: Apartment; locale: string; /** Initial value (probed server-side for "tomorrow") — kept up to date * client-side via /api/min-stay whenever the guest changes the check-in * date, since the effective minimum stay can vary by season/rate. */ minStayNights: number; } export function BookingForm({ apartment, locale, minStayNights: initialMinStayNights }: BookingFormProps) { const router = useRouter(); const searchParams = useSearchParams(); const [start, setStart] = useState(searchParams.get("checkin") || ""); const [end, setEnd] = useState(searchParams.get("checkout") || ""); const [guestName, setGuestName] = useState(""); const [guestEmail, setGuestEmail] = useState(""); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); const [attemptedSubmit, setAttemptedSubmit] = useState(false); const [minStayNights, setMinStayNights] = useState(initialMinStayNights); const [minStayLoading, setMinStayLoading] = useState(false); const [price, setPrice] = useState<{ nights: number; basePricePerNight: number; totalAfterDiscount: number; depositAmount: number; discountPercent: number; minStayValid: boolean; available: boolean; } | null>(null); const t = (key: string) => { const translations: Record> = { de: { checkIn: "Anreise", checkOut: "Abreise", guests: "Gäste", name: "Name", email: "E-Mail", calculate: "Preis berechnen", bookNow: "Jetzt anzahlungspflichtig buchen", minStay: "Mindestaufenthalt", nights: "Nächte", discount: "Ersparnis ggü. Kurzzeitpreis (bereits enthalten)", total: "Gesamt", deposit: "Anzahlung", unavailable: "Nicht verfügbar", loading: "Wird geladen...", needsCalculation: "Bitte zuerst den Preis berechnen.", needsNameAndEmail: "Bitte Name und E-Mail-Adresse eingeben, um fortzufahren.", needsName: "Bitte Namen eingeben, um fortzufahren.", needsEmail: "Bitte E-Mail-Adresse eingeben, um fortzufahren.", }, en: { checkIn: "Check-in", checkOut: "Check-out", guests: "Guests", name: "Name", email: "Email", calculate: "Calculate price", bookNow: "Book with deposit", minStay: "Minimum stay", nights: "nights", discount: "Savings vs. short-term rate (already included)", total: "Total", deposit: "Deposit", unavailable: "Not available", loading: "Loading...", needsCalculation: "Please calculate the price first.", needsNameAndEmail: "Please enter your name and email address to continue.", needsName: "Please enter your name to continue.", needsEmail: "Please enter your email address to continue.", }, }; return translations[locale]?.[key] || key; }; // Re-fetch the real, effective minimum stay whenever the check-in date // changes — Beds24's active rate (which can override the room's base // minimum) may require a different length of stay depending on the season. useEffect(() => { if (!start) return; let cancelled = false; setMinStayLoading(true); fetch(`/api/min-stay?apartmentSlug=${apartment.slug}&arrival=${start}`) .then((res) => res.json()) .then((data) => { if (!cancelled && typeof data.minStayNights === "number") { setMinStayNights(data.minStayNights); setEnd((currentEnd) => currentEnd && currentEnd < addDays(start, data.minStayNights) ? "" : currentEnd ); } }) .catch(() => { /* keep previous value on error */ }) .finally(() => { if (!cancelled) setMinStayLoading(false); }); return () => { cancelled = true; }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [start, apartment.slug]); async function calculatePrice() { if (!start || !end) return; setLoading(true); setError(""); try { const res = await fetch( `/api/availability?apartmentSlug=${apartment.slug}&start=${start}&end=${end}` ); const data = await res.json(); if (!res.ok) { throw new Error(data.error || "Failed to calculate price"); } setPrice(data.price); } catch (err) { setError(err instanceof Error ? err.message : "Unknown error"); setPrice(null); } finally { setLoading(false); } } async function handleBooking() { if (!price || !guestName || !guestEmail) { setAttemptedSubmit(true); return; } setAttemptedSubmit(false); setLoading(true); setError(""); try { const res = await fetch("/api/checkout-session", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ apartmentSlug: apartment.slug, start, end, guestName, guestEmail, }), }); const data = await res.json(); if (!res.ok) { throw new Error(data.error || "Failed to create checkout session"); } if (data.url) { router.push(data.url); } else { throw new Error("No checkout URL returned"); } } catch (err) { setError(err instanceof Error ? err.message : "Unknown error"); } finally { setLoading(false); } } return (

{locale === "de" ? "Buchungsanfrage" : "Booking request"}

{t("minStay")}:{" "} {minStayLoading ? locale === "de" ? "wird geprüft..." : "checking..." : `${minStayNights} ${t("nights")}`}

{ setStart(e.target.value); // The checkout date's validity (and the min-stay value shown // below) is re-checked by the useEffect above once we know // the real minimum stay for this new check-in date. setPrice(null); }} className="w-full pl-9 pr-3 py-2 rounded-lg border border-brand-grey-soft focus:border-brand-green focus:ring-1 focus:ring-brand-green outline-none" />

{start ? new Intl.DateTimeFormat(locale === "de" ? "de-DE" : "en-US", { dateStyle: "medium", }).format(new Date(`${start}T00:00:00`)) : locale === "de" ? "Format: TT.MM.JJJJ" : "Format: MM/DD/YYYY"}

{ setEnd(e.target.value); setPrice(null); }} className="w-full pl-9 pr-3 py-2 rounded-lg border border-brand-grey-soft focus:border-brand-green focus:ring-1 focus:ring-brand-green outline-none" />

{end ? new Intl.DateTimeFormat(locale === "de" ? "de-DE" : "en-US", { dateStyle: "medium", }).format(new Date(`${end}T00:00:00`)) : locale === "de" ? "Format: TT.MM.JJJJ" : "Format: MM/DD/YYYY"}

{ setGuestName(e.target.value); if (e.target.value) setAttemptedSubmit(false); }} className={`w-full px-3 py-2 rounded-lg border focus:ring-1 outline-none ${ attemptedSubmit && !guestName ? "border-red-400 focus:border-red-500 focus:ring-red-400" : "border-brand-grey-soft focus:border-brand-green focus:ring-brand-green" }`} />
{ setGuestEmail(e.target.value); if (e.target.value) setAttemptedSubmit(false); }} className={`w-full px-3 py-2 rounded-lg border focus:ring-1 outline-none ${ attemptedSubmit && !guestEmail ? "border-red-400 focus:border-red-500 focus:ring-red-400" : "border-brand-grey-soft focus:border-brand-green focus:ring-brand-green" }`} />
{price && (
{price.nights} {t("nights")} {new Intl.NumberFormat("de-DE", { style: "currency", currency: "EUR", }).format(price.totalAfterDiscount)}
{price.discountPercent > 0 && (
{t("discount")} {price.discountPercent}%
)}
{t("deposit")} {new Intl.NumberFormat("de-DE", { style: "currency", currency: "EUR", }).format(price.depositAmount)}
)} {error && (
{error}
)} {!loading && (!price || !guestName || !guestEmail) && (

{!price ? t("needsCalculation") : !guestName && !guestEmail ? t("needsNameAndEmail") : !guestName ? t("needsName") : t("needsEmail")}

)}
); }