diff --git a/frontend/src/app/dashboard/page.tsx b/frontend/src/app/dashboard/page.tsx new file mode 100644 index 0000000..8f99f91 --- /dev/null +++ b/frontend/src/app/dashboard/page.tsx @@ -0,0 +1,12 @@ +import { Dashboard } from '@/components/dashboard'; + +export default function DashboardPage() { + return ( +
+
+

Dashboard

+ +
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/app/registrations/page.tsx b/frontend/src/app/registrations/page.tsx new file mode 100644 index 0000000..a127a82 --- /dev/null +++ b/frontend/src/app/registrations/page.tsx @@ -0,0 +1,15 @@ +import { RegistrationList } from '@/components/registration-list'; +import { Suspense } from 'react'; + +export default function RegistrationsPage() { + return ( +
+
+

My Registrations

+ Loading...
}> + + +
+ + ); +} \ No newline at end of file diff --git a/frontend/src/components/dashboard.tsx b/frontend/src/components/dashboard.tsx new file mode 100644 index 0000000..f9fdd3c --- /dev/null +++ b/frontend/src/components/dashboard.tsx @@ -0,0 +1,200 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useAuth } from '@/lib/auth-context'; +import { api } from '@/lib/api'; + +export function Dashboard() { + const { user } = useAuth(); + const [dashboard, setDashboard] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + loadDashboard(); + }, []); + + const loadDashboard = async () => { + try { + setIsLoading(true); + if (user?.role === 'Organizer') { + const data = await api.getOrganizerDashboard(); + setDashboard(data); + } else { + const data = await api.getParticipantDashboard(); + setDashboard(data); + } + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to load dashboard'); + } finally { + setIsLoading(false); + } + }; + + if (isLoading) { + return
Loading dashboard...
; + } + + if (error) { + return ( +
+ {error} +
+ ); + } + + if (!dashboard) { + return
No data available
; + } + + return ( +
+ {/* Quick Actions */} +
+ {user?.role === 'Organizer' ? ( + <> + + Create Event + + + Manage Events + + + ) : ( + <> + + Browse Events + + + My Registrations + + + )} +
+ + {/* Stats Grid */} + {user?.role === 'Organizer' ? ( + + ) : ( + + )} +
+ ); +} + +function OrganizerDashboard({ data }: { data: any }) { + return ( + <> +
+ + + + +
+ +
+
+

Upcoming Events

+ {data.upcomingEvents?.length > 0 ? ( +
+ {data.upcomingEvents.map((event: any) => ( +
+ + {event.name} + +

+ {new Date(event.eventDate).toLocaleDateString()} - {event.registrationCount} registered +

+
+ ))} +
+ ) : ( +

No upcoming events

+ )} +
+ +
+

Revenue

+
+ ${data.totalRevenue?.toFixed(2) || '0.00'} +
+
+
+
{data.paidRegistrations}
+
Paid
+
+
+
{data.pendingRegistrations}
+
Pending
+
+
+
{data.cancelledRegistrations}
+
Cancelled
+
+
+
+
+ + ); +} + +function ParticipantDashboard({ data }: { data: any }) { + return ( + <> +
+ + + + +
+ +
+

My Recent Registrations

+ {data.myRegistrations?.length > 0 ? ( +
+ {data.myRegistrations.slice(0, 5).map((reg: any) => ( +
+
+ + {reg.eventName} + +

+ {new Date(reg.eventDate).toLocaleDateString()} - + + {reg.status} + +

+
+
+ ))} +
+ ) : ( +

+ No registrations yet. Browse events +

+ )} +
+ + ); +} + +function StatCard({ title, value, color = 'gray' }: { title: string; value: number; color?: string }) { + const colorClasses: Record = { + gray: 'bg-white', + green: 'bg-green-50', + yellow: 'bg-yellow-50', + blue: 'bg-blue-50', + red: 'bg-red-50', + }; + + return ( +
+

{title}

+
{value}
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/components/registration-list.tsx b/frontend/src/components/registration-list.tsx new file mode 100644 index 0000000..ecab533 --- /dev/null +++ b/frontend/src/components/registration-list.tsx @@ -0,0 +1,124 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { api, Registration } from '@/lib/api'; +import { useAuth } from '@/lib/auth-context'; + +export function RegistrationList() { + const [registrations, setRegistrations] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + const { user } = useAuth(); + + useEffect(() => { + loadRegistrations(); + }, []); + + const loadRegistrations = async () => { + try { + setIsLoading(true); + const data = await api.getMyRegistrations(); + setRegistrations(data); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to load registrations'); + } finally { + setIsLoading(false); + } + }; + + const handleCancel = async (id: string) => { + if (!confirm('Are you sure you want to cancel this registration?')) { + return; + } + + try { + await api.cancelRegistration(id); + loadRegistrations(); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to cancel registration'); + } + }; + + const getStatusColor = (status: string) => { + switch (status) { + case 'Confirmed': + return 'bg-green-100 text-green-800'; + case 'Pending': + return 'bg-yellow-100 text-yellow-800'; + case 'Cancelled': + return 'bg-red-100 text-red-800'; + default: + return 'bg-gray-100 text-gray-800'; + } + }; + + if (isLoading) { + return
Loading registrations...
; + } + + if (error) { + return ( +
+ {error} +
+ ); + } + + return ( +
+ {registrations.length === 0 ? ( +
+

You haven't registered for any events yet.

+ + Browse events + +
+ ) : ( +
+ {registrations.map((registration) => ( +
+
+
+
+ + {registration.status} + + + {new Date(registration.eventDate).toLocaleDateString()} + +
+

{registration.eventName}

+

+ Registered: {new Date(registration.registeredAt).toLocaleDateString()} +

+ {registration.totalPaid > 0 && ( +

+ Paid: ${registration.totalPaid.toFixed(2)} +

+ )} +
+ +
+ + View Event + + {registration.status !== 'Cancelled' && registration.status !== 'Completed' && ( + + )} +
+
+
+ ))} +
+ )} +
+ ); +} \ No newline at end of file