From 7e4baa33884a2e00eab5641af881d5d21fb83228 Mon Sep 17 00:00:00 2001 From: Ryderjj89 Date: Mon, 26 May 2025 17:15:52 -0400 Subject: [PATCH] Add email column and action buttons to EventAdmin RSVP table - Added email column to display attendee_email in the RSVP management table - Added email icon button to resend confirmation emails to attendees - Added clipboard icon button to copy RSVP edit links to clipboard - Updated RSVP interface to include attendee_email and edit_id fields - Added backend endpoint /api/rsvps/resend-email/:editId for resending emails - Email and copy buttons are disabled when email/edit_id are not available - Improved admin functionality for managing RSVPs and communicating with attendees --- backend/src/index.ts | 42 +++++++++++++++++ frontend/src/components/EventAdmin.tsx | 64 ++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/backend/src/index.ts b/backend/src/index.ts index d41d7dd..52e3aa5 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -522,6 +522,48 @@ app.get('/api/rsvps/edit/:editId', async (req: Request, res: Response) => { } }); +// Resend RSVP edit link email +app.post('/api/rsvps/resend-email/:editId', async (req: Request, res: Response) => { + try { + const { editId } = req.params; + + // Get RSVP and event details + const rsvp = await db.get(` + SELECT r.*, e.title, e.slug + FROM rsvps r + JOIN events e ON r.event_id = e.id + WHERE r.edit_id = ? + `, [editId]); + + if (!rsvp) { + return res.status(404).json({ error: 'RSVP not found' }); + } + + if (!rsvp.attendee_email) { + return res.status(400).json({ error: 'No email address associated with this RSVP' }); + } + + if (!process.env.EMAIL_USER) { + return res.status(500).json({ error: 'Email service not configured' }); + } + + // Send the edit link email + const editLink = `${process.env.FRONTEND_BASE_URL}/events/${rsvp.slug}/rsvp/edit/${editId}`; + await sendRSVPEditLinkEmail({ + eventTitle: rsvp.title, + eventSlug: rsvp.slug, + name: rsvp.name, + to: rsvp.attendee_email, + editLink, + }); + + res.json({ message: 'Email sent successfully' }); + } catch (error) { + console.error('Error resending RSVP edit link email:', error); + res.status(500).json({ error: 'Failed to send email' }); + } +}); + app.delete('/api/events/:slug/rsvps/:id', async (req: Request, res: Response) => { try { diff --git a/frontend/src/components/EventAdmin.tsx b/frontend/src/components/EventAdmin.tsx index 1ec8d9a..b5c152d 100644 --- a/frontend/src/components/EventAdmin.tsx +++ b/frontend/src/components/EventAdmin.tsx @@ -34,6 +34,8 @@ import DeleteIcon from '@mui/icons-material/Delete'; import EditIcon from '@mui/icons-material/Edit'; import AddIcon from '@mui/icons-material/Add'; import WallpaperIcon from '@mui/icons-material/Wallpaper'; +import EmailIcon from '@mui/icons-material/Email'; +import ContentCopyIcon from '@mui/icons-material/ContentCopy'; import axios from 'axios'; interface RSVP { @@ -45,6 +47,8 @@ interface RSVP { guest_names: string[] | string; items_bringing: string[] | string; other_items?: string; + attendee_email?: string; + edit_id?: string; event_id?: number; created_at?: string; updated_at?: string; @@ -658,6 +662,48 @@ 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'); + 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}`); + } catch (error) { + console.error('Error sending email:', error); + setError('Failed to send email'); + } + }; + + const handleCopyLink = async (rsvp: RSVP) => { + if (!rsvp.edit_id || !event) { + setError('Cannot copy link: missing edit ID'); + return; + } + + const editLink = `${window.location.origin}/events/${event.slug}/rsvp/edit/${rsvp.edit_id}`; + + try { + await navigator.clipboard.writeText(editLink); + // Show success message (you could add a snackbar or toast here) + console.log('Link copied to clipboard'); + } catch (error) { + console.error('Error copying to clipboard:', error); + // Fallback for older browsers + const textArea = document.createElement('textarea'); + textArea.value = editLink; + document.body.appendChild(textArea); + textArea.select(); + document.execCommand('copy'); + document.body.removeChild(textArea); + console.log('Link copied to clipboard (fallback method)'); + } + }; + if (loading) { return ( @@ -854,6 +900,7 @@ const EventAdmin: React.FC = () => { Name + Email Attending Guests Needed Items @@ -865,6 +912,7 @@ const EventAdmin: React.FC = () => { {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) : @@ -917,9 +965,25 @@ const EventAdmin: React.FC = () => { handleDeleteRsvp(rsvp)} + sx={{ mr: 1 }} > + handleSendEmail(rsvp)} + sx={{ mr: 1 }} + disabled={!rsvp.attendee_email} + > + + + handleCopyLink(rsvp)} + disabled={!rsvp.edit_id} + > + + ))}