'use client'; import { useMemo, useState } from 'react'; import Link from 'next/link'; import { CalendarIcon, ClockIcon, MapPinIcon, ArrowTopRightOnSquareIcon, TicketIcon, UserPlusIcon, QrCodeIcon, } from '@heroicons/react/24/outline'; import toast from 'react-hot-toast'; import Card from '@/components/ui/Card'; import Button from '@/components/ui/Button'; import type { UserTicket, NextEventInfo } from '@/lib/api'; import { useLanguage } from '@/context/LanguageContext'; import { formatDateLong, formatTime, parseDate } from '@/lib/utils'; import { StatusPill, deriveTicketStatus } from './_shared/status'; import { groupByBooking, isUnpaid, isAwaitingApproval, isOnHold, isActionableAttention, ticketAmount, shareTicket, isToday, type BookingGroup, } from './_shared/helpers'; import PayActions from './_shared/PayActions'; import AttentionBanner from './_shared/AttentionBanner'; import QrTicketModal from './_shared/QrTicketModal'; interface OverviewTabProps { nextEvent: NextEventInfo | null; tickets: UserTicket[]; locale: string; userName: string; onChange: () => void; } export default function OverviewTab({ nextEvent, tickets, locale, userName, onChange, }: OverviewTabProps) { const [qrTickets, setQrTickets] = useState(null); const activeTickets = useMemo( () => tickets.filter((t) => t.status !== 'cancelled'), [tickets] ); // Banner: the single booking that most needs attention (unpaid first, then // awaiting approval), ordered by soonest event. const attentionTicket = useMemo(() => { const candidates = activeTickets.filter( (t) => isActionableAttention(t) && (isUnpaid(t) || isOnHold(t) || isAwaitingApproval(t)) ); const priority = (t: UserTicket) => (isUnpaid(t) ? 0 : isOnHold(t) ? 1 : 2); candidates.sort((a, b) => { const aUnpaid = priority(a); const bUnpaid = priority(b); if (aUnpaid !== bUnpaid) return aUnpaid - bUnpaid; const aStart = a.event?.startDatetime ? parseDate(a.event.startDatetime).getTime() : Infinity; const bStart = b.event?.startDatetime ? parseDate(b.event.startDatetime).getTime() : Infinity; return aStart - bStart; }); return candidates[0] || null; }, [activeTickets]); // Hero: full tickets for the next event's booking. const heroGroup = useMemo(() => { if (!nextEvent) return null; const primary = activeTickets.find((t) => t.id === nextEvent.ticket.id) || null; if (!primary) return null; const groupTickets = primary.bookingId ? activeTickets.filter((t) => t.bookingId === primary.bookingId) : [primary]; return { bookingId: primary.bookingId || primary.id, tickets: groupTickets }; }, [nextEvent, activeTickets]); // "Also coming up": upcoming booking groups other than the hero. const upcomingGroups = useMemo(() => { const now = Date.now(); const groups = groupByBooking( activeTickets.filter( (t) => t.event && parseDate(t.event.startDatetime).getTime() > now ) ); return groups.filter((g) => g.bookingId !== heroGroup?.bookingId); }, [activeTickets, heroGroup]); const handleShare = async (ticket: UserTicket) => { const title = (locale === 'es' && ticket.event?.titleEs ? ticket.event.titleEs : ticket.event?.title) || 'Event'; const result = await shareTicket(ticket.id, title, locale); if (result === 'copied') toast.success(locale === 'es' ? 'Enlace copiado' : 'Link copied'); else if (result === 'failed') toast.error(locale === 'es' ? 'No se pudo compartir' : 'Could not share'); }; // Empty state. if (activeTickets.length === 0) { return (

{locale === 'es' ? `¡Hola, ${userName}!` : `Welcome, ${userName}!`}

{locale === 'es' ? 'Todavía no tienes reservas. Encuentra tu primer evento de intercambio de idiomas.' : "You have no bookings yet. Find your first language exchange event."}

); } return (
{/* 1 — Attention banner (only when money is owed / awaiting). */} {attentionTicket && ( )} {/* 2 — Next event hero. */} {heroGroup && heroGroup.tickets[0].event && ( setQrTickets(heroGroup.tickets)} onShare={handleShare} /> )} {/* 3 — Also coming up. */} {upcomingGroups.length > 0 && (

{locale === 'es' ? 'También se viene' : 'Also coming up'}

{upcomingGroups.map((group) => { const t = group.tickets[0]; const status = deriveTicketStatus(t.status, t.payment?.status); const title = (locale === 'es' && t.event?.titleEs ? t.event.titleEs : t.event?.title) || 'Event'; const { amount, currency } = ticketAmount(t); return (

{title}

{group.tickets.length > 1 && ( ×{group.tickets.length} )}

{t.event && formatDateLong(t.event.startDatetime, locale as 'en' | 'es')}

{isOnHold(t) ? ( ) : isUnpaid(t) ? ( ) : ( (status === 'confirmed' || status === 'attended') && ( ) )}
); })}
)} {/* 6 — Browse events. */}
{qrTickets && ( setQrTickets(null)} /> )}
); } function HeroCard({ group, locale, onChange, onShowQr, onShare, }: { group: BookingGroup; locale: string; onChange: () => void; onShowQr: () => void; onShare: (ticket: UserTicket) => void; }) { const { t } = useLanguage(); const ticket = group.tickets[0]; const event = ticket.event!; const title = (locale === 'es' && event.titleEs ? event.titleEs : event.title) || 'Event'; const status = deriveTicketStatus(ticket.status, ticket.payment?.status); const { amount, currency } = ticketAmount(ticket); const multi = group.tickets.length > 1; const today = isToday(event.startDatetime); const showQrReady = status === 'confirmed' || status === 'attended'; return ( {/* Image with status pill + today badge overlay. */}
{event.bannerUrl ? ( {title} ) : (
)}
{today && ( {locale === 'es' ? 'Hoy' : 'Today'} )}

{locale === 'es' ? 'Tu próximo evento' : 'Your next event'}

{title}

{multi && ( {locale === 'es' ? `${group.tickets.length} entradas` : `${group.tickets.length} tickets`} )}

{formatDateLong(event.startDatetime, locale as 'en' | 'es')}

{formatTime(event.startDatetime, locale as 'en' | 'es')}

{event.location} {event.locationUrl && ( {locale === 'es' ? 'Cómo llegar' : 'Get directions'} )}

{/* Today + ready → show the QR prominently for check-in. */} {today && showQrReady && (

{locale === 'es' ? 'Muestra este código en la entrada' : 'Show this code at the door'}

)} {/* Smart primary action. */} {isOnHold(ticket) ? ( ) : isUnpaid(ticket) ? ( ) : isAwaitingApproval(ticket) ? (

{t('dashboard.payment.receivedConfirmShortly')}

) : ( !today && ( ) )} {/* Multi-ticket guest reminder. */} {multi && showQrReady && (

{locale === 'es' ? 'Tienes una entrada de más para un invitado.' : 'You have a spare ticket for a guest.'}

)}
); }