diff --git a/CHANGELOG.md b/CHANGELOG.md index 207b6f5..9d43513 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,11 @@ and this project follows [Semantic Versioning](https://semver.org/). - `/dashboard/user/pay` — the self-service partial-payment page — has been removed; it's no longer linked to from anywhere in the app. +### Fixed + +- `/registration/success` read the auth token from the wrong `localStorage` key (`token` instead of `hope_events_token`), which silently broke loading the registration/attendee-form data on that page and made the "Pay with Yoco" button fail with "Not authorized, token failed". Now uses the shared auth context, like the rest of the app. +- User dashboard: clicking "Make payment" briefly showed a "Sending tickets…" loading dialog (borrowed from the ticket-email flow) instead of a payment-specific message. + ## [1.1.0] - 2026-07-24 ### Added diff --git a/frontend/src/app/dashboard/user/page.tsx b/frontend/src/app/dashboard/user/page.tsx index 0c737fe..0ae717e 100644 --- a/frontend/src/app/dashboard/user/page.tsx +++ b/frontend/src/app/dashboard/user/page.tsx @@ -87,7 +87,7 @@ export default function UserDashboardPage() { // Registration details modal const [activeRegId, setActiveRegId] = useState(null); - const [dialog, setDialog] = useState<{ open: boolean; message: string; loading?: boolean }>({ open: false, message: "", loading: false }); + const [dialog, setDialog] = useState<{ open: boolean; message: string; loading?: boolean; loadingTitle?: string; loadingSubtitle?: string }>({ open: false, message: "", loading: false }); // Track whether the active registration's event has attendee forms const [activeEventHasForm, setActiveEventHasForm] = useState(null); @@ -318,7 +318,7 @@ export default function UserDashboardPage() { if (!token) return; setError(null); setInfo(null); - setDialog({ open: true, message: "Creating payment link…", loading: true }); + setDialog({ open: true, message: "Creating payment link…", loading: true, loadingTitle: "Creating payment link…", loadingSubtitle: "Please wait while we redirect you to Yoco." }); try { const res = await createFullPaymentCheckout(token, registrationId); if (res.priceUpdated) { @@ -1131,8 +1131,8 @@ export default function UserDashboardPage() { {dialog.loading ? (
-
Sending tickets…
-

Please wait while we send your tickets.

+
{dialog.loadingTitle || "Sending tickets…"}
+

{dialog.loadingSubtitle || "Please wait while we send your tickets."}

) : ( <> diff --git a/frontend/src/app/registration/success/page.tsx b/frontend/src/app/registration/success/page.tsx index f04c19f..b519980 100644 --- a/frontend/src/app/registration/success/page.tsx +++ b/frontend/src/app/registration/success/page.tsx @@ -3,12 +3,14 @@ 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"; 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(() => { @@ -32,7 +34,6 @@ function RegistrationSuccessContent() { try { setLoading(true); // Try to fetch registration and event form - const token = (typeof window !== 'undefined') ? localStorage.getItem('token') : null; const r = token ? await (await import('@/lib/api')).apiFetch(`/api/registrations/${encodeURIComponent(registrationId)}`, { authToken: token }) : null; if (r) { setReg(r); @@ -50,7 +51,7 @@ function RegistrationSuccessContent() { setLoading(false); } })(); - }, [registrationId]); + }, [registrationId, token]); const totalDue = React.useMemo(() => { if (!reg) return 0; @@ -70,7 +71,6 @@ function RegistrationSuccessContent() { // attempt ticket generation if status is paid (may fail if required forms aren't completed) (async () => { try { - const token = (typeof window !== 'undefined') ? localStorage.getItem('token') : null; if (token && reg?.id) { await (await import('@/lib/api')).apiFetch('/api/tickets/generate', { method: 'POST', authToken: token, body: { registrationId: reg.id } }); } @@ -78,11 +78,10 @@ function RegistrationSuccessContent() { setTimeout(() => router.replace('/dashboard'), 600); })(); } - }, [reg, totalDue, router]); + }, [reg, totalDue, router, token]); const goPay = async () => { if (!registrationId) return; - const token = (typeof window !== 'undefined') ? localStorage.getItem('token') : null; if (!token) { setError('Please login to pay.'); return; } try { setError(null); @@ -153,6 +152,7 @@ function RegistrationSuccessContent() { } 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); @@ -181,7 +181,6 @@ function AttendeeForms({ reg, form, formsData, setFormsData, setError, setInfo } const submit = async () => { try { setError(null); setInfo(null); - const token = (typeof window !== 'undefined') ? localStorage.getItem('token') : null; if (!token) { setError('Please login to submit forms.'); return; } const payload = [] as any[]; for (let i = 0; i < count; i++) { @@ -201,7 +200,6 @@ function AttendeeForms({ reg, form, formsData, setFormsData, setError, setInfo } const saveDraft = async () => { try { setError(null); setInfo(null); - const token = (typeof window !== 'undefined') ? localStorage.getItem('token') : null; if (!token) { setError('Please login to save drafts.'); return; } await (await import('@/lib/api')).apiFetch(`/api/registrations/${encodeURIComponent(registrationId)}/forms/draft`, { method: 'PUT',