263 lines
9.4 KiB
TypeScript
263 lines
9.4 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import { useParams } from 'next/navigation';
|
|
import Link from 'next/link';
|
|
import { useLanguage } from '@/context/LanguageContext';
|
|
import { ticketsApi, Ticket } from '@/lib/api';
|
|
import Card from '@/components/ui/Card';
|
|
import Button from '@/components/ui/Button';
|
|
import {
|
|
CheckCircleIcon,
|
|
ClockIcon,
|
|
XCircleIcon,
|
|
TicketIcon,
|
|
ArrowPathIcon,
|
|
ArrowDownTrayIcon,
|
|
} from '@heroicons/react/24/outline';
|
|
|
|
export default function BookingSuccessPage() {
|
|
const params = useParams();
|
|
const { t, locale } = useLanguage();
|
|
const [ticket, setTicket] = useState<Ticket | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [polling, setPolling] = useState(false);
|
|
|
|
const ticketId = params.ticketId as string;
|
|
|
|
const checkPaymentStatus = async () => {
|
|
try {
|
|
const { ticket: ticketData } = await ticketsApi.getById(ticketId);
|
|
setTicket(ticketData);
|
|
|
|
// If still pending, continue polling
|
|
if (ticketData.status === 'pending' && ticketData.payment?.status === 'pending') {
|
|
return false; // Not done yet
|
|
}
|
|
return true; // Done polling
|
|
} catch (error) {
|
|
console.error('Error checking payment status:', error);
|
|
return true; // Stop polling on error
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (!ticketId) return;
|
|
|
|
// Initial load
|
|
checkPaymentStatus().finally(() => setLoading(false));
|
|
|
|
// Poll for payment status every 3 seconds
|
|
setPolling(true);
|
|
const interval = setInterval(async () => {
|
|
const isDone = await checkPaymentStatus();
|
|
if (isDone) {
|
|
setPolling(false);
|
|
clearInterval(interval);
|
|
}
|
|
}, 3000);
|
|
|
|
// Stop polling after 5 minutes
|
|
const timeout = setTimeout(() => {
|
|
setPolling(false);
|
|
clearInterval(interval);
|
|
}, 5 * 60 * 1000);
|
|
|
|
return () => {
|
|
clearInterval(interval);
|
|
clearTimeout(timeout);
|
|
};
|
|
}, [ticketId]);
|
|
|
|
const formatDate = (dateStr: string) => {
|
|
return new Date(dateStr).toLocaleDateString(locale === 'es' ? 'es-ES' : 'en-US', {
|
|
weekday: 'long',
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
});
|
|
};
|
|
|
|
const formatTime = (dateStr: string) => {
|
|
return new Date(dateStr).toLocaleTimeString(locale === 'es' ? 'es-ES' : 'en-US', {
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
});
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="section-padding">
|
|
<div className="container-page max-w-2xl text-center">
|
|
<div className="animate-spin w-8 h-8 border-4 border-primary-yellow border-t-transparent rounded-full mx-auto" />
|
|
<p className="mt-4 text-gray-600">
|
|
{locale === 'es' ? 'Verificando pago...' : 'Verifying payment...'}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!ticket) {
|
|
return (
|
|
<div className="section-padding">
|
|
<div className="container-page max-w-2xl">
|
|
<Card className="p-8 text-center">
|
|
<XCircleIcon className="w-16 h-16 text-red-500 mx-auto mb-4" />
|
|
<h1 className="text-2xl font-bold text-primary-dark mb-2">
|
|
{locale === 'es' ? 'Reserva no encontrada' : 'Booking not found'}
|
|
</h1>
|
|
<p className="text-gray-600 mb-6">
|
|
{locale === 'es'
|
|
? 'No pudimos encontrar tu reserva. Por favor, contacta con soporte.'
|
|
: 'We could not find your booking. Please contact support.'}
|
|
</p>
|
|
<Link href="/events">
|
|
<Button>{locale === 'es' ? 'Ver Eventos' : 'Browse Events'}</Button>
|
|
</Link>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const isPaid = ticket.status === 'confirmed' || ticket.payment?.status === 'paid';
|
|
const isPending = ticket.status === 'pending' && ticket.payment?.status === 'pending';
|
|
const isFailed = ticket.payment?.status === 'failed';
|
|
|
|
return (
|
|
<div className="section-padding">
|
|
<div className="container-page max-w-2xl">
|
|
<Card className="p-8 text-center">
|
|
{/* Status Icon */}
|
|
{isPaid ? (
|
|
<div className="w-16 h-16 rounded-full bg-green-100 flex items-center justify-center mx-auto mb-6">
|
|
<CheckCircleIcon className="w-10 h-10 text-green-600" />
|
|
</div>
|
|
) : isPending ? (
|
|
<div className="w-16 h-16 rounded-full bg-yellow-100 flex items-center justify-center mx-auto mb-6">
|
|
<ClockIcon className="w-10 h-10 text-yellow-600" />
|
|
</div>
|
|
) : (
|
|
<div className="w-16 h-16 rounded-full bg-red-100 flex items-center justify-center mx-auto mb-6">
|
|
<XCircleIcon className="w-10 h-10 text-red-600" />
|
|
</div>
|
|
)}
|
|
|
|
{/* Title */}
|
|
<h1 className="text-2xl font-bold text-primary-dark mb-2">
|
|
{isPaid
|
|
? (locale === 'es' ? '¡Pago Confirmado!' : 'Payment Confirmed!')
|
|
: isPending
|
|
? (locale === 'es' ? 'Esperando Pago...' : 'Waiting for Payment...')
|
|
: (locale === 'es' ? 'Pago Fallido' : 'Payment Failed')
|
|
}
|
|
</h1>
|
|
|
|
<p className="text-gray-600 mb-6">
|
|
{isPaid
|
|
? (locale === 'es'
|
|
? 'Tu reserva está confirmada. ¡Te esperamos!'
|
|
: 'Your booking is confirmed. See you there!')
|
|
: isPending
|
|
? (locale === 'es'
|
|
? 'Estamos verificando tu pago. Esto puede tomar unos segundos.'
|
|
: 'We are verifying your payment. This may take a few seconds.')
|
|
: (locale === 'es'
|
|
? 'Hubo un problema con tu pago. Por favor, intenta de nuevo.'
|
|
: 'There was an issue with your payment. Please try again.')
|
|
}
|
|
</p>
|
|
|
|
{/* Polling indicator */}
|
|
{polling && isPending && (
|
|
<div className="flex items-center justify-center gap-2 text-yellow-600 mb-6">
|
|
<ArrowPathIcon className="w-5 h-5 animate-spin" />
|
|
<span className="text-sm">
|
|
{locale === 'es' ? 'Verificando...' : 'Checking...'}
|
|
</span>
|
|
</div>
|
|
)}
|
|
|
|
{/* Ticket Details */}
|
|
<div className="bg-secondary-gray rounded-lg p-6 mb-6">
|
|
<div className="flex items-center justify-center gap-2 mb-4">
|
|
<TicketIcon className="w-6 h-6 text-primary-yellow" />
|
|
<span className="font-mono text-lg font-bold">{ticket.qrCode}</span>
|
|
</div>
|
|
|
|
<div className="text-sm text-gray-600 space-y-2">
|
|
{ticket.event && (
|
|
<>
|
|
<p><strong>{locale === 'es' ? 'Evento' : 'Event'}:</strong> {ticket.event.title}</p>
|
|
<p><strong>{locale === 'es' ? 'Fecha' : 'Date'}:</strong> {formatDate(ticket.event.startDatetime)}</p>
|
|
<p><strong>{locale === 'es' ? 'Hora' : 'Time'}:</strong> {formatTime(ticket.event.startDatetime)}</p>
|
|
<p><strong>{locale === 'es' ? 'Ubicación' : 'Location'}:</strong> {ticket.event.location}</p>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Status Badge */}
|
|
<div className="mb-6">
|
|
<span className={`inline-block px-4 py-2 rounded-full text-sm font-medium ${
|
|
isPaid
|
|
? 'bg-green-100 text-green-800'
|
|
: isPending
|
|
? 'bg-yellow-100 text-yellow-800'
|
|
: 'bg-red-100 text-red-800'
|
|
}`}>
|
|
{isPaid
|
|
? (locale === 'es' ? 'Confirmado' : 'Confirmed')
|
|
: isPending
|
|
? (locale === 'es' ? 'Pendiente de Pago' : 'Pending Payment')
|
|
: (locale === 'es' ? 'Pago Fallido' : 'Payment Failed')
|
|
}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Email note */}
|
|
{isPaid && (
|
|
<p className="text-sm text-gray-500 mb-6">
|
|
{locale === 'es'
|
|
? 'Un correo de confirmación ha sido enviado a tu bandeja de entrada.'
|
|
: 'A confirmation email has been sent to your inbox.'}
|
|
</p>
|
|
)}
|
|
|
|
{/* Download Ticket Button */}
|
|
{isPaid && (
|
|
<div className="mb-6">
|
|
<a
|
|
href={ticket.bookingId
|
|
? `/api/tickets/booking/${ticket.bookingId}/pdf`
|
|
: `/api/tickets/${ticketId}/pdf`
|
|
}
|
|
download
|
|
className="inline-flex items-center gap-2 px-4 py-2 bg-primary-yellow text-primary-dark font-medium rounded-btn hover:bg-primary-yellow/90 transition-colors"
|
|
>
|
|
<ArrowDownTrayIcon className="w-5 h-5" />
|
|
{locale === 'es' ? 'Descargar Ticket(s)' : 'Download Ticket(s)'}
|
|
</a>
|
|
</div>
|
|
)}
|
|
|
|
{/* Actions */}
|
|
<div className="flex flex-col sm:flex-row gap-3 justify-center">
|
|
<Link href="/events">
|
|
<Button variant="outline">
|
|
{locale === 'es' ? 'Ver Más Eventos' : 'Browse More Events'}
|
|
</Button>
|
|
</Link>
|
|
<Link href="/">
|
|
<Button>
|
|
{locale === 'es' ? 'Volver al Inicio' : 'Back to Home'}
|
|
</Button>
|
|
</Link>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|