Fix wrong auth token key and mislabeled loading dialog on payment flows
registration/success read the token from the wrong localStorage key (token instead of hope_events_token), which broke registration/form loading there and made the new "Pay with Yoco" checkout call fail with "Not authorized, token failed" — switched to the shared auth context like the rest of the app. dashboard/user's "Make payment" reused the ticket-email dialog, which hardcoded "Sending tickets…" while loading — the dialog now takes an optional loading title/subtitle so payNow can show its own message.
This commit is contained in:
@@ -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.
|
- `/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
|
## [1.1.0] - 2026-07-24
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export default function UserDashboardPage() {
|
|||||||
|
|
||||||
// Registration details modal
|
// Registration details modal
|
||||||
const [activeRegId, setActiveRegId] = useState<string | null>(null);
|
const [activeRegId, setActiveRegId] = useState<string | null>(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
|
// Track whether the active registration's event has attendee forms
|
||||||
const [activeEventHasForm, setActiveEventHasForm] = useState<boolean | null>(null);
|
const [activeEventHasForm, setActiveEventHasForm] = useState<boolean | null>(null);
|
||||||
@@ -318,7 +318,7 @@ export default function UserDashboardPage() {
|
|||||||
if (!token) return;
|
if (!token) return;
|
||||||
setError(null);
|
setError(null);
|
||||||
setInfo(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 {
|
try {
|
||||||
const res = await createFullPaymentCheckout(token, registrationId);
|
const res = await createFullPaymentCheckout(token, registrationId);
|
||||||
if (res.priceUpdated) {
|
if (res.priceUpdated) {
|
||||||
@@ -1131,8 +1131,8 @@ export default function UserDashboardPage() {
|
|||||||
{dialog.loading ? (
|
{dialog.loading ? (
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<div className="w-10 h-10 mb-3 border-4 border-blue-600 border-t-transparent rounded-full animate-spin" aria-label="Loading" />
|
<div className="w-10 h-10 mb-3 border-4 border-blue-600 border-t-transparent rounded-full animate-spin" aria-label="Loading" />
|
||||||
<div className="text-base font-medium">Sending tickets…</div>
|
<div className="text-base font-medium">{dialog.loadingTitle || "Sending tickets…"}</div>
|
||||||
<p className="text-sm text-gray-600 mt-1">Please wait while we send your tickets.</p>
|
<p className="text-sm text-gray-600 mt-1">{dialog.loadingSubtitle || "Please wait while we send your tickets."}</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ import React, { Suspense } from "react";
|
|||||||
import { Navbar } from "@/components/layout/Navbar";
|
import { Navbar } from "@/components/layout/Navbar";
|
||||||
import { Footer } from "@/components/layout/Footer";
|
import { Footer } from "@/components/layout/Footer";
|
||||||
import { useSearchParams, useRouter } from "next/navigation";
|
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 };
|
type FormField = { id: string; type: 'yes_no'|'text'|'date'|'numeric'|'statement'|'paragraph'; label: string; isRequired?: boolean; helpText?: string|null };
|
||||||
|
|
||||||
function RegistrationSuccessContent() {
|
function RegistrationSuccessContent() {
|
||||||
const search = useSearchParams();
|
const search = useSearchParams();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { token } = useAuth();
|
||||||
const registrationId = search.get("registrationId") || search.get("id");
|
const registrationId = search.get("registrationId") || search.get("id");
|
||||||
const fallbackTotalParam = search.get("totalDue");
|
const fallbackTotalParam = search.get("totalDue");
|
||||||
const fallbackTotalDue = React.useMemo(() => {
|
const fallbackTotalDue = React.useMemo(() => {
|
||||||
@@ -32,7 +34,6 @@ function RegistrationSuccessContent() {
|
|||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
// Try to fetch registration and event form
|
// 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;
|
const r = token ? await (await import('@/lib/api')).apiFetch(`/api/registrations/${encodeURIComponent(registrationId)}`, { authToken: token }) : null;
|
||||||
if (r) {
|
if (r) {
|
||||||
setReg(r);
|
setReg(r);
|
||||||
@@ -50,7 +51,7 @@ function RegistrationSuccessContent() {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, [registrationId]);
|
}, [registrationId, token]);
|
||||||
|
|
||||||
const totalDue = React.useMemo(() => {
|
const totalDue = React.useMemo(() => {
|
||||||
if (!reg) return 0;
|
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)
|
// attempt ticket generation if status is paid (may fail if required forms aren't completed)
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const token = (typeof window !== 'undefined') ? localStorage.getItem('token') : null;
|
|
||||||
if (token && reg?.id) {
|
if (token && reg?.id) {
|
||||||
await (await import('@/lib/api')).apiFetch('/api/tickets/generate', { method: 'POST', authToken: token, body: { registrationId: 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);
|
setTimeout(() => router.replace('/dashboard'), 600);
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
}, [reg, totalDue, router]);
|
}, [reg, totalDue, router, token]);
|
||||||
|
|
||||||
const goPay = async () => {
|
const goPay = async () => {
|
||||||
if (!registrationId) return;
|
if (!registrationId) return;
|
||||||
const token = (typeof window !== 'undefined') ? localStorage.getItem('token') : null;
|
|
||||||
if (!token) { setError('Please login to pay.'); return; }
|
if (!token) { setError('Please login to pay.'); return; }
|
||||||
try {
|
try {
|
||||||
setError(null);
|
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<number, Record<string,string>>; setFormsData: any; setError: any; setInfo: any; }) {
|
function AttendeeForms({ reg, form, formsData, setFormsData, setError, setInfo }: { reg: any; form: { isRequired: boolean; fields: FormField[] }; formsData: Record<number, Record<string,string>>; setFormsData: any; setError: any; setInfo: any; }) {
|
||||||
|
const { token } = useAuth();
|
||||||
const registrationId = reg?.id;
|
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 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 count = Math.max(0, mainTickets);
|
||||||
@@ -181,7 +181,6 @@ function AttendeeForms({ reg, form, formsData, setFormsData, setError, setInfo }
|
|||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
try {
|
try {
|
||||||
setError(null); setInfo(null);
|
setError(null); setInfo(null);
|
||||||
const token = (typeof window !== 'undefined') ? localStorage.getItem('token') : null;
|
|
||||||
if (!token) { setError('Please login to submit forms.'); return; }
|
if (!token) { setError('Please login to submit forms.'); return; }
|
||||||
const payload = [] as any[];
|
const payload = [] as any[];
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
@@ -201,7 +200,6 @@ function AttendeeForms({ reg, form, formsData, setFormsData, setError, setInfo }
|
|||||||
const saveDraft = async () => {
|
const saveDraft = async () => {
|
||||||
try {
|
try {
|
||||||
setError(null); setInfo(null);
|
setError(null); setInfo(null);
|
||||||
const token = (typeof window !== 'undefined') ? localStorage.getItem('token') : null;
|
|
||||||
if (!token) { setError('Please login to save drafts.'); return; }
|
if (!token) { setError('Please login to save drafts.'); return; }
|
||||||
await (await import('@/lib/api')).apiFetch(`/api/registrations/${encodeURIComponent(registrationId)}/forms/draft`, {
|
await (await import('@/lib/api')).apiFetch(`/api/registrations/${encodeURIComponent(registrationId)}/forms/draft`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
|
|||||||
Reference in New Issue
Block a user