Block self-service payment and cancellation on closed/past registrations

Extends the earlier user-dashboard fix: canEditActive is renamed
canModifyActive and now also gates the "Make payment" and "Cancel
registration" actions, not just editing. Backend enforcement added to
cancelRegistration and createYocoCheckout to block past-event
self-service payment/cancellation server-side (cashup-closed events
were already blocked via assertEventOpen; admins/supervisors are
exempt from the past-date check, consistent with existing overrides).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 16:51:10 +02:00
parent eb46ed8264
commit 349686e182
4 changed files with 21 additions and 8 deletions
+7 -5
View File
@@ -447,7 +447,9 @@ export default function UserDashboardPage() {
const [editMinQty, setEditMinQty] = useState<Record<string, number>>({});
const [editError, setEditError] = useState<string | null>(null);
const canEditActive = useMemo(() => {
// Whether the active registration's event still permits self-service changes
// (edit items, pay, cancel) — false once the event is past or its cashup is closed.
const canModifyActive = useMemo(() => {
if (!activeReg) return false;
return !isEventOver(activeReg.event);
}, [activeReg]);
@@ -847,7 +849,7 @@ export default function UserDashboardPage() {
<div>
<div className="font-medium mb-1 flex items-center justify-between">
<span>Registration items</span>
{!editMode && canEditActive && (
{!editMode && canModifyActive && (
<button disabled={editLoading} onClick={beginEdit} className="px-2.5 py-1 text-xs bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50">Edit</button>
)}
</div>
@@ -987,7 +989,7 @@ export default function UserDashboardPage() {
onClick={() => router.push(`/dashboard/user/forms?registrationId=${encodeURIComponent(activeReg.id)}`)}
>Attendee forms</button>
)}
{activeBill && activeBill.outstanding > 0 ? (
{canModifyActive && activeBill && activeBill.outstanding > 0 ? (
<button
className="px-3 py-1.5 text-sm bg-green-600 text-white rounded hover:bg-green-700 shadow-sm focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-1"
onClick={() => router.push(`/dashboard/user/pay?registrationId=${encodeURIComponent(activeReg.id)}`)}
@@ -1007,8 +1009,8 @@ export default function UserDashboardPage() {
)}
</div>
{/* Cancel registration — only shown when no payments have been made */}
{activeReg.status !== 'cancelled' && (activeBill?.totalPaid ?? 0) === 0 && (
{/* Cancel registration — only shown when no payments have been made and the event still permits changes */}
{canModifyActive && activeReg.status !== 'cancelled' && (activeBill?.totalPaid ?? 0) === 0 && (
<div className="pt-3 border-t mt-1">
{!showCancelConfirm ? (
<button