Add EventDetails component and types with proper type definitions
This commit is contained in:
@@ -21,6 +21,8 @@
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.15",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/axios": "^0.14.0",
|
||||
"typescript": "^4.9.5",
|
||||
"react-scripts": "5.0.1"
|
||||
},
|
||||
|
||||
101
frontend/src/components/EventDetails.tsx
Normal file
101
frontend/src/components/EventDetails.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
import { Event, Rsvp } from '../types';
|
||||
import { Button, Container, Typography, Box, Paper, List, ListItem, ListItemText, CircularProgress, Alert } from '@mui/material';
|
||||
|
||||
const EventDetails: React.FC = () => {
|
||||
const { slug } = useParams<{ slug: string }>();
|
||||
const navigate = useNavigate();
|
||||
const [event, setEvent] = useState<Event | null>(null);
|
||||
const [rsvps, setRsvps] = useState<Rsvp[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchEventDetails = async () => {
|
||||
try {
|
||||
const [eventResponse, rsvpsResponse] = await Promise.all([
|
||||
axios.get<Event>(`/api/events/${slug}`),
|
||||
axios.get<Rsvp[]>(`/api/events/${slug}/rsvps`)
|
||||
]);
|
||||
setEvent(eventResponse.data);
|
||||
setRsvps(rsvpsResponse.data);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
setError('Failed to load event details');
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchEventDetails();
|
||||
}, [slug]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Container>
|
||||
<Box display="flex" justifyContent="center" alignItems="center" minHeight="100vh">
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Container>
|
||||
<Alert severity="error">{error}</Alert>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
if (!event) {
|
||||
return (
|
||||
<Container>
|
||||
<Alert severity="warning">Event not found</Alert>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Paper elevation={3} sx={{ p: 3, my: 3 }}>
|
||||
<Typography variant="h4" gutterBottom>
|
||||
{event.title}
|
||||
</Typography>
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
{event.date} at {event.location}
|
||||
</Typography>
|
||||
<Typography variant="body1" paragraph>
|
||||
{event.description}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5" gutterBottom>
|
||||
RSVPs
|
||||
</Typography>
|
||||
<List>
|
||||
{rsvps.map((rsvp) => (
|
||||
<ListItem key={rsvp.id}>
|
||||
<ListItemText
|
||||
primary={rsvp.name}
|
||||
secondary={`${rsvp.email} - ${rsvp.status}`}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
|
||||
<Box mt={3}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => navigate('/')}
|
||||
>
|
||||
Back to Events
|
||||
</Button>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default EventDetails;
|
||||
20
frontend/src/types.ts
Normal file
20
frontend/src/types.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export interface Event {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
date: string;
|
||||
location: string;
|
||||
slug: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface Rsvp {
|
||||
id: number;
|
||||
event_id: number;
|
||||
name: string;
|
||||
email: string;
|
||||
status: 'attending' | 'not_attending' | 'maybe';
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
Reference in New Issue
Block a user