'use client' import { useState, useEffect } from 'react' import { useParams } from 'next/navigation' import Image from 'next/image' import Link from 'next/link' import { ArrowLeft, CalendarCheck, CircleCheck, Loader2 } from 'lucide-react' const TIME_SLOTS = ['08:00','09:00','10:00','11:00','12:00','13:00','14:00','15:00','16:00','17:00'] 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 Service { id: string; title: string; description: string; thumbnail: string variants: { id: string; prices: { amount: number; currency_code: string }[] }[] } export default function BookingPage() { const params = useParams() const productId = params.productId as string const [service, setService] = useState(null) const [loadingSvc, setLoadingSvc] = useState(true) const [form, setForm] = useState({ name: '', email: '', phone: '', date: '', time: '', notes: '' }) const [bookedSlots, setBookedSlots] = useState([]) const [loadingSlots, setLoadingSlots] = useState(false) const [submitting, setSubmitting] = useState(false) const [confirmationNumber, setConfirmationNumber] = useState('') const [success, setSuccess] = useState(false) const [error, setError] = useState('') useEffect(() => { if (!productId) return fetch(`/api/medusa/store/products/${productId}?fields=*variants`) .then(r => r.json()) .then(d => { setService(d.product ?? null); setLoadingSvc(false) }) .catch(() => setLoadingSvc(false)) }, [productId]) // Fetch booked slots whenever date changes useEffect(() => { if (!form.date || !productId) return setLoadingSlots(true) setForm(f => ({ ...f, time: '' })) fetch(`/api/medusa/store/appointments?service_id=${productId}&date=${form.date}`) .then(r => r.json()) .then(d => { setBookedSlots(d.booked_slots ?? []); setLoadingSlots(false) }) .catch(() => setLoadingSlots(false)) }, [form.date, productId]) const price = service?.variants?.[0]?.prices?.[0] const today = new Date().toISOString().split('T')[0] const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() if (!form.time) { setError('Please select a time slot.'); return } setSubmitting(true) setError('') try { const res = await fetch('/api/medusa/store/appointments', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ service_id: productId, appointment_date: form.date, appointment_time: form.time, service_name: service?.title || 'Appointment', customer_name: form.name, customer_phone: form.phone, customer_email: form.email || null, notes: form.notes || null, }), signal: AbortSignal.timeout(15000), }) const data = await res.json() if (!res.ok) throw new Error(data.error || 'Booking failed') setConfirmationNumber(data.confirmation_number ?? '') setSuccess(true) } catch (err: unknown) { setError(err instanceof Error ? err.message : 'Something went wrong. Please try again or call us directly.') } setSubmitting(false) } if (loadingSvc) return
if (!service) return

Service not found.

Back to Services
if (success) return (

Appointment Booked!

{service.title}

{form.date} at {form.time}

{confirmationNumber &&

{confirmationNumber}

}

We will confirm your appointment via phone shortly.

Browse More Services
) return (
Back to Services
{service.thumbnail && (
{service.title}
)}

{service.title}

{service.description &&

{service.description}

} {price &&

{formatPrice(price.amount, price.currency_code)}

}

Book an Appointment

setForm(f => ({...f, date: 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" />
{form.date && (
{loadingSlots && }
{TIME_SLOTS.map(slot => { const booked = bookedSlots.includes(slot) const selected = form.time === slot return ( ) })}
)}
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, 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" />
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" />