diff --git a/backend/src/index.ts b/backend/src/index.ts index 02e238c..3d8c666 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -4,6 +4,8 @@ import sqlite3 from 'sqlite3'; import { open } from 'sqlite'; import dotenv from 'dotenv'; import path from 'path'; +import multer from 'multer'; +import fs from 'fs'; dotenv.config(); @@ -45,26 +47,62 @@ app.get('/api/events/:slug', async (req: Request, res: Response) => { if (rows.length === 0) { return res.status(404).json({ error: 'Event not found' }); } - res.json(rows[0]); + + // Parse needed_items from JSON string to array + const event = rows[0]; + try { + event.needed_items = event.needed_items ? JSON.parse(event.needed_items) : []; + } catch (e) { + console.error('Error parsing needed_items:', e); + event.needed_items = []; + } + + res.json(event); } catch (error) { console.error('Error fetching event:', error); res.status(500).json({ error: 'Internal server error' }); } }); -app.post('/api/events', async (req: Request, res: Response) => { +app.post('/api/events', multer().single('wallpaper'), async (req: Request, res: Response) => { try { const { title, description, date, location, needed_items } = req.body; + const wallpaperPath = req.file ? `/uploads/wallpapers/${req.file.filename}` : null; + // Generate a slug from the title const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, '-'); + // Ensure needed_items is properly formatted + let parsedNeededItems: string[] = []; + try { + if (typeof needed_items === 'string') { + parsedNeededItems = JSON.parse(needed_items); + } else if (Array.isArray(needed_items)) { + parsedNeededItems = needed_items; + } + } catch (e) { + console.error('Error parsing needed_items:', e); + } + const result = await db.run( - 'INSERT INTO events (title, description, date, location, slug, needed_items) VALUES (?, ?, ?, ?, ?, ?)', - [title, description, date, location, slug, JSON.stringify(needed_items || [])] + 'INSERT INTO events (title, description, date, location, slug, needed_items, wallpaper) VALUES (?, ?, ?, ?, ?, ?, ?)', + [title, description, date, location, slug, JSON.stringify(parsedNeededItems), wallpaperPath] ); - res.status(201).json({ ...result, slug }); + + res.status(201).json({ + ...result, + slug, + wallpaper: wallpaperPath, + needed_items: parsedNeededItems + }); } catch (error) { console.error('Error creating event:', error); + if (req.file) { + // Clean up uploaded file if there was an error + fs.unlink(req.file.path, (err) => { + if (err) console.error('Error deleting file:', err); + }); + } res.status(500).json({ error: 'Internal server error' }); } }); diff --git a/frontend/src/components/RSVPForm.tsx b/frontend/src/components/RSVPForm.tsx index 8620be6..7c592d6 100644 --- a/frontend/src/components/RSVPForm.tsx +++ b/frontend/src/components/RSVPForm.tsx @@ -18,6 +18,7 @@ import { OutlinedInput, Chip, } from '@mui/material'; +import { Event } from '../types'; interface RSVPFormData { name: string; @@ -49,23 +50,18 @@ const RSVPForm: React.FC = () => { const fetchEventDetails = async () => { try { const [eventResponse, rsvpsResponse] = await Promise.all([ - axios.get(`/api/events/${slug}`), + axios.get(`/api/events/${slug}`), axios.get(`/api/events/${slug}/rsvps`) ]); // Process needed items let items: string[] = []; if (eventResponse.data.needed_items) { - try { - items = typeof eventResponse.data.needed_items === 'string' + items = Array.isArray(eventResponse.data.needed_items) + ? eventResponse.data.needed_items + : typeof eventResponse.data.needed_items === 'string' ? JSON.parse(eventResponse.data.needed_items) - : Array.isArray(eventResponse.data.needed_items) - ? eventResponse.data.needed_items - : []; - } catch (e) { - console.error('Error parsing needed_items:', e); - items = []; - } + : []; } // Get all claimed items from existing RSVPs @@ -74,26 +70,18 @@ const RSVPForm: React.FC = () => { try { let rsvpItems: string[] = []; if (typeof rsvp.items_bringing === 'string') { - try { - const parsed = JSON.parse(rsvp.items_bringing); - rsvpItems = Array.isArray(parsed) ? parsed : []; - } catch (e) { - console.error('Error parsing items_bringing JSON:', e); - rsvpItems = []; - } + rsvpItems = JSON.parse(rsvp.items_bringing); } else if (Array.isArray(rsvp.items_bringing)) { rsvpItems = rsvp.items_bringing; } - if (Array.isArray(rsvpItems)) { - rsvpItems.forEach((item: string) => claimed.add(item)); - } + rsvpItems.forEach((item: string) => claimed.add(item)); } catch (e) { console.error('Error processing RSVP items:', e); } }); - // Filter out claimed items + // Filter out claimed items from available items const availableItems = items.filter(item => !claimed.has(item)); setNeededItems(availableItems); @@ -153,17 +141,11 @@ const RSVPForm: React.FC = () => { // Process needed items let items: string[] = []; if (eventResponse.data.needed_items) { - try { - if (typeof eventResponse.data.needed_items === 'string') { - const parsed = JSON.parse(eventResponse.data.needed_items); - items = Array.isArray(parsed) ? parsed : []; - } else if (Array.isArray(eventResponse.data.needed_items)) { - items = eventResponse.data.needed_items; - } - } catch (e) { - console.error('Error parsing needed_items:', e); - items = []; - } + items = Array.isArray(eventResponse.data.needed_items) + ? eventResponse.data.needed_items + : typeof eventResponse.data.needed_items === 'string' + ? JSON.parse(eventResponse.data.needed_items) + : []; } // Get all claimed items from existing RSVPs including the new submission @@ -179,12 +161,7 @@ const RSVPForm: React.FC = () => { try { let rsvpItems: string[] = []; if (typeof rsvp.items_bringing === 'string') { - try { - const parsed = JSON.parse(rsvp.items_bringing); - rsvpItems = Array.isArray(parsed) ? parsed : []; - } catch (e) { - console.error('Error parsing items_bringing JSON:', e); - } + rsvpItems = JSON.parse(rsvp.items_bringing); } else if (Array.isArray(rsvp.items_bringing)) { rsvpItems = rsvp.items_bringing; }