From d577c8e45596187ef2345956a24475bf90817766 Mon Sep 17 00:00:00 2001 From: "shakespeare.diy" Date: Thu, 3 Sep 2026 15:02:25 +0700 Subject: [PATCH] Prevent scheduling events in the past + full events-section audit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audited the whole events flow end-to-end (create, hosting dashboard, discovery grid, detail, RSVP, sponsors) and confirmed the wiring is coherent: - Create → parse → display round-trips correctly for sponsors, tour/spot/crew/ musician links, entry fee, and featured listings - Sponsor parser safely skips shop-coordinate `sponsor` tags (no name at pos 2), so events and tour/shop sponsorships never collide - Batch RSVP counts feed the grid; hosting dashboard + detail keep per-item counts Fix surfaced by the audit: CreateEventPage let a host pick a start time in the past, silently producing a dead event in the Past tab. Added a `min={today}` on the date pickers and a submit guard (10-min grace) with a clear message. Co-authored-by: shakespeare.diy --- src/pages/CreateEventPage.tsx | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/pages/CreateEventPage.tsx b/src/pages/CreateEventPage.tsx index 2bd27bd..054bfee 100644 --- a/src/pages/CreateEventPage.tsx +++ b/src/pages/CreateEventPage.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useMemo } from 'react'; import { useSeoMeta } from '@unhead/react'; import { useNavigate, useSearchParams } from 'react-router-dom'; import ngeohash from 'ngeohash'; @@ -158,6 +158,14 @@ export default function CreateEventPage() { return acc; }, {} as Record); + // Today (local) as YYYY-MM-DD, used as the min for the date pickers so hosts + // can't accidentally schedule an event in the past. + const todayStr = useMemo(() => { + const d = new Date(); + const off = d.getTimezoneOffset(); + return new Date(d.getTime() - off * 60_000).toISOString().split('T')[0]; + }, []); + const toggleType = (type: string) => { setSelectedTypes((prev) => prev.includes(type) ? prev.filter((t) => t !== type) : [...prev, type] @@ -272,6 +280,17 @@ export default function CreateEventPage() { return; } + // Guard against scheduling in the past (a 10-min grace window covers + // "starting right now" without rejecting it). + if (startTimestamp < Math.floor(Date.now() / 1000) - 600) { + toast({ + title: 'That start time is in the past', + description: 'Pick a date and time in the future so skaters can RSVP and show up.', + variant: 'destructive', + }); + return; + } + // Parse end time if provided let endTimestamp: number | undefined; if (endDate && endTime) { @@ -535,6 +554,7 @@ export default function CreateEventPage() { setStartDate(e.target.value)} required /> @@ -554,6 +574,7 @@ export default function CreateEventPage() { setEndDate(e.target.value)} />