"use client"; import React, { Suspense } from "react"; import { Navbar } from "@/components/layout/Navbar"; import { Footer } from "@/components/layout/Footer"; import { useSearchParams, useRouter } from "next/navigation"; import { useAuth } from "@/hooks/useAuth"; import { CheckCircle2 } from "lucide-react"; type FormField = { id: string; type: 'yes_no'|'text'|'date'|'numeric'|'statement'|'paragraph'; label: string; isRequired?: boolean; helpText?: string|null }; function RegistrationSuccessContent() { const search = useSearchParams(); const router = useRouter(); const { token } = useAuth(); const registrationId = search.get("registrationId") || search.get("id"); const fallbackTotalParam = search.get("totalDue"); const fallbackTotalDue = React.useMemo(() => { const n = fallbackTotalParam ? Number(fallbackTotalParam) : 0; return isNaN(n) ? 0 : n; }, [fallbackTotalParam]); const [reg, setReg] = React.useState(null); const [form, setForm] = React.useState<{ isRequired: boolean; fields: FormField[] } | null>(null); const [formsData, setFormsData] = React.useState>>({}); const [loading, setLoading] = React.useState(false); const [error, setError] = React.useState(null); const [info, setInfo] = React.useState(null); const [payLoading, setPayLoading] = React.useState(false); React.useEffect(() => { (async () => { setError(null); setInfo(null); if (!registrationId) return; try { setLoading(true); // Try to fetch registration and event form const r = token ? await (await import('@/lib/api')).apiFetch(`/api/registrations/${encodeURIComponent(registrationId)}`, { authToken: token }) : null; if (r) { setReg(r); const ev = await (await import('@/lib/api')).apiFetch(`/api/events/${encodeURIComponent(r.eventId)}`); if (ev?.form) setForm(ev.form); // Load any saved draft try { const d = await (await import('@/lib/api')).apiFetch(`/api/registrations/${encodeURIComponent(registrationId)}/forms/draft`, { authToken: token || undefined }); if (d && d.data && typeof d.data === 'object') setFormsData(d.data); } catch {} } } catch (e: any) { setError(e?.message || 'Failed to load details'); } finally { setLoading(false); } })(); }, [registrationId, token]); const totalDue = React.useMemo(() => { if (!reg) return 0; try { return (reg.registrationOptions || []).reduce((s: number, ro: any) => { const unit = (ro.priceSnapshot !== null && ro.priceSnapshot !== undefined) ? Number(ro.priceSnapshot) : (ro.variant?.price ?? ro.eventOption?.price ?? 0); return s + unit * (ro.quantity || 0); }, 0); } catch { return 0; } }, [reg]); React.useEffect(() => { // If free registration, do not show Yoco and redirect to dashboard after a short delay if (reg && totalDue === 0) { // attempt ticket generation if status is paid (may fail if required forms aren't completed) (async () => { try { if (token && reg?.id) { await (await import('@/lib/api')).apiFetch('/api/tickets/generate', { method: 'POST', authToken: token, body: { registrationId: reg.id } }); } } catch {} setTimeout(() => router.replace('/dashboard'), 600); })(); } }, [reg, totalDue, router, token]); const goPay = async () => { if (!registrationId) return; if (!token) { setError('Please login to pay.'); return; } try { setError(null); setPayLoading(true); const { createFullPaymentCheckout } = await import('@/lib/api'); const res = await createFullPaymentCheckout(token, registrationId); if (res.priceUpdated) { // Early-bird price changed since registration — surface the new total and // reload the registration so the displayed totalDue reflects it, instead of redirecting. setError(`${res.message || 'Pricing has changed.'} New total: R ${(res.newTotal ?? 0).toFixed(2)}. Please try again.`); try { const r = await (await import('@/lib/api')).apiFetch(`/api/registrations/${encodeURIComponent(registrationId)}`, { authToken: token }); if (r) setReg(r); } catch {} return; } if (!res.redirectUrl) { setError('Failed to create checkout'); return; } window.location.href = res.redirectUrl; } catch (e: any) { setError(e?.message || 'Failed to create checkout'); } finally { setPayLoading(false); } }; const goDashboard = () => { router.push("/dashboard/user"); }; return (

Registration successful

Thank you! Your registration has been created{registrationId ? ` (#${registrationId.slice(0,8)})` : ""}.

{form?.isRequired && reg ? (
This event requires attendee details.
Please complete one form per main ticket to receive tickets. You can also do this later from your dashboard, but tickets cannot be generated until completed.
) : (totalDue > 0 || (!reg && fallbackTotalDue > 0)) ? (

Would you like to pay with Yoco now?

) : null}
{(totalDue > 0 || (!reg && fallbackTotalDue > 0)) && ( )}
{error &&

{error}

} {!registrationId && (

Missing registration reference. You can still go to your Dashboard to view registrations.

)}
); } function AttendeeForms({ reg, form, formsData, setFormsData, setError, setInfo }: { reg: any; form: { isRequired: boolean; fields: FormField[] }; formsData: Record>; setFormsData: any; setError: any; setInfo: any; }) { const { token } = useAuth(); const registrationId = reg?.id; const mainTickets = (reg?.registrationOptions || []).filter((o: any) => o?.eventOption?.isMainTicket).reduce((s: number, o: any) => s + (o.quantity || 0), 0); const count = Math.max(0, mainTickets); const canSubmit = React.useMemo(() => { if (!form || count <= 0) return false; const reqFields = (form.fields || []) .filter(f => !!f.isRequired && f.type !== 'statement' && f.type !== 'paragraph') .map(f => f.id); for (let i = 0; i < count; i++) { const data = formsData[i] || {}; for (const fid of reqFields) { const v = data[fid]; if (v === undefined || v === null || String(v).trim() === '') { return false; } } } return true; }, [form, formsData, count]); const update = (idx: number, fieldId: string, value: string) => { setFormsData((prev: any) => ({ ...prev, [idx]: { ...(prev[idx]||{}), [fieldId]: value } })); }; const submit = async () => { try { setError(null); setInfo(null); if (!token) { setError('Please login to submit forms.'); return; } const payload = [] as any[]; for (let i = 0; i < count; i++) { payload.push({ answers: formsData[i] || {} }); } await (await import('@/lib/api')).apiFetch(`/api/registrations/${encodeURIComponent(registrationId)}/forms/responses`, { method: 'POST', authToken: token, body: { responses: payload } }); setInfo('Attendee forms submitted. You will receive tickets once payment is confirmed.'); } catch (e: any) { setError(e?.message || 'Failed to submit forms'); } }; const saveDraft = async () => { try { setError(null); setInfo(null); if (!token) { setError('Please login to save drafts.'); return; } await (await import('@/lib/api')).apiFetch(`/api/registrations/${encodeURIComponent(registrationId)}/forms/draft`, { method: 'PUT', authToken: token, body: { data: formsData } }); setInfo('Draft saved. You can finish later from your dashboard.'); } catch (e: any) { setError(e?.message || 'Failed to save draft'); } }; if (!form || !Array.isArray(form.fields) || count === 0) return null; return (
Attendee details
{Array.from({ length: count }, (_, idx) => (
Attendee {idx + 1}
{form.fields.map((f) => (
{f.type === 'statement' ? (
{f.label}
) : f.type === 'paragraph' ? (
{f.label &&
{f.label}
} {f.helpText &&
{f.helpText}
}
) : ( <> {f.type === 'yes_no' ? ( ) : f.type === 'date' ? ( update(idx, f.id, e.target.value)} /> ) : f.type === 'numeric' ? ( update(idx, f.id, e.target.value)} /> ) : ( update(idx, f.id, e.target.value)} /> )} {f.helpText &&
{f.helpText}
} )}
))}
))}
); } export default function RegistrationSuccessPage() { return ( Loading...}> ); }