'use client'; import { useState, useEffect } from 'react'; import Link from 'next/link'; import Image from 'next/image'; import { useLanguage } from '@/context/LanguageContext'; import { eventsApi, Event } from '@/lib/api'; import { formatPrice, formatDateLong, formatTime, eventSpotsLeft, isEventSoldOut } from '@/lib/utils'; import Card from '@/components/ui/Card'; import Button from '@/components/ui/Button'; import ShareButtons from '@/components/ShareButtons'; import { CalendarIcon, MapPinIcon, UserGroupIcon, ArrowLeftIcon, MinusIcon, PlusIcon, } from '@heroicons/react/24/outline'; interface EventDetailClientProps { eventId: string; initialEvent: Event; } const MAX_TICKETS_PER_PERSON = 5; export default function EventDetailClient({ eventId, initialEvent }: EventDetailClientProps) { const { t, locale } = useLanguage(); const [event, setEvent] = useState(initialEvent); const [mounted, setMounted] = useState(false); const [ticketQuantity, setTicketQuantity] = useState(1); // Ensure consistent hydration by only rendering dynamic content after mount useEffect(() => { setMounted(true); }, []); // Refresh event data on client for real-time availability useEffect(() => { eventsApi.getById(eventId) .then(({ event }) => setEvent(event)) .catch(console.error); }, [eventId]); // Server-authoritative availability (paid + claimed seats count; abandoned // pending bookings don't) — matches the booking API's sold-out check exactly. const spotsLeft = eventSpotsLeft(event); const isSoldOut = isEventSoldOut(event); const maxTickets = isSoldOut ? 0 : Math.min(MAX_TICKETS_PER_PERSON, Math.max(1, spotsLeft)); useEffect(() => { if (maxTickets > 0) { setTicketQuantity((q) => Math.min(q, maxTickets)); } }, [maxTickets]); const decreaseQuantity = () => { setTicketQuantity(prev => Math.max(1, prev - 1)); }; const increaseQuantity = () => { setTicketQuantity(prev => Math.min(maxTickets, prev + 1)); }; const formatDate = (dateStr: string) => formatDateLong(dateStr, locale as 'en' | 'es'); const fmtTime = (dateStr: string) => formatTime(dateStr, locale as 'en' | 'es'); const isCancelled = event.status === 'cancelled'; // Only calculate isPastEvent after mount to avoid hydration mismatch const isPastEvent = mounted ? new Date(event.startDatetime) < new Date() : false; const canBook = !isSoldOut && !isCancelled && !isPastEvent && (event.status === 'published' || event.status === 'unlisted'); // Booking card content - reused for mobile and desktop positions const BookingCardContent = () => ( <>

{t('events.details.price')}

{event.price === 0 ? t('events.details.free') : formatPrice(event.price, event.currency)}

{event.price > 0 && (

{locale === 'es' ? 'por persona' : 'per person'}

)}
{/* Ticket Quantity Selector */} {canBook && !event.externalBookingEnabled && (
{ticketQuantity}
{ticketQuantity > 1 && event.price > 0 && (

{locale === 'es' ? 'Total' : 'Total'}: {formatPrice(event.price * ticketQuantity, event.currency)}

)}
)} {canBook ? ( event.externalBookingEnabled && event.externalBookingUrl ? ( ) : ( ) ) : ( )} {!event.externalBookingEnabled && (

{spotsLeft} / {event.capacity} {t('events.details.spotsLeft')}

)} ); return (
{t('common.back')}
{/* Event Details */}
{/* Top section: Image + Event Info side by side on desktop */}
{/* Image - smaller on desktop, side by side */} {event.bannerUrl ? (
{`${event.title}
) : (
)} {/* Event title and key info */}

{locale === 'es' && event.titleEs ? event.titleEs : event.title}

{isCancelled && ( {t('events.details.cancelled')} )} {isSoldOut && !isCancelled && ( {t('events.details.soldOut')} )}

{t('events.details.date')}

{formatDate(event.startDatetime)}

{t('events.details.time')}

{fmtTime(event.startDatetime)} {event.endDatetime && ` - ${fmtTime(event.endDatetime)}`}

{t('events.details.location')}

{event.location}

{event.locationUrl && ( View on map )}
{!event.externalBookingEnabled && (

{t('events.details.capacity')}

{spotsLeft} / {event.capacity} {t('events.details.spotsLeft')}

)}
{/* Mobile Booking Card - shown between event details and description on mobile */} {/* Description section - separate card below */}

About this event

{locale === 'es' && event.descriptionEs ? event.descriptionEs : event.description}

{/* Social Sharing */}
{/* Desktop Booking Card - hidden on mobile, shown in sidebar on desktop */}
); }