'use client' /** * @description Kiosk walk-in booking flow for in-store service reservations * @audit BUSINESS RULE: Walk-in flow designed for touch screen with large buttons and simple navigation * @audit SECURITY: Authenticated via x-kiosk-api-key header for all API calls * @audit Validate: Multi-step flow with service → customer → confirm → success states * @audit PERFORMANCE: Optimized for offline-capable touch interface */ 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' import { ResourceAssignment } from './ResourceAssignment' import { Clock, User, Mail, Phone, CheckCircle } from 'lucide-react' interface WalkInFlowProps { apiKey: string onComplete: (booking: any) => void onCancel: () => void } /** * @description Walk-in booking flow component for kiosk terminals * @param {string} apiKey - Kiosk API key for authentication * @param {Function} onComplete - Callback when walk-in booking is completed successfully * @param {Function} onCancel - Callback when customer cancels the walk-in process * @returns {JSX.Element} Multi-step wizard for service selection, customer info, and confirmation * @audit BUSINESS RULE: 4-step flow: services → customer info → resource assignment → success * @audit BUSINESS RULE: Resources auto-assigned based on availability and service priority * @audit SECURITY: All API calls require valid kiosk API key in header * @audit Validate: Customer name and service selection required before booking * @audit PERFORMANCE: Single-page flow optimized for touch interaction * @audit AUDIT: Walk-in bookings logged through /api/kiosk/walkin endpoint */ export function WalkInFlow({ apiKey, onComplete, onCancel }: WalkInFlowProps) { const [step, setStep] = useState<'services' | 'customer' | 'confirm' | 'success'>('services') const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [services, setServices] = useState([]) const [selectedService, setSelectedService] = useState(null) const [customerData, setCustomerData] = useState({ name: '', email: '', phone: '' }) const [availableResources, setAvailableResources] = useState(null) const [createdBooking, setCreatedBooking] = useState(null) const loadServices = async () => { setLoading(true) setError(null) try { const response = await fetch('/api/services', { headers: { 'x-kiosk-api-key': apiKey } }) const data = await response.json() if (!response.ok) { throw new Error(data.error || 'Error al cargar servicios') } setServices(data.services || []) } catch (err) { setError(err instanceof Error ? err.message : 'Error al cargar servicios') } finally { setLoading(false) } } const checkAvailability = async (service: any) => { setLoading(true) setError(null) try { const now = new Date() const endTime = new Date(now) endTime.setMinutes(endTime.getMinutes() + service.duration_minutes) const response = await fetch( `/api/kiosk/resources/available?start_time=${now.toISOString()}&end_time=${endTime.toISOString()}&service_id=${service.id}`, { headers: { 'x-kiosk-api-key': apiKey } } ) const data = await response.json() if (!response.ok) { throw new Error(data.error || 'Error al verificar disponibilidad') } if (data.resources.length === 0) { setError('No hay espacios disponibles ahora mismo') return } setSelectedService(service) setAvailableResources(data.resources) setStep('customer') } catch (err) { setError(err instanceof Error ? err.message : 'Error al verificar disponibilidad') } finally { setLoading(false) } } const handleCustomerSubmit = async () => { if (!customerData.name || !customerData.email) { setError('Nombre y email son requeridos') return } const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ if (!emailRegex.test(customerData.email)) { setError('Email inválido') return } setStep('confirm') } const handleConfirmBooking = async () => { setLoading(true) setError(null) try { const response = await fetch('/api/kiosk/walkin', { method: 'POST', headers: { 'x-kiosk-api-key': apiKey, 'Content-Type': 'application/json' }, body: JSON.stringify({ customer_email: customerData.email, customer_phone: customerData.phone, customer_name: customerData.name, service_id: selectedService.id, notes: 'Walk-in desde kiosko' }) }) const data = await response.json() if (!response.ok) { throw new Error(data.error || 'Error al crear reserva') } setCreatedBooking(data.booking) setStep('success') onComplete(data.booking) } catch (err) { setError(err instanceof Error ? err.message : 'Error al crear reserva') } finally { setLoading(false) } } const formatCurrency = (amount: number) => { return new Intl.NumberFormat('es-MX', { style: 'currency', currency: 'MXN' }).format(amount) } const formatDateTime = (dateTime: string) => { const date = new Date(dateTime) return new Intl.DateTimeFormat('es-MX', { dateStyle: 'full', timeStyle: 'short', timeZone: 'America/Monterrey' }).format(date) } if (step === 'services') { return ( Reserva Inmediata (Walk-in) Selecciona el servicio que deseas recibir ahora {services.length === 0 && !loading && ( )} {loading && (
Cargando servicios...
)} {services.length > 0 && (
{services.map((service) => ( ))}
)} {error && (
{error}
)}
) } if (step === 'customer') { return ( Tus Datos Ingresa tu información para crear la reserva {selectedService && (

{selectedService.name}

{formatCurrency(selectedService.base_price)} • {selectedService.duration_minutes} min

)}
setCustomerData({ ...customerData, name: e.target.value })} disabled={loading} />
setCustomerData({ ...customerData, email: e.target.value })} disabled={loading} />
setCustomerData({ ...customerData, phone: e.target.value })} disabled={loading} />
{error && (
{error}
)}
) } if (step === 'confirm') { return ( Confirmar Reserva Revisa los detalles antes de confirmar

Servicio

{selectedService.name}

{formatCurrency(selectedService.base_price)} • {selectedService.duration_minutes} minutos

Cliente

{customerData.name}

{customerData.email}

{customerData.phone && (

{customerData.phone}

)}
{availableResources && ( )}
{error && (
{error}
)}
) } if (step === 'success' && createdBooking) { return ( ¡Reserva Creada con Éxito! Tu código de reserva es: {createdBooking.short_id}

Detalles de la Reserva

Código: {createdBooking.short_id}
Servicio: {createdBooking.service?.name}
Artista: {createdBooking.staff_name || createdBooking.staff?.display_name}
Espacio: {createdBooking.resource_name || createdBooking.resource?.name}
Hora: {formatDateTime(createdBooking.start_time_utc)}
Estado: Confirmada
) } return null }