import React from 'react'; import { useLocation, useNavigate, useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { CheckCircle2, Clock3, AlertTriangle, ReceiptText, Sparkles } from 'lucide-react'; import { YStack, XStack } from '@tamagui/stacks'; import { SizableText as Text } from '@tamagui/text'; import { EventAddonPurchaseSummary, getEvent, getEventAddonPurchase, TenantEvent } from '../api'; import { getApiErrorMessage } from '../lib/apiError'; import { adminPath, ADMIN_BILLING_PATH, ADMIN_EVENT_ADDONS_PATH, ADMIN_EVENT_CONTROL_ROOM_PATH, ADMIN_EVENT_VIEW_PATH } from '../constants'; import { useBackNavigation } from './hooks/useBackNavigation'; import { useAdminTheme } from './theme'; import { MobileShell } from './components/MobileShell'; import { CTAButton, MobileCard, PillBadge, SkeletonCard } from './components/Primitives'; import { resolveEventDisplayName } from '../lib/events'; function formatAmount(value: number | null, currency: string | null): string { if (value === null || value === undefined) { return '—'; } const resolvedCurrency = currency ?? 'EUR'; try { return new Intl.NumberFormat(undefined, { style: 'currency', currency: resolvedCurrency }).format(value); } catch { return `${value} ${resolvedCurrency}`; } } function formatDateTime(value: string | null | undefined): string { if (!value) { return '—'; } const date = new Date(value); if (Number.isNaN(date.getTime())) { return '—'; } return date.toLocaleString(undefined, { day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit', }); } async function launchConfetti(): Promise { if (typeof window === 'undefined') { return; } const prefersReducedMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches; if (prefersReducedMotion) { return; } try { const { default: confetti } = await import('canvas-confetti'); confetti({ particleCount: 80, spread: 70, origin: { x: 0.5, y: 0.3 }, ticks: 180, scalar: 0.95, }); setTimeout(() => { confetti({ particleCount: 50, spread: 90, origin: { x: 0.5, y: 0.2 }, ticks: 140, scalar: 0.8, }); }, 260); } catch {} } export default function MobileEventAddonSuccessPage() { const { slug } = useParams<{ slug: string }>(); const navigate = useNavigate(); const location = useLocation(); const { t } = useTranslation('management'); const { textStrong, text, muted, border, successText, warningText, danger, primary } = useAdminTheme(); const back = useBackNavigation(slug ? ADMIN_EVENT_ADDONS_PATH(slug) : adminPath('/mobile/events')); const [event, setEvent] = React.useState(null); const [purchase, setPurchase] = React.useState(null); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(null); const confettiTriggeredRef = React.useRef(false); const query = React.useMemo(() => { const params = new URLSearchParams(location.search); return { addonIntent: params.get('addon_intent') ?? undefined, checkoutId: params.get('checkout_id') ?? undefined, addonKey: params.get('addon_key') ?? undefined, }; }, [location.search]); const load = React.useCallback(async () => { if (!slug) { return; } setLoading(true); try { const [eventData, purchaseData] = await Promise.all([ getEvent(slug), getEventAddonPurchase(slug, { addonIntent: query.addonIntent, checkoutId: query.checkoutId, addonKey: query.addonKey, }), ]); setEvent(eventData); setPurchase(purchaseData); if (!purchaseData) { setError(t('events.addons.success.notFound', 'We are still confirming this purchase.')); } else { setError(null); } } catch (err) { setError(getApiErrorMessage(err, t('events.addons.success.loadFailed', 'Purchase result could not be loaded.'))); } finally { setLoading(false); } }, [query.addonIntent, query.addonKey, query.checkoutId, slug, t]); React.useEffect(() => { void load(); }, [load]); React.useEffect(() => { if (!purchase || purchase.status !== 'completed' || confettiTriggeredRef.current) { return; } confettiTriggeredRef.current = true; void launchConfetti(); }, [purchase]); const statusTone = purchase?.status === 'completed' ? 'success' : purchase?.status === 'pending' ? 'warning' : 'danger'; const statusText = purchase ? t(`mobileBilling.status.${purchase.status}`, purchase.status) : t('events.addons.success.statusUnknown', 'Unknown'); const StatusIcon = purchase?.status === 'completed' ? CheckCircle2 : purchase?.status === 'pending' ? Clock3 : AlertTriangle; const statusColor = purchase?.status === 'completed' ? successText : purchase?.status === 'pending' ? warningText : danger; if (loading) { return ( ); } return ( {purchase?.status === 'completed' ? t('events.addons.success.completedTitle', 'Purchase completed') : purchase?.status === 'pending' ? t('events.addons.success.pendingTitle', 'Purchase in progress') : t('events.addons.success.failedTitle', 'Purchase failed')} {purchase?.status === 'completed' ? t('events.addons.success.completedBody', 'Your event add-on is now active and limits update shortly.') : purchase?.status === 'pending' ? t('events.addons.success.pendingBody', 'We are still processing your payment confirmation.') : t('events.addons.success.failedBody', 'The payment could not be completed. You can try again.')} {statusText} {t('events.addons.event', 'Event')} {resolveEventDisplayName(event)} {t('events.addons.success.summaryTitle', 'Purchase summary')} {error ? ( {error} ) : null} {purchase ? ( {purchase.checkout_id ? ( ) : null} {purchase.transaction_id ? ( ) : null} ) : null} {purchase ? ( {purchase.extra_photos > 0 ? ( {t('mobileBilling.extra.photos', '+{{count}} photos', { count: purchase.extra_photos })} ) : null} {purchase.extra_guests > 0 ? ( {t('mobileBilling.extra.guests', '+{{count}} guests', { count: purchase.extra_guests })} ) : null} {purchase.extra_gallery_days > 0 ? ( {t('mobileBilling.extra.days', '+{{count}} days', { count: purchase.extra_gallery_days })} ) : null} ) : null} {t('events.addons.success.nextTitle', 'What next?')} navigate(slug ? ADMIN_EVENT_ADDONS_PATH(slug) : adminPath('/mobile/events'))} /> navigate(slug ? ADMIN_EVENT_CONTROL_ROOM_PATH(slug) : adminPath('/mobile/events'))} /> navigate(ADMIN_BILLING_PATH)} /> navigate(slug ? ADMIN_EVENT_VIEW_PATH(slug) : adminPath('/mobile/events'))} /> ); } function SummaryRow({ label, value }: { label: string; value: string }) { const { textStrong, muted, border } = useAdminTheme(); return ( {label} {value} ); }