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
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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 (
|
||||
<Container maxWidth="lg">
|
||||
@@ -854,6 +900,7 @@ const EventAdmin: React.FC = () => {
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Email</TableCell>
|
||||
<TableCell>Attending</TableCell>
|
||||
<TableCell>Guests</TableCell>
|
||||
<TableCell>Needed Items</TableCell>
|
||||
@@ -865,6 +912,7 @@ const EventAdmin: React.FC = () => {
|
||||
{rsvps.map((rsvp: RSVP) => (
|
||||
<TableRow key={rsvp.id}>
|
||||
<TableCell>{rsvp.name || 'No name provided'}</TableCell>
|
||||
<TableCell>{rsvp.attendee_email || 'No email provided'}</TableCell>
|
||||
<TableCell>
|
||||
{rsvp.attending ?
|
||||
rsvp.attending.charAt(0).toUpperCase() + rsvp.attending.slice(1) :
|
||||
@@ -917,9 +965,25 @@ const EventAdmin: React.FC = () => {
|
||||
<IconButton
|
||||
color="error"
|
||||
onClick={() => handleDeleteRsvp(rsvp)}
|
||||
sx={{ mr: 1 }}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
color="info"
|
||||
onClick={() => handleSendEmail(rsvp)}
|
||||
sx={{ mr: 1 }}
|
||||
disabled={!rsvp.attendee_email}
|
||||
>
|
||||
<EmailIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
color="secondary"
|
||||
onClick={() => handleCopyLink(rsvp)}
|
||||
disabled={!rsvp.edit_id}
|
||||
>
|
||||
<ContentCopyIcon />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user