Add max guests per RSVP feature to event creation and RSVP forms
This commit is contained in:
@@ -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
|
||||||
)
|
)
|
||||||
`);
|
`);
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user