'use client' import { useState, useEffect, Suspense } from 'react' import { useParams, useSearchParams } from 'next/navigation' import Image from 'next/image' import Link from 'next/link' import { ArrowLeft, CalendarCheck, CircleCheck, Loader2 } from 'lucide-react' const CURRENCY_SYMBOLS: Record = { zar: 'R', usd: '$', eur: '€', gbp: '£', kes: 'KSh', ngn: '₦', ghs: '₵' } const formatPrice = (amount: number, code?: string) => { const sym = CURRENCY_SYMBOLS[(code ?? '').toLowerCase()] ?? (code?.toUpperCase() ?? '') return `${sym}${(amount / 100).toFixed(0)}` } interface Room { id: string title: string description: string thumbnail: string images: { url: string }[] variants: { id: string; prices: { amount: number; currency_code: string }[] }[] } function calcNights(checkIn: string, checkOut: string) { if (!checkIn || !checkOut) return 0 return Math.max(0, Math.round((new Date(checkOut).getTime() - new Date(checkIn).getTime()) / 86400000)) } function BookingPageContent() { const params = useParams() const searchParams = useSearchParams() const productId = params.productId as string const today = new Date().toISOString().split('T')[0] const [room, setRoom] = useState(null) const [loadingRoom, setLoadingRoom] = useState(true) const [activeImg, setActiveImg] = useState(0) const [checkIn, setCheckIn] = useState(searchParams.get('checkIn') || '') const [checkOut, setCheckOut] = useState(searchParams.get('checkOut') || '') const [availability, setAvailability] = useState<{ available: boolean } | null>(null) const [checkingAvail, setCheckingAvail] = useState(false) const [form, setForm] = useState({ name: '', email: '', phone: '', notes: '' }) const [submitting, setSubmitting] = useState(false) const [success, setSuccess] = useState(false) const [confirmNo, setConfirmNo] = useState('') const [error, setError] = useState('') useEffect(() => { if (!productId) return fetch(`/api/medusa/store/products/${productId}?fields=*variants,*images`) .then(r => r.json()) .then(d => { setRoom(d.product ?? null); setLoadingRoom(false) }) .catch(() => setLoadingRoom(false)) }, [productId]) useEffect(() => { if (!checkIn || !checkOut || !room) return if (new Date(checkOut) <= new Date(checkIn)) return setCheckingAvail(true) setAvailability(null) const qs = new URLSearchParams({ room_type: room.id, check_in: checkIn, check_out: checkOut }).toString() fetch(`/api/medusa/store/hotel/bookings?${qs}`) .then(r => r.json()) .then(d => { setAvailability(d.available != null ? { available: d.available } : null); setCheckingAvail(false) }) .catch(() => setCheckingAvail(false)) }, [checkIn, checkOut, room]) const nights = calcNights(checkIn, checkOut) const variant = room?.variants?.[0] const price = variant?.prices?.[0] const pricePerNight = price?.amount ?? 0 const currency = price?.currency_code const totalPrice = pricePerNight * nights const allImages = room ? [room.thumbnail, ...(room.images?.map(i => i.url) ?? [])].filter(Boolean) : [] const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() if (!room || !availability?.available || nights === 0) return setSubmitting(true) setError('') try { const res = await fetch('/api/medusa/store/hotel/bookings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ room_type: room.id, check_in: checkIn, check_out: checkOut, customer_name: form.name, customer_email: form.email || null, customer_phone: form.phone, notes: form.notes || null, }), signal: AbortSignal.timeout(15000), }) const data = await res.json() if (!res.ok) throw new Error(data.error || 'Booking failed') setConfirmNo(data.confirmation_number ?? '') setSuccess(true) } catch (err: any) { setError(err.message === 'Room is not available for the selected dates' ? 'Sorry, those dates are no longer available. Please choose different dates.' : 'Something went wrong. Please try again or call us directly.') } setSubmitting(false) } if (loadingRoom) return (
) if (!room) return (

Room not found.

Back to Rooms
) if (success) return (

Booking Confirmed!

{confirmNo &&

Ref: {confirmNo}

}

{room.title}

Check-in: {checkIn}

Check-out: {checkOut}

{nights > 0 && totalPrice > 0 && (

{formatPrice(totalPrice, currency)} total · {nights} night{nights !== 1 ? 's' : ''}

)}

{form.email ? `A confirmation will be sent to ${form.email}.` : `We will call ${form.phone} to confirm your reservation.`}

Browse More Rooms
) return (
Back to Rooms
{/* Left — Room details */}
{allImages.length > 0 && (
{room.title}
{allImages.length > 1 && (
{allImages.map((img, i) => ( ))}
)}
)}

{room.title}

{room.description &&

{room.description}

} {pricePerNight > 0 && (

{formatPrice(pricePerNight, currency)} / night

)}
{/* Right — Booking form */}

Reserve This Room

{/* Dates */}
{ setCheckIn(e.target.value); setAvailability(null) }} className="w-full border border-border rounded-lg px-3 py-2 bg-background text-sm focus:outline-none focus:ring-2 focus:ring-primary" />
{ setCheckOut(e.target.value); setAvailability(null) }} className="w-full border border-border rounded-lg px-3 py-2 bg-background text-sm focus:outline-none focus:ring-2 focus:ring-primary" />
{/* Availability indicator */} {checkingAvail &&

Checking availability…

} {availability && (
{availability.available ? "✓ Available for selected dates" : "✗ Not available for selected dates — please choose different dates"}
)} {/* Price breakdown */} {nights > 0 && pricePerNight > 0 && (
{formatPrice(pricePerNight, currency)} × {nights} night{nights !== 1 ? 's' : ''} {formatPrice(totalPrice, currency)}
Total {formatPrice(totalPrice, currency)}
)} {/* Guest details */}
setForm(f => ({...f, name: e.target.value}))} className="w-full border border-border rounded-lg px-3 py-2 bg-background text-sm focus:outline-none focus:ring-2 focus:ring-primary" />
setForm(f => ({...f, email: e.target.value}))} className="w-full border border-border rounded-lg px-3 py-2 bg-background text-sm focus:outline-none focus:ring-2 focus:ring-primary" />
setForm(f => ({...f, phone: e.target.value}))} placeholder="+27 " className="w-full border border-border rounded-lg px-3 py-2 bg-background text-sm focus:outline-none focus:ring-2 focus:ring-primary" />