'use client' /** * @description Kiosk booking confirmation interface for customers arriving with appointments * @audit BUSINESS RULE: Customers confirm appointments by entering 6-character short ID * @audit SECURITY: Authenticated via x-kiosk-api-key header for all API calls * @audit Validate: Only pending bookings can be confirmed; already confirmed shows warning * @audit PERFORMANCE: Large touch-friendly input optimized for self-service kiosks */ import { useState } from 'react' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' interface BookingConfirmationProps { apiKey: string onConfirm: (booking: any) => void onCancel: () => void } /** * @description Booking confirmation component for kiosk self-service check-in * @param {string} apiKey - Kiosk API key for authentication * @param {Function} onConfirm - Callback when booking is successfully confirmed * @param {Function} onCancel - Callback when customer cancels the process * @returns {JSX.Element} Input form for 6-character booking code with confirmation options * @audit BUSINESS RULE: Search by short_id (6 characters) for quick customer lookup * @audit BUSINESS RULE: Only pending bookings can be confirmed; other statuses show error * @audit SECURITY: All API calls require valid kiosk API key in header * @audit Validate: Short ID must be exactly 6 characters * @audit PERFORMANCE: Single API call to fetch booking by short_id * @audit AUDIT: Booking confirmations logged through /api/kiosk/bookings/[shortId]/confirm */ export function BookingConfirmation({ apiKey, onConfirm, onCancel }: BookingConfirmationProps) { const [shortId, setShortId] = useState('') const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [booking, setBooking] = useState(null) const [confirming, setConfirming] = useState(false) const handleSearch = async () => { if (!shortId || shortId.length !== 6) { setError('Ingresa el código de 6 caracteres de tu cita') return } setLoading(true) setError(null) setBooking(null) try { const response = await fetch(`/api/kiosk/bookings?short_id=${shortId}`, { headers: { 'x-kiosk-api-key': apiKey } }) const data = await response.json() if (!response.ok) { throw new Error(data.error || 'No se encontró la cita') } if (!data.bookings || data.bookings.length === 0) { setError('No se encontró ninguna cita con ese código') return } const foundBooking = data.bookings[0] if (foundBooking.status !== 'pending') { setError(`La cita ya está ${foundBooking.status === 'confirmed' ? 'confirmada' : foundBooking.status}`) setBooking(foundBooking) return } setBooking(foundBooking) } catch (err) { setError(err instanceof Error ? err.message : 'Error al buscar la cita') } finally { setLoading(false) } } const handleConfirm = async () => { if (!booking) return setConfirming(true) setError(null) try { const response = await fetch(`/api/kiosk/bookings/${shortId}/confirm`, { method: 'POST', headers: { 'x-kiosk-api-key': apiKey } }) const data = await response.json() if (!response.ok) { throw new Error(data.error || 'Error al confirmar la cita') } onConfirm(data.booking) } catch (err) { setError(err instanceof Error ? err.message : 'Error al confirmar la cita') } finally { setConfirming(false) } } const formatDateTime = (dateTime: string, timezone: string) => { const date = new Date(dateTime) return new Intl.DateTimeFormat('es-MX', { dateStyle: 'full', timeStyle: 'short', timeZone: timezone || 'America/Monterrey' }).format(date) } return ( Confirmar Cita Ingresa el código de tu cita para confirmar tu llegada {!booking ? ( <>
setShortId(e.target.value.toUpperCase())} maxLength={6} className="text-center text-2xl tracking-widest uppercase" disabled={loading} />
{error && (
{error}
)} ) : (

Detalles de la Cita

Código: {booking.short_id}
Servicio: {booking.service?.name}
Duración: {booking.service?.duration_minutes} minutos
Artista: {booking.staff?.display_name}
Espacio: {booking.resource?.name}
Fecha: {formatDateTime(booking.start_time_utc, 'America/Monterrey')}
Estado: {booking.status === 'confirmed' ? 'Confirmada' : booking.status === 'pending' ? 'Pendiente' : booking.status}
{booking.status === 'pending' && ( <> )} {error && (
{error}
)}
)}
) }