diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 8a1b9e2..a1b32a6 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -6,6 +6,7 @@ import { Container } from '@mui/material';
import EventList from './components/EventList';
import EventForm from './components/EventForm';
import RSVPForm from './components/RSVPForm';
+import EventAdmin from './components/EventAdmin';
import './App.css';
const darkTheme = createTheme({
@@ -41,6 +42,7 @@ const App: React.FC = () => {
} />
} />
} />
+ } />
diff --git a/frontend/src/components/EventAdmin.tsx b/frontend/src/components/EventAdmin.tsx
new file mode 100644
index 0000000..be3ca7c
--- /dev/null
+++ b/frontend/src/components/EventAdmin.tsx
@@ -0,0 +1,184 @@
+import React, { useState, useEffect } from 'react';
+import { useParams, useNavigate } from 'react-router-dom';
+import {
+ Box,
+ Paper,
+ Typography,
+ Button,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ IconButton,
+ Container,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+} from '@mui/material';
+import DeleteIcon from '@mui/icons-material/Delete';
+import EditIcon from '@mui/icons-material/Edit';
+import axios from 'axios';
+
+interface RSVP {
+ id: number;
+ name: string;
+ attending: string;
+ bringing_guests: string;
+ guest_count: number;
+ guest_names: string;
+ items_bringing: string;
+}
+
+interface Event {
+ id: number;
+ title: string;
+ description: string;
+ date: string;
+ location: string;
+ slug: string;
+}
+
+const EventAdmin: React.FC = () => {
+ const { slug } = useParams<{ slug: string }>();
+ const navigate = useNavigate();
+ const [event, setEvent] = useState(null);
+ const [rsvps, setRsvps] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
+ const [rsvpToDelete, setRsvpToDelete] = useState(null);
+
+ useEffect(() => {
+ fetchEventAndRsvps();
+ }, [slug]);
+
+ const fetchEventAndRsvps = async () => {
+ try {
+ const [eventResponse, rsvpsResponse] = await Promise.all([
+ axios.get(`/api/events/${slug}`),
+ axios.get(`/api/events/${slug}/rsvps`)
+ ]);
+ setEvent(eventResponse.data);
+ setRsvps(rsvpsResponse.data);
+ setLoading(false);
+ } catch (error) {
+ setError('Failed to load event data');
+ setLoading(false);
+ }
+ };
+
+ const handleDeleteRsvp = async (rsvp: RSVP) => {
+ setRsvpToDelete(rsvp);
+ setDeleteDialogOpen(true);
+ };
+
+ const confirmDelete = async () => {
+ if (!rsvpToDelete) return;
+
+ try {
+ await axios.delete(`/api/events/${slug}/rsvps/${rsvpToDelete.id}`);
+ setRsvps(rsvps.filter(r => r.id !== rsvpToDelete.id));
+ setDeleteDialogOpen(false);
+ setRsvpToDelete(null);
+ } catch (error) {
+ setError('Failed to delete RSVP');
+ }
+ };
+
+ if (loading) {
+ return (
+
+ Loading...
+
+ );
+ }
+
+ if (error || !event) {
+ return (
+
+ {error || 'Event not found'}
+
+ );
+ }
+
+ return (
+
+
+
+
+ {event.title} - Admin
+
+
+
+
+
+ RSVPs ({rsvps.length})
+
+
+
+
+
+
+ Name
+ Attending
+ Guests
+ Items Bringing
+ Actions
+
+
+
+ {rsvps.map((rsvp) => (
+
+ {rsvp.name}
+ {rsvp.attending}
+
+ {rsvp.bringing_guests === 'yes' ? (
+ `${rsvp.guest_count} - ${rsvp.guest_names}`
+ ) : (
+ 'No'
+ )}
+
+ {rsvp.items_bringing}
+
+ handleDeleteRsvp(rsvp)}
+ >
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+ );
+};
+
+export default EventAdmin;
\ No newline at end of file
diff --git a/frontend/src/components/EventForm.tsx b/frontend/src/components/EventForm.tsx
index f177371..d16df32 100644
--- a/frontend/src/components/EventForm.tsx
+++ b/frontend/src/components/EventForm.tsx
@@ -6,6 +6,7 @@ import {
TextField,
Typography,
Container,
+ Paper,
} from '@mui/material';
import axios from 'axios';
@@ -17,6 +18,7 @@ const EventForm: React.FC = () => {
date: '',
location: '',
});
+ const [error, setError] = useState(null);
const handleChange = (e: React.ChangeEvent) => {
const { name, value } = e.target;
@@ -28,28 +30,37 @@ const EventForm: React.FC = () => {
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
+ setError(null);
try {
await axios.post('/api/events', formData);
navigate('/');
} catch (error) {
+ setError('Failed to create event. Please try again.');
console.error('Error creating event:', error);
}
};
return (
-
-
+
+
Create New Event
-
-
+
+
);
};
diff --git a/frontend/src/components/EventList.tsx b/frontend/src/components/EventList.tsx
index 62b17ef..3af7268 100644
--- a/frontend/src/components/EventList.tsx
+++ b/frontend/src/components/EventList.tsx
@@ -7,7 +7,10 @@ import {
CardContent,
Typography,
Grid,
+ CardActions,
+ IconButton,
} from '@mui/material';
+import AdminPanelSettingsIcon from '@mui/icons-material/AdminPanelSettings';
import axios from 'axios';
interface Event {
@@ -40,6 +43,10 @@ const EventList: React.FC = () => {
navigate(`/events/${event.slug}/rsvp`);
};
+ const handleAdminClick = (event: Event) => {
+ navigate(`/events/${event.slug}/admin`);
+ };
+
return (
@@ -78,6 +85,18 @@ const EventList: React.FC = () => {
{event.description}
+
+ {
+ e.stopPropagation();
+ handleAdminClick(event);
+ }}
+ color="primary"
+ aria-label="admin"
+ >
+
+
+
))}
diff --git a/frontend/src/components/RSVPForm.tsx b/frontend/src/components/RSVPForm.tsx
index 0c653b8..3d7bbf0 100644
--- a/frontend/src/components/RSVPForm.tsx
+++ b/frontend/src/components/RSVPForm.tsx
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
-import { useParams } from 'react-router-dom';
+import { useParams, useNavigate } from 'react-router-dom';
import axios from 'axios';
import {
Box,
@@ -37,6 +37,7 @@ const RSVPForm: React.FC = () => {
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState(null);
const [success, setSuccess] = useState(false);
+ const navigate = useNavigate();
const handleChange = (e: React.ChangeEvent) => {
const { name, value } = e.target;
@@ -76,9 +77,16 @@ const RSVPForm: React.FC = () => {
Thank You!
-
+
Your RSVP has been submitted successfully.
+
);