From b6c548f25a3a62fe375c650960232dccdc2da6f5 Mon Sep 17 00:00:00 2001 From: Ryderjj89 Date: Mon, 26 May 2025 17:29:44 -0400 Subject: [PATCH] Fix email field in EventAdmin RSVP edit form and add snackbar feedback - Added email_address field to EditFormData interface - Updated handleEditRsvp to populate email field from rsvp.attendee_email - Modified handleEditSubmit to include email_address in submission data - Added snackbar state variables for user feedback notifications - Enhanced handleSendEmail and handleCopyLink with success/error snackbar messages - Added handleSnackbarClose function for snackbar management - Improved user experience with immediate visual feedback for admin actions - Fixed issue where email field would show [object Object] instead of actual email text --- frontend/src/components/EventAdmin.tsx | 625 ++----------------------- 1 file changed, 32 insertions(+), 593 deletions(-) diff --git a/frontend/src/components/EventAdmin.tsx b/frontend/src/components/EventAdmin.tsx index b5c152d..d4fe464 100644 --- a/frontend/src/components/EventAdmin.tsx +++ b/frontend/src/components/EventAdmin.tsx @@ -29,6 +29,8 @@ import { ListItemText, Checkbox, Chip, + Snackbar, + Alert, } from '@mui/material'; import DeleteIcon from '@mui/icons-material/Delete'; import EditIcon from '@mui/icons-material/Edit'; @@ -73,6 +75,7 @@ interface Event { interface EditFormData { name: string; + email_address: string; attending: 'yes' | 'no' | 'maybe'; bringing_guests: 'yes' | 'no'; guest_count: number; @@ -110,6 +113,7 @@ const EventAdmin: React.FC = () => { const [rsvpToEdit, setRsvpToEdit] = useState(null); const [editForm, setEditForm] = useState({ name: '', + email_address: '', attending: 'yes', bringing_guests: 'no', guest_count: 0, @@ -134,6 +138,9 @@ const EventAdmin: React.FC = () => { event_conclusion_email_enabled: false, event_conclusion_message: '' }); + const [snackbarOpen, setSnackbarOpen] = useState(false); + const [snackbarMessage, setSnackbarMessage] = useState(''); + const [snackbarSeverity, setSnackbarSeverity] = useState<'success' | 'error'>('success'); useEffect(() => { fetchEventAndRsvps(); @@ -266,6 +273,7 @@ const EventAdmin: React.FC = () => { setRsvpToEdit(rsvp); setEditForm({ name: rsvp.name, + email_address: rsvp.attendee_email || '', attending: rsvp.attending || 'yes', bringing_guests: rsvp.bringing_guests || 'no', guest_count: typeof rsvp.guest_count === 'number' ? rsvp.guest_count : 0, @@ -379,6 +387,7 @@ const EventAdmin: React.FC = () => { // Prepare submission data in the exact format the backend expects const submissionData = { name: editForm.name, + email_address: editForm.email_address, attending: editForm.attending, bringing_guests: editForm.bringing_guests, guest_count: editForm.bringing_guests === 'yes' ? Math.max(1, parseInt(editForm.guest_count.toString(), 10)) : 0, @@ -417,6 +426,7 @@ const EventAdmin: React.FC = () => { const updatedRsvp: RSVP = { ...rsvpToEdit, ...submissionData, + attendee_email: editForm.email_address, guest_names: filteredGuestNames, items_bringing: editForm.items_bringing, other_items: splitOtherItems @@ -664,24 +674,30 @@ const EventAdmin: React.FC = () => { const handleSendEmail = async (rsvp: RSVP) => { if (!rsvp.attendee_email || !rsvp.edit_id || !event) { - setError('Cannot send email: missing email address or edit ID'); + setSnackbarMessage('Cannot send email: missing email address or edit ID'); + setSnackbarSeverity('error'); + setSnackbarOpen(true); return; } try { - // Create a backend endpoint to resend the edit link email await axios.post(`/api/rsvps/resend-email/${rsvp.edit_id}`); - // Show success message (you could add a snackbar or toast here) - console.log(`Email sent to ${rsvp.attendee_email}`); + setSnackbarMessage(`Email sent successfully to ${rsvp.attendee_email}`); + setSnackbarSeverity('success'); + setSnackbarOpen(true); } catch (error) { console.error('Error sending email:', error); - setError('Failed to send email'); + setSnackbarMessage('Failed to send email'); + setSnackbarSeverity('error'); + setSnackbarOpen(true); } }; const handleCopyLink = async (rsvp: RSVP) => { if (!rsvp.edit_id || !event) { - setError('Cannot copy link: missing edit ID'); + setSnackbarMessage('Cannot copy link: missing edit ID'); + setSnackbarSeverity('error'); + setSnackbarOpen(true); return; } @@ -689,8 +705,9 @@ const EventAdmin: React.FC = () => { try { await navigator.clipboard.writeText(editLink); - // Show success message (you could add a snackbar or toast here) - console.log('Link copied to clipboard'); + setSnackbarMessage('Link copied to clipboard successfully'); + setSnackbarSeverity('success'); + setSnackbarOpen(true); } catch (error) { console.error('Error copying to clipboard:', error); // Fallback for older browsers @@ -700,10 +717,16 @@ const EventAdmin: React.FC = () => { textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); - console.log('Link copied to clipboard (fallback method)'); + setSnackbarMessage('Link copied to clipboard successfully'); + setSnackbarSeverity('success'); + setSnackbarOpen(true); } }; + const handleSnackbarClose = () => { + setSnackbarOpen(false); + }; + if (loading) { return ( @@ -780,587 +803,3 @@ const EventAdmin: React.FC = () => { sx={{ minWidth: 'fit-content', whiteSpace: 'nowrap' - }} - > - Manage Items - - - - - - - - Info: {event.description || 'None'} - - - Location: {event.location} - - - Date: {new Date(event.date).toLocaleString()} - - {event.rsvp_cutoff_date && ( - - RSVP cut-off date: {new Date(event.rsvp_cutoff_date).toLocaleString()} - - )} - - - {/* Add items status section */} - - - Items Status - - - - - Still Needed: - - - {neededItems.map((item: string, index: number) => ( - - ))} - {neededItems.length === 0 && ( - - All items have been claimed - - )} - - - - - Claimed Items: - - - {claimedItems.map((item: string, index: number) => ( - - ))} - {claimedItems.length === 0 && ( - - No items have been claimed yet - - )} - - - {/* Other Items Section */} - - - Other Items: - - - {otherItems.length > 0 - ? otherItems.join(', ') - : 'No other items have been brought'} - - - - - - - RSVPs: {rsvps.length} | Total Guests: {rsvps.reduce((total, rsvp) => { - // Count the RSVP person as 1 if they're attending - const rsvpCount = rsvp.attending === 'yes' ? 1 : 0; - // Add their guests if they're bringing any - const guestCount = (rsvp.attending === 'yes' && rsvp.bringing_guests === 'yes') ? rsvp.guest_count : 0; - return total + rsvpCount + guestCount; - }, 0)} - - - - - - - Name - Email - Attending - Guests - Needed Items - Other Items - Actions - - - - {rsvps.map((rsvp: RSVP) => ( - - {rsvp.name || 'No name provided'} - {rsvp.attendee_email || 'No email provided'} - - {rsvp.attending ? - rsvp.attending.charAt(0).toUpperCase() + rsvp.attending.slice(1) : - 'Unknown' - } - - - {rsvp.bringing_guests === 'yes' ? - `${rsvp.guest_count || 0} (${Array.isArray(rsvp.guest_names) ? - rsvp.guest_names.join(', ') : - typeof rsvp.guest_names === 'string' ? - rsvp.guest_names.replace(/\s+/g, ', ') : - 'No names provided'})` : - 'No' - } - - - {Array.isArray(rsvp.items_bringing) ? - rsvp.items_bringing.map((item, index) => ( - - )) : - typeof rsvp.items_bringing === 'string' ? - JSON.parse(rsvp.items_bringing).map((item: string, index: number) => ( - - )) : - 'None' - } - - - {rsvp.other_items || 'None'} - - - handleEditRsvp(rsvp)} - sx={{ mr: 1 }} - > - - - handleDeleteRsvp(rsvp)} - sx={{ mr: 1 }} - > - - - handleSendEmail(rsvp)} - sx={{ mr: 1 }} - disabled={!rsvp.attendee_email} - > - - - handleCopyLink(rsvp)} - disabled={!rsvp.edit_id} - > - - - - - ))} - -
-
- - - setDeleteDialogOpen(false)} - > - Delete RSVP - - - Are you sure you want to delete {rsvpToDelete?.name}'s RSVP? - - - - - - - - - setEditDialogOpen(false)} - maxWidth="sm" - fullWidth - > - Edit RSVP - - - - - Attending - - - - Bringing Guests - - - {editForm.bringing_guests === 'yes' && ( - <> - - {/* Individual guest name fields */} - {Array.from({ length: editForm.guest_count }, (_, index) => ( - - ))} - - )} - - What items are you bringing? - - multiple - name="items_bringing" - value={editForm.items_bringing} - onChange={handleItemsChange} - input={} - renderValue={(selected) => ( - - {selected.map((value: string) => ( - - ))} - - )} - > - {Array.from(new Set([...neededItems, ...editForm.items_bringing])).map((item: string) => ( - - - - - ))} - - - setEditForm(prev => ({ ...prev, other_items: e.target.value }))} - fullWidth - multiline - rows={3} - /> - - - - - - - - - setDeleteEventDialogOpen(false)} - maxWidth="sm" - fullWidth - > - Delete Event - - - Are you sure you want to delete "{event.title}"? This action cannot be undone. - - - All RSVPs associated with this event will also be deleted. - - - - - - - - - setManageItemsDialogOpen(false)} - maxWidth="sm" - fullWidth - > - Manage Needed Items - - - - setNewItem(e.target.value)} - fullWidth - /> - - - - Current Items: - - - {event?.needed_items && Array.isArray(event.needed_items) && event.needed_items.map((item, index) => ( - handleRemoveItem(item)} - color={claimedItems.includes(item) ? "success" : "primary"} - /> - ))} - - - - - - - - - setUpdateInfoDialogOpen(false)} - maxWidth="sm" - fullWidth - > - Update Event Information - - - {/* Event Title Field */} - setUpdateForm(prev => ({ ...prev, title: e.target.value }))} - fullWidth - /> - setUpdateForm(prev => ({ ...prev, description: e.target.value }))} - fullWidth - multiline - rows={3} - /> - setUpdateForm(prev => ({ ...prev, location: e.target.value }))} - fullWidth - /> - setUpdateForm(prev => ({ ...prev, date: e.target.value }))} - fullWidth - InputLabelProps={{ - shrink: true, - }} - /> - setUpdateForm(prev => ({ ...prev, rsvp_cutoff_date: e.target.value }))} - fullWidth - InputLabelProps={{ - shrink: true, - }} - /> - - - Email Notifications - - setUpdateForm(prev => ({ - ...prev, - email_notifications_enabled: e.target.checked - }))} - /> - } - label="Enable Email Notifications" - /> - - {updateForm.email_notifications_enabled && ( - setUpdateForm(prev => ({ - ...prev, - email_recipients: e.target.value - }))} - variant="outlined" - placeholder="email1@example.com, email2@example.com" - helperText="Enter email addresses separated by commas" - sx={{ mt: 2 }} - /> - )} - - - {/* Event Conclusion Email Settings */} - - - Event Conclusion Email - - setUpdateForm(prev => ({ - ...prev, - event_conclusion_email_enabled: e.target.checked - }))} - /> - } - label="Enable Event Conclusion Email" - /> - - {updateForm.event_conclusion_email_enabled && ( - setUpdateForm(prev => ({ - ...prev, - event_conclusion_message: e.target.value - }))} - variant="outlined" - multiline - rows={4} - helperText="This message will be sent to attendees who opted for email notifications the day after the event." - sx={{ mt: 2 }} - /> - )} - - - - - Wallpaper - - {event.wallpaper && ( - - - Current wallpaper: - - - - )} - - {updateForm.wallpaper && ( - - Selected file: {updateForm.wallpaper.name} - - )} - - - - - - - - -
- - - ); -}; - -export default EventAdmin;