Prevent scheduling events in the past + full events-section audit
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 <assistant@shakespeare.diy>
This commit is contained in:
parent
fbb70983cd
commit
d577c8e455
1 changed files with 22 additions and 1 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useMemo } from 'react';
|
||||||
import { useSeoMeta } from '@unhead/react';
|
import { useSeoMeta } from '@unhead/react';
|
||||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import ngeohash from 'ngeohash';
|
import ngeohash from 'ngeohash';
|
||||||
|
|
@ -158,6 +158,14 @@ export default function CreateEventPage() {
|
||||||
return acc;
|
return acc;
|
||||||
}, {} as Record<string, SkateSpot[]>);
|
}, {} as Record<string, SkateSpot[]>);
|
||||||
|
|
||||||
|
// 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) => {
|
const toggleType = (type: string) => {
|
||||||
setSelectedTypes((prev) =>
|
setSelectedTypes((prev) =>
|
||||||
prev.includes(type) ? prev.filter((t) => t !== type) : [...prev, type]
|
prev.includes(type) ? prev.filter((t) => t !== type) : [...prev, type]
|
||||||
|
|
@ -272,6 +280,17 @@ export default function CreateEventPage() {
|
||||||
return;
|
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
|
// Parse end time if provided
|
||||||
let endTimestamp: number | undefined;
|
let endTimestamp: number | undefined;
|
||||||
if (endDate && endTime) {
|
if (endDate && endTime) {
|
||||||
|
|
@ -535,6 +554,7 @@ export default function CreateEventPage() {
|
||||||
<Input
|
<Input
|
||||||
type="date"
|
type="date"
|
||||||
value={startDate}
|
value={startDate}
|
||||||
|
min={todayStr}
|
||||||
onChange={(e) => setStartDate(e.target.value)}
|
onChange={(e) => setStartDate(e.target.value)}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
@ -554,6 +574,7 @@ export default function CreateEventPage() {
|
||||||
<Input
|
<Input
|
||||||
type="date"
|
type="date"
|
||||||
value={endDate}
|
value={endDate}
|
||||||
|
min={startDate || todayStr}
|
||||||
onChange={(e) => setEndDate(e.target.value)}
|
onChange={(e) => setEndDate(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue