Compare commits
27 Commits
7b3f8f74a8
...
53340fc210
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53340fc210 | ||
|
|
bd8f0fa2cd | ||
|
|
ecd53fbadb | ||
|
|
630cf7be41 | ||
|
|
9760eccf4c | ||
|
|
54d0e8aab4 | ||
|
|
5d67d42205 | ||
|
|
becc530c18 | ||
|
|
2587a5663b | ||
|
|
3af12b431b | ||
|
|
f366a9e4f3 | ||
|
|
044b7f2cde | ||
|
|
88c06e06f6 | ||
|
|
e0a3f3f889 | ||
|
|
aa69252657 | ||
|
|
01939f5881 | ||
|
|
2b6f4cfd2f | ||
|
|
16db01292c | ||
|
|
52cacc8646 | ||
|
|
4546185a89 | ||
|
|
4722aeeb22 | ||
|
|
4e66ce876d | ||
|
|
482718050d | ||
|
|
b5ecb32893 | ||
|
|
4d4920a751 | ||
|
|
3521643196 | ||
|
|
64db6c4c08 |
@@ -729,7 +729,7 @@ const EventAdmin: React.FC = () => {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Container maxWidth="lg">
|
||||
<Container maxWidth="xl">
|
||||
<Typography>Loading...</Typography>
|
||||
</Container>
|
||||
);
|
||||
@@ -737,7 +737,7 @@ const EventAdmin: React.FC = () => {
|
||||
|
||||
if (error || !event) {
|
||||
return (
|
||||
<Container maxWidth="lg">
|
||||
<Container maxWidth="xl">
|
||||
<Typography color="error">{error || 'Event not found'}</Typography>
|
||||
</Container>
|
||||
);
|
||||
@@ -763,7 +763,7 @@ const EventAdmin: React.FC = () => {
|
||||
}}
|
||||
>
|
||||
<Box sx={{ py: 4 }}>
|
||||
<Container maxWidth="lg">
|
||||
<Container maxWidth="xl">
|
||||
<Paper elevation={3} sx={{ p: { xs: 2, sm: 4 }, mt: 4 }}>
|
||||
<Box sx={{ mb: 4 }}>
|
||||
<Typography variant="h4" component="h2" color="primary" gutterBottom>
|
||||
@@ -826,16 +826,16 @@ const EventAdmin: React.FC = () => {
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
<strong>Info:</strong> {event.description || 'None'}
|
||||
</Typography>
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
<strong>Location:</strong> {event.location}
|
||||
</Typography>
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
<strong>Date:</strong> {new Date(event.date).toLocaleString()}
|
||||
</Typography>
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
<strong>Location:</strong> {event.location}
|
||||
</Typography>
|
||||
{event.rsvp_cutoff_date && (
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
<strong>RSVP cut-off date:</strong> {new Date(event.rsvp_cutoff_date).toLocaleString()}
|
||||
</Typography>
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
<strong>RSVP cut-off date:</strong> {new Date(event.rsvp_cutoff_date).toLocaleString()}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -945,9 +945,9 @@ const EventAdmin: React.FC = () => {
|
||||
<TableCell>
|
||||
{rsvp.bringing_guests === 'yes' ?
|
||||
`${rsvp.guest_count || 0} (${Array.isArray(rsvp.guest_names) ?
|
||||
rsvp.guest_names.join(', ') :
|
||||
rsvp.guest_names.map(name => name.trim()).join(', ') :
|
||||
typeof rsvp.guest_names === 'string' ?
|
||||
rsvp.guest_names.replace(/\s+/g, ', ') :
|
||||
rsvp.guest_names.replace(/\s+/g, ', ').trim() :
|
||||
'No names provided'})` :
|
||||
'No'
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ const EventView: React.FC = () => {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Container maxWidth="lg">
|
||||
<Container maxWidth="xl">
|
||||
<Typography>Loading...</Typography>
|
||||
</Container>
|
||||
);
|
||||
@@ -134,7 +134,7 @@ const EventView: React.FC = () => {
|
||||
|
||||
if (error || !event) {
|
||||
return (
|
||||
<Container maxWidth="lg">
|
||||
<Container maxWidth="xl">
|
||||
<Typography color="error">{error || 'Event not found'}</Typography>
|
||||
</Container>
|
||||
);
|
||||
@@ -160,7 +160,7 @@ const EventView: React.FC = () => {
|
||||
}}
|
||||
>
|
||||
<Box sx={{ py: 4 }}>
|
||||
<Container maxWidth="lg">
|
||||
<Container maxWidth="xl">
|
||||
<Paper elevation={3} sx={{ p: { xs: 2, sm: 4 }, mt: 4 }}>
|
||||
<Box sx={{ mb: 4 }}>
|
||||
<Typography variant="h4" component="h2" color="primary" gutterBottom>
|
||||
@@ -189,16 +189,16 @@ const EventView: React.FC = () => {
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
<strong>Info:</strong> {event.description || 'None'}
|
||||
</Typography>
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
<strong>Location:</strong> {event.location}
|
||||
</Typography>
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
<strong>Date:</strong> {new Date(event.date).toLocaleString()}
|
||||
</Typography>
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
<strong>Location:</strong> {event.location}
|
||||
</Typography>
|
||||
{event.rsvp_cutoff_date && (
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
<strong>RSVP cut-off date:</strong> {new Date(event.rsvp_cutoff_date).toLocaleString()}
|
||||
</Typography>
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
<strong>RSVP cut-off date:</strong> {new Date(event.rsvp_cutoff_date).toLocaleString()}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -293,9 +293,9 @@ const EventView: React.FC = () => {
|
||||
<TableCell>
|
||||
{rsvp.bringing_guests === 'yes' ?
|
||||
`${rsvp.guest_count} (${Array.isArray(rsvp.guest_names) ?
|
||||
rsvp.guest_names.join(', ') :
|
||||
rsvp.guest_names.map(name => name.trim()).join(', ') :
|
||||
typeof rsvp.guest_names === 'string' ?
|
||||
rsvp.guest_names.replace(/\s+/g, ', ') :
|
||||
rsvp.guest_names.replace(/\s+/g, ', ').trim() :
|
||||
'No names provided'})` :
|
||||
'No'
|
||||
}
|
||||
@@ -350,4 +350,4 @@ const EventView: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default EventView;
|
||||
export default EventView;
|
||||
|
||||
@@ -399,19 +399,19 @@ const RSVPForm: React.FC = () => {
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', gap: 2, justifyContent: 'center' }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={() => navigate('/')}
|
||||
>
|
||||
Back to Events
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => navigate('/')}
|
||||
onClick={() => navigate(`/view/events/${slug}`)}
|
||||
sx={{ flexGrow: 1 }}
|
||||
>
|
||||
Back to Events
|
||||
View RSVPs
|
||||
</Button>
|
||||
</Box>
|
||||
</Paper>
|
||||
@@ -538,51 +538,69 @@ const RSVPForm: React.FC = () => {
|
||||
|
||||
{formData.bringing_guests === 'yes' && (
|
||||
<>
|
||||
<TextField
|
||||
label="Number of Guests"
|
||||
name="guest_count"
|
||||
type="number"
|
||||
value={formData.guest_count}
|
||||
onChange={(e) => {
|
||||
const value = parseInt(e.target.value);
|
||||
if (isNaN(value)) return;
|
||||
|
||||
// Check if there's a maximum guest limit
|
||||
const maxGuests = event?.max_guests_per_rsvp;
|
||||
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`
|
||||
}
|
||||
/>
|
||||
{/* Render dropdown if there's a max limit, otherwise render number input */}
|
||||
{event?.max_guests_per_rsvp !== undefined && event?.max_guests_per_rsvp !== -1 ? (
|
||||
<FormControl fullWidth required>
|
||||
<InputLabel>Number of Guests</InputLabel>
|
||||
<Select
|
||||
name="guest_count"
|
||||
value={formData.guest_count.toString()}
|
||||
onChange={(e) => {
|
||||
const value = parseInt(e.target.value);
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
guest_count: value,
|
||||
guest_names: Array(value).fill('').map((_, i) => prev.guest_names[i] || '')
|
||||
}));
|
||||
}}
|
||||
label="Number of Guests"
|
||||
required
|
||||
>
|
||||
{Array.from({ length: event.max_guests_per_rsvp }, (_, i) => i + 1).map((num) => (
|
||||
<MenuItem key={num} value={num.toString()}>
|
||||
{num}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ mt: 0.5, ml: 1.75 }}>
|
||||
Maximum {event.max_guests_per_rsvp} additional guests allowed
|
||||
</Typography>
|
||||
</FormControl>
|
||||
) : (
|
||||
<TextField
|
||||
label="Number of Guests"
|
||||
name="guest_count"
|
||||
value={formData.guest_count}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
// Only allow numbers
|
||||
if (!/^\d*$/.test(value)) return;
|
||||
|
||||
const numValue = parseInt(value) || 0;
|
||||
// Ensure count is at least 1 when not empty
|
||||
const newCount = value === '' ? 0 : Math.max(1, numValue);
|
||||
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
guest_count: newCount,
|
||||
guest_names: Array(newCount).fill('').map((_, i) => prev.guest_names[i] || '')
|
||||
}));
|
||||
}}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
required
|
||||
inputProps={{
|
||||
inputMode: 'numeric',
|
||||
pattern: '[0-9]*'
|
||||
}}
|
||||
error={formData.guest_count < 1}
|
||||
helperText={
|
||||
formData.guest_count < 1
|
||||
? "Number of guests must be at least 1"
|
||||
: "No limit on number of guests"
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{Array.from({ length: formData.guest_count }).map((_, index) => (
|
||||
<TextField
|
||||
@@ -599,65 +617,83 @@ const RSVPForm: React.FC = () => {
|
||||
)}
|
||||
|
||||
{neededItems.length > 0 && (
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>What items are you bringing?</InputLabel>
|
||||
<Select
|
||||
multiple
|
||||
name="items_bringing"
|
||||
value={formData.items_bringing}
|
||||
onChange={handleItemsChange}
|
||||
input={<OutlinedInput label="What items are you bringing?" />}
|
||||
renderValue={(selected) => (
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
|
||||
{selected.map((value) => (
|
||||
<Chip key={value} label={value} />
|
||||
<>
|
||||
{claimedItems.length > 0 && (
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant="subtitle2" gutterBottom sx={{ fontWeight: 500 }}>
|
||||
Items already being brought:
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
|
||||
{claimedItems.map((item) => (
|
||||
<Chip
|
||||
key={item}
|
||||
label={item}
|
||||
color="success"
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
open={isItemsSelectOpen} // Control open state
|
||||
onOpen={() => setIsItemsSelectOpen(true)} // Set open when opened
|
||||
onClose={() => setIsItemsSelectOpen(false)} // Set closed when closed
|
||||
MenuProps={{
|
||||
PaperProps: {
|
||||
sx: {
|
||||
maxHeight: 300, // Limit height of the dropdown
|
||||
overflowY: 'auto',
|
||||
},
|
||||
},
|
||||
MenuListProps: {
|
||||
sx: {
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{neededItems.map((item) => (
|
||||
<MenuItem key={item} value={item}>
|
||||
<Checkbox checked={formData.items_bringing.includes(item)} />
|
||||
<ListItemText primary={item} />
|
||||
</MenuItem>
|
||||
))}
|
||||
<Box sx={{
|
||||
position: 'sticky',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
backgroundColor: 'background.paper',
|
||||
padding: 1,
|
||||
zIndex: 1,
|
||||
borderTop: '1px solid',
|
||||
borderColor: 'divider',
|
||||
textAlign: 'center',
|
||||
}}>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => setIsItemsSelectOpen(false)}
|
||||
fullWidth
|
||||
>
|
||||
Done
|
||||
</Button>
|
||||
</Box>
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>What items are you bringing?</InputLabel>
|
||||
<Select
|
||||
multiple
|
||||
name="items_bringing"
|
||||
value={formData.items_bringing}
|
||||
onChange={handleItemsChange}
|
||||
input={<OutlinedInput label="What items are you bringing?" />}
|
||||
renderValue={(selected) => (
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
|
||||
{selected.map((value) => (
|
||||
<Chip key={value} label={value} />
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
open={isItemsSelectOpen} // Control open state
|
||||
onOpen={() => setIsItemsSelectOpen(true)} // Set open when opened
|
||||
onClose={() => setIsItemsSelectOpen(false)} // Set closed when closed
|
||||
MenuProps={{
|
||||
PaperProps: {
|
||||
sx: {
|
||||
maxHeight: 300, // Limit height of the dropdown
|
||||
overflowY: 'auto',
|
||||
},
|
||||
},
|
||||
MenuListProps: {
|
||||
sx: {
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{neededItems.map((item) => (
|
||||
<MenuItem key={item} value={item}>
|
||||
<Checkbox checked={formData.items_bringing.includes(item)} />
|
||||
<ListItemText primary={item} />
|
||||
</MenuItem>
|
||||
))}
|
||||
<Box sx={{
|
||||
position: 'sticky',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
backgroundColor: 'background.paper',
|
||||
padding: 1,
|
||||
zIndex: 1,
|
||||
borderTop: '1px solid',
|
||||
borderColor: 'divider',
|
||||
textAlign: 'center',
|
||||
}}>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => setIsItemsSelectOpen(false)}
|
||||
fullWidth
|
||||
>
|
||||
Done
|
||||
</Button>
|
||||
</Box>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</>
|
||||
)}
|
||||
|
||||
<TextField
|
||||
|
||||
Reference in New Issue
Block a user