From e54456135cc8605fad949a955261cecc3f986355 Mon Sep 17 00:00:00 2001 From: Paul Nothaft Date: Sun, 10 May 2026 20:54:36 +0200 Subject: [PATCH] fix(events): admins can clear expiration on edit even when "Require expiration" is ON (#426) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit iSchumi6210 reported that with the global "Require expiration date" toggle ON, an admin couldn't clear the expiration on an existing event via the Edit Event form. The PUT returned 400 "Expiration date is required." The cause was intentional in the original code: the global setting was enforced on both create AND edit, so once flipped ON, no event could ever be cleared of its expiration — not even by admins editing one-by- one. Reproduced the exact scenario byte-for-byte against beta: Toggle ON → POST /admin/events {expiration_days: 30} → 200 created Toggle ON → PUT /admin/events/:id {expires_at: null} → 400 rejected The setting now controls only the create-time default. On edit, an admin can clear the field and the value persists as NULL ("never expires"). Matches CMS-style admin tool conventions where field- required-by-default doesn't lock the field after creation. Backend: drop the `getEventFieldRequirements()` enforcement on the expires_at branch in PUT /admin/events/:id. Empty/null on edit normalizes to NULL. Frontend: drop the matching `requireExpiration && !editForm.expires_at` toast in EventDetailsPage. The variable is no longer referenced, so remove its declaration too. Verified end-to-end with toggle ON: STEP 1: create with expiration → ok (unchanged) STEP 2: create without expiration → backend auto-applies default 30d (create-time enforcement intact) STEP 3: PUT {expires_at: null} on existing → "Event updated successfully" (was 400) STEP 4: DB column expires_at is NULL STEP 5: PUT {expires_at: ''} also accepted (matches what an HTML date input sends when cleared) Smoke 13/13 green; no regressions. --- backend/src/routes/adminEvents.js | 15 +++++++-------- frontend/src/pages/admin/EventDetailsPage.tsx | 10 +++++----- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/backend/src/routes/adminEvents.js b/backend/src/routes/adminEvents.js index adcf5557..ebd27507 100644 --- a/backend/src/routes/adminEvents.js +++ b/backend/src/routes/adminEvents.js @@ -1237,14 +1237,13 @@ router.put('/:id', adminAuth, requirePermission('events.edit'), requireEventOwne } // Enforce expires_at requirement based on app settings - if (Object.prototype.hasOwnProperty.call(updates, 'expires_at')) { - if (!updates.expires_at) { - const fieldReqs = await getEventFieldRequirements(); - if (fieldReqs.require_expiration) { - return res.status(400).json({ error: 'Expiration date is required.' }); - } - updates.expires_at = null; - } + // Allow admins to clear `expires_at` on edit even when the global + // `event_require_expiration` setting is ON (#426). The setting now + // controls only the create-time default — once an event exists, an + // admin editing it can override and remove the expiration. Empty / + // null values normalize to NULL in the column ("never expires"). + if (Object.prototype.hasOwnProperty.call(updates, 'expires_at') && !updates.expires_at) { + updates.expires_at = null; } // Format hero logo settings if provided diff --git a/frontend/src/pages/admin/EventDetailsPage.tsx b/frontend/src/pages/admin/EventDetailsPage.tsx index f29ec14e..fc00bb8c 100644 --- a/frontend/src/pages/admin/EventDetailsPage.tsx +++ b/frontend/src/pages/admin/EventDetailsPage.tsx @@ -459,7 +459,6 @@ export const EventDetailsPage: React.FC = () => { }, [showMediaFilter, photoFilters.media_type]); const { data: publicSettings } = usePublicSettings(); - const requireExpiration = publicSettings?.event_require_expiration !== false; const phoneFieldEnabled = publicSettings?.event_phone_field_enabled === true; // Fetch categories for the event @@ -686,10 +685,11 @@ export const EventDetailsPage: React.FC = () => { return; } - if (requireExpiration && !editForm.expires_at) { - toast.error(t('validation.expirationRequired', 'Expiration date is required.')); - return; - } + // No expiration-required validation on edit (#426). The global + // `event_require_expiration` setting only enforces a default at + // create-time — once an event exists, an admin can clear the + // expiration via this form. The matching backend gate was dropped + // in adminEvents.js. // Clean up the data - remove undefined values const updateData: any = {