Add max guests per RSVP feature to event creation and RSVP forms

This commit is contained in:
Ryderjj89
2025-05-16 18:27:09 -04:00
parent 05e1e30384
commit efe143ca68
4 changed files with 116 additions and 32 deletions

View File

@@ -152,7 +152,7 @@ app.get('/api/events/:slug', async (req: Request, res: Response) => {
app.post('/api/events', upload.single('wallpaper'), async (req: MulterRequest, res: Response) => { app.post('/api/events', upload.single('wallpaper'), async (req: MulterRequest, res: Response) => {
try { try {
const { title, description, date, location, needed_items, rsvp_cutoff_date } = req.body; const { title, description, date, location, needed_items, rsvp_cutoff_date, max_guests_per_rsvp } = req.body;
const wallpaperPath = req.file ? `${req.file.filename}` : null; const wallpaperPath = req.file ? `${req.file.filename}` : null;
// Generate a slug from the title // Generate a slug from the title
@@ -170,9 +170,12 @@ app.post('/api/events', upload.single('wallpaper'), async (req: MulterRequest, r
console.error('Error parsing needed_items:', e); console.error('Error parsing needed_items:', e);
} }
// Parse max_guests_per_rsvp to ensure it's a number
const maxGuests = parseInt(max_guests_per_rsvp as string) || 0;
const result = await db.run( const result = await db.run(
'INSERT INTO events (title, description, date, location, slug, needed_items, wallpaper, rsvp_cutoff_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', 'INSERT INTO events (title, description, date, location, slug, needed_items, wallpaper, rsvp_cutoff_date, max_guests_per_rsvp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
[title, description, date, location, slug, JSON.stringify(parsedNeededItems), wallpaperPath, rsvp_cutoff_date] [title, description, date, location, slug, JSON.stringify(parsedNeededItems), wallpaperPath, rsvp_cutoff_date, maxGuests]
); );
res.status(201).json({ res.status(201).json({
@@ -180,7 +183,8 @@ app.post('/api/events', upload.single('wallpaper'), async (req: MulterRequest, r
slug, slug,
wallpaper: wallpaperPath ? `/uploads/wallpapers/${wallpaperPath}` : null, wallpaper: wallpaperPath ? `/uploads/wallpapers/${wallpaperPath}` : null,
needed_items: parsedNeededItems, needed_items: parsedNeededItems,
rsvp_cutoff_date rsvp_cutoff_date,
max_guests_per_rsvp: maxGuests
}); });
} catch (error) { } catch (error) {
console.error('Error creating event:', error); console.error('Error creating event:', error);
@@ -448,7 +452,7 @@ app.put('/api/events/:slug/rsvps/:id', async (req: Request, res: Response) => {
app.put('/api/events/:slug', upload.single('wallpaper'), async (req: MulterRequest, res: Response) => { app.put('/api/events/:slug', upload.single('wallpaper'), async (req: MulterRequest, res: Response) => {
try { try {
const { slug } = req.params; const { slug } = req.params;
const { title, description, date, location, needed_items, rsvp_cutoff_date } = req.body; const { title, description, date, location, needed_items, rsvp_cutoff_date, max_guests_per_rsvp } = req.body;
// Verify the event exists // Verify the event exists
const eventRows = await db.all('SELECT * FROM events WHERE slug = ?', [slug]); const eventRows = await db.all('SELECT * FROM events WHERE slug = ?', [slug]);
@@ -469,6 +473,11 @@ app.put('/api/events/:slug', upload.single('wallpaper'), async (req: MulterReque
console.error('Error parsing needed_items:', e); console.error('Error parsing needed_items:', e);
} }
// Parse max_guests_per_rsvp to ensure it's a number
const maxGuests = max_guests_per_rsvp !== undefined ?
(parseInt(max_guests_per_rsvp as string) || 0) :
eventRows[0].max_guests_per_rsvp || 0;
// Handle wallpaper update // Handle wallpaper update
let wallpaperPath = eventRows[0].wallpaper; let wallpaperPath = eventRows[0].wallpaper;
if (req.file) { if (req.file) {
@@ -486,7 +495,7 @@ app.put('/api/events/:slug', upload.single('wallpaper'), async (req: MulterReque
// Update the event // Update the event
await db.run( await db.run(
'UPDATE events SET title = ?, description = ?, date = ?, location = ?, needed_items = ?, rsvp_cutoff_date = ?, wallpaper = ? WHERE slug = ?', 'UPDATE events SET title = ?, description = ?, date = ?, location = ?, needed_items = ?, rsvp_cutoff_date = ?, wallpaper = ?, max_guests_per_rsvp = ? WHERE slug = ?',
[ [
title ?? eventRows[0].title, title ?? eventRows[0].title,
description === undefined ? eventRows[0].description : description, description === undefined ? eventRows[0].description : description,
@@ -495,6 +504,7 @@ app.put('/api/events/:slug', upload.single('wallpaper'), async (req: MulterReque
JSON.stringify(parsedNeededItems), JSON.stringify(parsedNeededItems),
rsvp_cutoff_date !== undefined ? rsvp_cutoff_date : eventRows[0].rsvp_cutoff_date, rsvp_cutoff_date !== undefined ? rsvp_cutoff_date : eventRows[0].rsvp_cutoff_date,
wallpaperPath, wallpaperPath,
maxGuests,
slug slug
] ]
); );
@@ -543,6 +553,7 @@ async function initializeDatabase() {
needed_items TEXT, needed_items TEXT,
wallpaper TEXT, wallpaper TEXT,
rsvp_cutoff_date TEXT, rsvp_cutoff_date TEXT,
max_guests_per_rsvp INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) )
`); `);

View File

@@ -43,6 +43,7 @@ interface FormData {
location: string; location: string;
needed_items: string[]; needed_items: string[];
rsvp_cutoff_date: string; rsvp_cutoff_date: string;
max_guests_per_rsvp: number;
} }
const EventForm: React.FC = () => { const EventForm: React.FC = () => {
@@ -54,6 +55,7 @@ const EventForm: React.FC = () => {
location: '', location: '',
needed_items: [], needed_items: [],
rsvp_cutoff_date: '', rsvp_cutoff_date: '',
max_guests_per_rsvp: 0,
}); });
const [wallpaper, setWallpaper] = useState<File | null>(null); const [wallpaper, setWallpaper] = useState<File | null>(null);
const [currentItem, setCurrentItem] = useState(''); const [currentItem, setCurrentItem] = useState('');
@@ -198,6 +200,24 @@ const EventForm: React.FC = () => {
shrink: true, shrink: true,
}} }}
/> />
<DarkTextField
fullWidth
label="Maximum Additional Guests Per RSVP"
name="max_guests_per_rsvp"
type="number"
value={formData.max_guests_per_rsvp}
onChange={(e) => {
const value = parseInt(e.target.value);
setFormData((prev) => ({
...prev,
max_guests_per_rsvp: isNaN(value) ? 0 : value,
}));
}}
variant="outlined"
helperText="Set to 0 for no additional guests, -1 for unlimited"
inputProps={{ min: -1 }}
/>
<DarkTextField <DarkTextField
fullWidth fullWidth
label="Location" label="Location"

View File

@@ -199,15 +199,35 @@ const RSVPForm: React.FC = () => {
other_items: '' other_items: ''
})); }));
} else if (name === 'bringing_guests') { } else if (name === 'bringing_guests') {
// When bringing guests is changed // When bringing guests is changed
setFormData(prev => ({ setFormData(prev => {
...prev, const maxGuests = event?.max_guests_per_rsvp;
bringing_guests: value as 'yes' | 'no', let initialGuestCount = 1;
// If changing to 'yes', set guest count to 1 and initialize one empty name field
guest_count: value === 'yes' ? 1 : 0, // If max_guests_per_rsvp is 0, don't allow guests
// Clear guest names if changing to 'no', otherwise initialize with empty string if (maxGuests === 0 && value === 'yes') {
guest_names: value === 'no' ? [] : [''] return {
})); ...prev,
bringing_guests: 'no',
guest_count: 0,
guest_names: []
};
}
// If max_guests_per_rsvp is set and not -1 (unlimited), limit initial count
if (maxGuests !== undefined && maxGuests !== -1 && maxGuests < initialGuestCount) {
initialGuestCount = maxGuests;
}
return {
...prev,
bringing_guests: value as 'yes' | 'no',
// If changing to 'yes', set guest count to appropriate value
guest_count: value === 'yes' ? initialGuestCount : 0,
// Clear guest names if changing to 'no', otherwise initialize with empty strings
guest_names: value === 'no' ? [] : Array(initialGuestCount).fill('')
};
});
} else { } else {
setFormData(prev => ({ setFormData(prev => ({
...prev, ...prev,
@@ -465,19 +485,51 @@ const RSVPForm: React.FC = () => {
{formData.bringing_guests === 'yes' && ( {formData.bringing_guests === 'yes' && (
<> <>
<TextField <TextField
label="Number of Guests" label="Number of Guests"
name="guest_count" name="guest_count"
type="number" type="number"
value={formData.guest_count} value={formData.guest_count}
onChange={handleChange} onChange={(e) => {
fullWidth const value = parseInt(e.target.value);
variant="outlined" if (isNaN(value)) return;
required
inputProps={{ min: 1 }} // Check if there's a maximum guest limit
error={formData.guest_count < 1} const maxGuests = event?.max_guests_per_rsvp;
helperText={formData.guest_count < 1 ? "Number of guests must be at least 1" : ""} let newCount = value;
/>
// If max_guests_per_rsvp is set and not -1 (unlimited), enforce the limit
if (maxGuests !== undefined && maxGuests !== -1 && value > maxGuests) {
newCount = maxGuests;
}
// Ensure count is at least 1
if (newCount < 1) newCount = 1;
setFormData(prev => ({
...prev,
guest_count: newCount,
guest_names: Array(newCount).fill('').map((_, i) => prev.guest_names[i] || '')
}));
}}
fullWidth
variant="outlined"
required
inputProps={{
min: 1,
max: event?.max_guests_per_rsvp === -1 ? undefined : event?.max_guests_per_rsvp
}}
error={formData.guest_count < 1}
helperText={
formData.guest_count < 1
? "Number of guests must be at least 1"
: event?.max_guests_per_rsvp === 0
? "No additional guests allowed for this event"
: event?.max_guests_per_rsvp === -1
? "No limit on number of guests"
: `Maximum ${event?.max_guests_per_rsvp} additional guests allowed`
}
/>
{Array.from({ length: formData.guest_count }).map((_, index) => ( {Array.from({ length: formData.guest_count }).map((_, index) => (
<TextField <TextField

View File

@@ -9,6 +9,7 @@ export interface Event {
needed_items: string[]; needed_items: string[];
wallpaper?: string; wallpaper?: string;
rsvp_cutoff_date?: string; rsvp_cutoff_date?: string;
max_guests_per_rsvp?: number;
} }
export interface Rsvp { export interface Rsvp {