From e1d23eeb32d49ce516c48d74cea07ca1997c4807 Mon Sep 17 00:00:00 2001 From: Starstrike Date: Thu, 1 May 2025 13:44:29 -0400 Subject: [PATCH] Feature: Add RSVP cut-off date functionality --- backend/src/index.ts | 1 + frontend/src/components/EventAdmin.tsx | 48 +++++--- frontend/src/components/EventForm.tsx | 14 +++ frontend/src/components/EventList.tsx | 146 +++++++++++++------------ frontend/src/components/RSVPForm.tsx | 15 +++ frontend/src/types.ts | 1 + 6 files changed, 142 insertions(+), 83 deletions(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index 4f91fa5..3c87c40 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -387,6 +387,7 @@ async function initializeDatabase() { slug TEXT NOT NULL UNIQUE, needed_items TEXT, wallpaper TEXT, + rsvp_cutoff_date TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) `); diff --git a/frontend/src/components/EventAdmin.tsx b/frontend/src/components/EventAdmin.tsx index 2213e2e..48b1a38 100644 --- a/frontend/src/components/EventAdmin.tsx +++ b/frontend/src/components/EventAdmin.tsx @@ -54,6 +54,7 @@ interface Event { slug: string; needed_items?: string[] | string; wallpaper?: string; + rsvp_cutoff_date?: string; } const EventAdmin: React.FC = () => { @@ -85,7 +86,8 @@ const EventAdmin: React.FC = () => { const [updateForm, setUpdateForm] = useState({ description: '', location: '', - date: '' + date: '', + rsvp_cutoff_date: '' }); useEffect(() => { @@ -369,6 +371,18 @@ const EventAdmin: React.FC = () => { } }; + const handleUpdateInfoClick = () => { + if (!event) return; + + setUpdateForm({ + description: event.description, + location: event.location, + date: event.date.slice(0, 16), // Format date for datetime-local input + rsvp_cutoff_date: event.rsvp_cutoff_date ? event.rsvp_cutoff_date.slice(0, 16) : '' + }); + setUpdateInfoDialogOpen(true); + }; + const handleUpdateInfoSubmit = async () => { if (!event) return; @@ -377,14 +391,16 @@ const EventAdmin: React.FC = () => { ...event, description: updateForm.description, location: updateForm.location, - date: updateForm.date + date: updateForm.date, + rsvp_cutoff_date: updateForm.rsvp_cutoff_date }); setEvent(prev => prev ? { ...prev, description: updateForm.description, location: updateForm.location, - date: updateForm.date + date: updateForm.date, + rsvp_cutoff_date: updateForm.rsvp_cutoff_date } : null); setUpdateInfoDialogOpen(false); @@ -393,17 +409,6 @@ const EventAdmin: React.FC = () => { } }; - const handleUpdateInfoClick = () => { - if (!event) return; - - setUpdateForm({ - description: event.description, - location: event.location, - date: event.date.slice(0, 16) // Format date for datetime-local input - }); - setUpdateInfoDialogOpen(true); - }; - if (loading) { return ( @@ -487,6 +492,11 @@ const EventAdmin: React.FC = () => { Date: {new Date(event.date).toLocaleString()} + {event.rsvp_cutoff_date && ( + + RSVP cut-off date: {new Date(event.rsvp_cutoff_date).toLocaleString()} + + )} {/* Add items status section */} @@ -837,6 +847,16 @@ const EventAdmin: React.FC = () => { shrink: true, }} /> + setUpdateForm(prev => ({ ...prev, rsvp_cutoff_date: e.target.value }))} + fullWidth + InputLabelProps={{ + shrink: true, + }} + /> diff --git a/frontend/src/components/EventForm.tsx b/frontend/src/components/EventForm.tsx index 66707f5..6c0a55f 100644 --- a/frontend/src/components/EventForm.tsx +++ b/frontend/src/components/EventForm.tsx @@ -42,6 +42,7 @@ interface FormData { date: string; location: string; needed_items: string[]; + rsvp_cutoff_date: string; } const EventForm: React.FC = () => { @@ -52,6 +53,7 @@ const EventForm: React.FC = () => { date: '', location: '', needed_items: [], + rsvp_cutoff_date: '', }); const [wallpaper, setWallpaper] = useState(null); const [currentItem, setCurrentItem] = useState(''); @@ -184,6 +186,18 @@ const EventForm: React.FC = () => { shrink: true, }} /> + { @@ -40,14 +42,28 @@ const EventList: React.FC = () => { } }; - const handleEventClick = (event: Event) => { - navigate(`/rsvp/events/${event.slug}`); + const isEventOpen = (event: Event) => { + if (!event.rsvp_cutoff_date) return true; + const cutoffDate = new Date(event.rsvp_cutoff_date); + return new Date() < cutoffDate; }; - const handleAdminClick = (event: Event) => { + const handleEventClick = (event: Event) => { + if (isEventOpen(event)) { + navigate(`/rsvp/events/${event.slug}`); + } + }; + + const handleAdminClick = (event: Event, e: React.MouseEvent) => { + e.stopPropagation(); navigate(`/admin/events/${event.slug}`); }; + const handleViewClick = (event: Event, e: React.MouseEvent) => { + e.stopPropagation(); + navigate(`/view/events/${event.slug}`); + }; + return ( @@ -74,75 +90,67 @@ const EventList: React.FC = () => { - {events.length > 0 && ( - - - Current Events - - - {events.map((event) => ( - - handleEventClick(event)} - sx={{ - cursor: 'pointer', - '&:hover': { - boxShadow: 6, - } - }} - > - - + + + {events.map((event) => ( + + handleEventClick(event)} + > + + + {event.title} - - {new Date(event.date).toLocaleString(undefined, { - year: 'numeric', - month: 'numeric', - day: 'numeric', - hour: 'numeric', - minute: '2-digit', - hour12: true - })} at {event.location} + + + + {event.description} + + + Date: {new Date(event.date).toLocaleString()} + + + Location: {event.location} + + {event.rsvp_cutoff_date && ( + + RSVP cut-off date: {new Date(event.rsvp_cutoff_date).toLocaleString()} - - {event.description} - - - - - - - - - ))} - - - )} + )} + + + + + + + + ))} + + ); }; diff --git a/frontend/src/components/RSVPForm.tsx b/frontend/src/components/RSVPForm.tsx index c67d5ed..65ba12c 100644 --- a/frontend/src/components/RSVPForm.tsx +++ b/frontend/src/components/RSVPForm.tsx @@ -56,6 +56,15 @@ const RSVPForm: React.FC = () => { axios.get(`/api/events/${slug}/rsvps`) ]); + // Check if event is closed for RSVPs + if (eventResponse.data.rsvp_cutoff_date) { + const cutoffDate = new Date(eventResponse.data.rsvp_cutoff_date); + if (new Date() > cutoffDate) { + navigate(`/view/events/${slug}`); + return; + } + } + // Process needed items let items: string[] = []; if (eventResponse.data.needed_items) { @@ -291,6 +300,12 @@ const RSVPForm: React.FC = () => { )} + {event?.rsvp_cutoff_date && ( + + Note: RSVPs will close on {new Date(event.rsvp_cutoff_date).toLocaleString()} + + )} +