From eb46ed82647908285606270cceefba553d0fabce Mon Sep 17 00:00:00 2001 From: joshua Date: Thu, 23 Jul 2026 16:43:33 +0200 Subject: [PATCH] Fix user dashboard: buried edit errors, closed events not hidden, closed/past registrations editable Registration-edit errors were set into a page-level error state rendered behind the edit modal overlay; they now render inside the modal. The "Show past events" toggle only hid date-based past events, letting cashup-closed events leak through by default; a shared isEventOver() check now covers both, applied to registrations, tickets, and the Edit button visibility (mirroring the backend's own edit block). Co-Authored-By: Claude Sonnet 5 --- CHANGELOG.md | 5 +++ .../src/controllers/registrationController.js | 2 +- frontend/src/app/dashboard/user/page.tsx | 35 ++++++++++--------- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cb9d58..bf0e723 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project follows [Semantic Versioning](https://semver.org/). ## [Unreleased] +### Fixed + +- User dashboard: registration-edit errors now show inside the edit popup instead of being hidden behind it. +- User dashboard: closed events are now hidden by default alongside past events (revealed via "Show past events"), and the Edit button no longer appears for closed or past registrations. + ## [1.0.0] - 2026-07-23 ### Added diff --git a/backend/src/controllers/registrationController.js b/backend/src/controllers/registrationController.js index 6a9ebcf..64116d9 100644 --- a/backend/src/controllers/registrationController.js +++ b/backend/src/controllers/registrationController.js @@ -415,7 +415,7 @@ const getUserRegistrations = async (req, res) => { const whereClause = showPast ? { userId: req.user.id } : { userId: req.user.id, status: { not: 'cancelled' }, - event: { endDate: { gte: now } } + event: { endDate: { gte: now }, cashupStatus: { not: 'closed' } } }; const registrations = await prisma.registration.findMany({ diff --git a/frontend/src/app/dashboard/user/page.tsx b/frontend/src/app/dashboard/user/page.tsx index 0b08938..8c54856 100644 --- a/frontend/src/app/dashboard/user/page.tsx +++ b/frontend/src/app/dashboard/user/page.tsx @@ -9,6 +9,8 @@ import { QrImage } from "@/components/shared/QrImage"; // Helper formatters const formatRand = (n: number) => `R ${n.toFixed(2)}`; const isFuture = (d: string | Date) => new Date(d).getTime() > Date.now(); +const isEventOver = (ev: any) => + !!ev && ((ev.endDate && new Date(ev.endDate).getTime() < Date.now()) || ev.cashupStatus === 'closed'); // Effective unit price for an event option (or one of its variants), early-bird aware. // Used by the registration editor, which works off raw /api/events/:id data rather than @@ -443,11 +445,11 @@ export default function UserDashboardPage() { const [editQuantities, setEditQuantities] = useState>({}); const [editBaseQty, setEditBaseQty] = useState>({}); const [editMinQty, setEditMinQty] = useState>({}); + const [editError, setEditError] = useState(null); const canEditActive = useMemo(() => { if (!activeReg) return false; - const end = activeReg.event?.endDate ? new Date(activeReg.event.endDate).getTime() : Infinity; - return end >= Date.now(); + return !isEventOver(activeReg.event); }, [activeReg]); const editTotal = useMemo(() => { @@ -464,6 +466,7 @@ export default function UserDashboardPage() { const beginEdit = async () => { if (!activeReg || !token) return; + setEditError(null); setEditLoading(true); try { // Load event options (with variants) for this event @@ -507,7 +510,7 @@ export default function UserDashboardPage() { setEditMinQty(minQtyMap); setEditMode(true); } catch (e) { - setError((e as any)?.message || 'Failed to load event options'); + setEditError((e as any)?.message || 'Failed to load event options'); } finally { setEditLoading(false); } @@ -515,6 +518,7 @@ export default function UserDashboardPage() { const saveEdit = async () => { if (!activeReg || !token) return; + setEditError(null); for (const [key, minQty] of Object.entries(editMinQty)) { if (minQty <= 0) continue; @@ -523,7 +527,7 @@ export default function UserDashboardPage() { const eo = editEventOptions.find((o: any) => o.id === eventOptionId); const variant = variantId ? (eo?.variants || []).find((v: any) => v.id === variantId) : null; const label = variant ? `${eo?.name || 'item'} (${variant.name})` : (eo?.name || 'item'); - setError(`Cannot reduce "${label}" below the ${minQty} already issued`); + setEditError(`Cannot reduce "${label}" below the ${minQty} already issued`); return; } } @@ -539,13 +543,13 @@ export default function UserDashboardPage() { } } if (payloadOptions.length === 0) { - setError('Please select at least one item'); + setEditError('Please select at least one item'); return; } // Enforce client-side: do not reduce below paid const paid = activeBill?.totalPaid || 0; if (editTotal < paid) { - setError('Cannot reduce items below the amount already paid'); + setEditError('Cannot reduce items below the amount already paid'); return; } setLoading(true); @@ -588,7 +592,7 @@ export default function UserDashboardPage() { setInfo('Registration updated successfully'); setEditMode(false); } catch (e: any) { - setError(e?.message || 'Failed to update registration'); + setEditError(e?.message || 'Failed to update registration'); } finally { setLoading(false); } @@ -635,11 +639,7 @@ export default function UserDashboardPage() { {registrations // Hide cancelled always; hide past unless showPast .filter((r: any) => r.status !== 'cancelled') - .filter((r: any) => { - if (showPast) return true; - const end = r.event?.endDate ? new Date(r.event.endDate).getTime() : Infinity; - return end >= Date.now(); - }) + .filter((r: any) => showPast || !isEventOver(r.event)) .map((r: any) => { const bill = billing[r.id]; const isCancelled = r.status === 'cancelled'; @@ -786,12 +786,12 @@ export default function UserDashboardPage() { )} - {tickets.filter(t => showPast || (t.event?.endDate ? new Date(t.event.endDate).getTime() >= Date.now() : true)).length === 0 ? ( + {tickets.filter(t => showPast || !isEventOver(t.event)).length === 0 ? (

No tickets yet.

) : (
    {tickets - .filter((t: any) => showPast || (t.event?.endDate ? new Date(t.event.endDate).getTime() >= Date.now() : true)) + .filter((t: any) => showPast || !isEventOver(t.event)) .map((t: any) => { const eventDate = t.event?.startDate ? new Date(t.event.startDate) : null; const optionName = t.registrationOption?.eventOption?.name || "Ticket"; @@ -827,11 +827,11 @@ export default function UserDashboardPage() { {activeReg && ( -
    setActiveRegId(null)}> +
    { setEditMode(false); setEditError(null); setShowCancelConfirm(false); setActiveRegId(null); }}>
    e.stopPropagation()}>

    {activeReg.event?.title || activeReg.eventId}

    - +
    @@ -870,6 +870,7 @@ export default function UserDashboardPage() {
) : (
+ {editError &&

{editError}

} {editLoading ? (
Loading options…
) : ( @@ -960,7 +961,7 @@ export default function UserDashboardPage() { })()}
- +