diff --git a/backend/src/index.ts b/backend/src/index.ts index a89fa00..6eb4e63 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -54,13 +54,13 @@ app.get('/api/events/:slug', async (req: Request, res: Response) => { app.post('/api/events', async (req: Request, res: Response) => { try { - const { title, description, date, location } = req.body; + const { title, description, date, location, needed_items } = req.body; // Generate a slug from the title const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, '-'); const result = await db.run( - 'INSERT INTO events (title, description, date, location, slug) VALUES (?, ?, ?, ?, ?)', - [title, description, date, location, slug] + 'INSERT INTO events (title, description, date, location, slug, needed_items) VALUES (?, ?, ?, ?, ?, ?)', + [title, description, date, location, slug, JSON.stringify(needed_items || [])] ); res.status(201).json({ ...result, slug }); } catch (error) { @@ -102,7 +102,7 @@ app.post('/api/events/:slug/rsvp', async (req: Request, res: Response) => { const eventId = eventRows[0].id; const result = await db.run( 'INSERT INTO rsvps (event_id, name, attending, bringing_guests, guest_count, guest_names, items_bringing) VALUES (?, ?, ?, ?, ?, ?, ?)', - [eventId, name, attending, bringing_guests, guest_count, guest_names, items_bringing] + [eventId, name, attending, bringing_guests, guest_count, guest_names, JSON.stringify(items_bringing || [])] ); res.status(201).json(result); } catch (error) { @@ -147,7 +147,7 @@ app.put('/api/events/:slug/rsvps/:id', async (req: Request, res: Response) => { const eventId = eventRows[0].id; await db.run( 'UPDATE rsvps SET name = ?, attending = ?, bringing_guests = ?, guest_count = ?, guest_names = ?, items_bringing = ? WHERE id = ? AND event_id = ?', - [name, attending, bringing_guests, guest_count, guest_names, items_bringing, id, eventId] + [name, attending, bringing_guests, guest_count, guest_names, JSON.stringify(items_bringing || []), id, eventId] ); res.status(200).json({ message: 'RSVP updated successfully' }); } catch (error) { @@ -167,6 +167,7 @@ async function initializeDatabase() { date TEXT NOT NULL, location TEXT, slug TEXT NOT NULL UNIQUE, + needed_items TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) `); diff --git a/frontend/src/components/EventAdmin.tsx b/frontend/src/components/EventAdmin.tsx index 04fe115..605f7d8 100644 --- a/frontend/src/components/EventAdmin.tsx +++ b/frontend/src/components/EventAdmin.tsx @@ -37,7 +37,7 @@ interface RSVP { bringing_guests: string; guest_count: number; guest_names: string; - items_bringing: string; + items_bringing: string[]; } interface Event { @@ -206,7 +206,7 @@ const EventAdmin: React.FC = () => { } - {rsvp.items_bringing.split('\n').map(item => item.trim()).filter(Boolean).join(', ')} + {rsvp.items_bringing.join(', ')} { const { slug } = useParams<{ slug: string }>(); @@ -31,6 +43,21 @@ const EventDetails: React.FC = () => { fetchEventDetails(); }, [slug]); + const formatAttendingStatus = (attending: string) => { + return attending.charAt(0).toUpperCase() + attending.slice(1); + }; + + const getStatusColor = (attending: string): "success" | "error" | "warning" => { + switch (attending.toLowerCase()) { + case 'yes': + return 'success'; + case 'no': + return 'error'; + default: + return 'warning'; + } + }; + if (loading) { return ( { ); } - const formatStatus = (status: string) => { - switch (status.toLowerCase()) { - case 'attending': - return 'Yes'; - case 'not_attending': - return 'No'; - case 'maybe': - return 'Maybe'; - default: - return status.charAt(0).toUpperCase() + status.slice(1); - } - }; - return ( - - - + + + {event.title} + + + {event.description} + + + + Date: {new Date(event.date).toLocaleDateString()} + + + Location: {event.location} + + {event.needed_items && event.needed_items.length > 0 && ( + + + Needed Items: + + + {event.needed_items.map((item, index) => ( + + ))} + + + )} + + + + + + - - {event.title} - - - - {event.date} at {event.location} - - - - {event.description} - - - - RSVPs - - - {rsvps.map((rsvp) => ( - - - {rsvp.name} - - } - secondary={ - - {rsvp.email} - {formatStatus(rsvp.status)} - - } - /> - - ))} - - - - - - - - + + + {rsvp.name} + + + + } + secondary={ + + {rsvp.bringing_guests === 'yes' && ( + + Bringing {rsvp.guest_count} guest{rsvp.guest_count !== 1 ? 's' : ''}: {rsvp.guest_names} + + )} + {rsvp.items_bringing && ( + + Items: {rsvp.items_bringing} + + )} + + } + /> + + ))} + {rsvps.length === 0 && ( + + + No RSVPs yet + + } + /> + + )} + + + ); }; diff --git a/frontend/src/components/EventForm.tsx b/frontend/src/components/EventForm.tsx index d16df32..708a89c 100644 --- a/frontend/src/components/EventForm.tsx +++ b/frontend/src/components/EventForm.tsx @@ -7,6 +7,7 @@ import { Typography, Container, Paper, + Chip, } from '@mui/material'; import axios from 'axios'; @@ -17,7 +18,9 @@ const EventForm: React.FC = () => { description: '', date: '', location: '', + needed_items: [] as string[], }); + const [currentItem, setCurrentItem] = useState(''); const [error, setError] = useState(null); const handleChange = (e: React.ChangeEvent) => { @@ -28,6 +31,27 @@ const EventForm: React.FC = () => { })); }; + const handleItemChange = (e: React.ChangeEvent) => { + setCurrentItem(e.target.value); + }; + + const handleAddItem = () => { + if (currentItem.trim()) { + setFormData((prev) => ({ + ...prev, + needed_items: [...prev.needed_items, currentItem.trim()], + })); + setCurrentItem(''); + } + }; + + const handleRemoveItem = (index: number) => { + setFormData((prev) => ({ + ...prev, + needed_items: prev.needed_items.filter((_, i) => i !== index), + })); + }; + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(null); @@ -95,6 +119,36 @@ const EventForm: React.FC = () => { variant="outlined" required /> + + Needed Items + + + + + + {formData.needed_items.map((item, index) => ( + handleRemoveItem(index)} + color="primary" + /> + ))} + +