From fa783ec3c6d9730e3b44845649be25299df286c5 Mon Sep 17 00:00:00 2001 From: Daniel Buta Date: Fri, 14 Nov 2025 14:24:26 -0500 Subject: [PATCH 1/5] Implement Top Events feature on homepage --- .../com/linkt/controller/EventController.java | 32 ++++ frontend/my-react-app/src/App.tsx | 154 ++++++++++++++---- frontend/my-react-app/src/api/events.api.ts | 21 +++ 3 files changed, 175 insertions(+), 32 deletions(-) diff --git a/backend/springboot-app/src/main/java/com/linkt/controller/EventController.java b/backend/springboot-app/src/main/java/com/linkt/controller/EventController.java index eeedec0a..4f0a46bd 100644 --- a/backend/springboot-app/src/main/java/com/linkt/controller/EventController.java +++ b/backend/springboot-app/src/main/java/com/linkt/controller/EventController.java @@ -40,6 +40,38 @@ public ResponseEntity> getAllEvents() { return ResponseEntity.ok(events); } + @GetMapping("/top") + public ResponseEntity>> getTopEvents() { + // Get all events + List allEvents = eventRepository.findAll(); + + // Sort by ticket count (descending) and limit to top 5 + List> topEvents = allEvents.stream() + .map(event -> { + java.util.Map eventMap = new java.util.HashMap<>(); + eventMap.put("eventId", event.getEventId()); + eventMap.put("title", event.getTitle()); + eventMap.put("description", event.getDescription()); + eventMap.put("eventType", event.getEventType()); + eventMap.put("startDateTime", event.getStartDateTime()); + eventMap.put("endDateTime", event.getEndDateTime()); + eventMap.put("location", event.getLocation()); + eventMap.put("capacity", event.getCapacity()); + eventMap.put("imageUrl", event.getImageUrl()); + eventMap.put("price", event.getPrice()); + eventMap.put("ticketsSold", event.getTickets().size()); + return eventMap; + }) + .sorted((e1, e2) -> Integer.compare( + (Integer) e2.get("ticketsSold"), + (Integer) e1.get("ticketsSold") + )) + .limit(5) + .collect(java.util.stream.Collectors.toList()); + + return ResponseEntity.ok(topEvents); + } + @GetMapping("/{id}") public ResponseEntity getEventById(@PathVariable Long id) { return eventRepository.findById(id) diff --git a/frontend/my-react-app/src/App.tsx b/frontend/my-react-app/src/App.tsx index 3d5c665b..a9deb998 100644 --- a/frontend/my-react-app/src/App.tsx +++ b/frontend/my-react-app/src/App.tsx @@ -1,17 +1,17 @@ // src/App.tsx import {Routes, Route, useNavigate, Outlet} from 'react-router-dom'; import { useAuth } from './contexts/AuthContext'; +import { useState, useEffect } from 'react'; import { - AppBar, Toolbar, Box, - TextField, - InputAdornment, - IconButton, - Menu, - MenuItem, - Avatar, Typography, + Card, + CardMedia, + CardContent, + Button, + CircularProgress, + Container, } from "@mui/material"; //import '@fontsource-variable/cabin'; import './App.css'; @@ -31,6 +31,8 @@ import MyEventsPage from "./pages/MyEventsPage.tsx"; import EditEventPage from "./pages/EditEventPage.tsx"; import ScanTicketPage from "./pages/ScanTicketPage.tsx"; import AdminDashboard from "./pages/AdminDashboard.tsx"; +import { getTopEvents } from './api/events.api'; +import type { Event } from './types/event.interface'; function MainLayout() { return ( <> @@ -55,6 +57,23 @@ function BlankLayout() { function Home() { const navigate = useNavigate(); const { user } = useAuth(); + const [topEvents, setTopEvents] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchTopEvents = async () => { + try { + const events = await getTopEvents(); + setTopEvents(events); + } catch (error) { + console.error('Failed to fetch top events:', error); + } finally { + setLoading(false); + } + }; + + fetchTopEvents(); + }, []); return ( <> @@ -86,31 +105,102 @@ function Home() { - - {/* - Former Logo - - Vite logo - - - React logo - - */} - Top Events - Backend for this hasn't been implemented yet! - neat college photo! - Frosh Night - New to school and don't know where to start? Have some drinks, play games and meet some new people at the school's frosh night! -

- neat college photo! - DJ Night - The EDM Club is organizing an all-night dance festival on the 22nd of October! Click for more details! -

- neat college photo! - Campus Museum Tour - Join us for a tour of the campus museum where you can browse artifacts of some of the school's greatest alumni! - {/* With this, the "Top Events" container is forced to extend its height to contain the floated images*/} - + + + Top Events + + {loading ? ( + + + + ) : topEvents.length === 0 ? ( + No events available yet. Check back soon! + ) : ( + + {topEvents.map((event) => ( + navigate(`/checkout/${event.eventID}`)} + > + {event.image && event.image.length > 0 && event.image[0] && ( + + )} + + + {event.title} + + + {event.description} + + + 📍 {event.location} + + + 🎫 {event.ticketsSold || 0} tickets sold + + + {event.price === 0 ? 'Free' : `$${event.price}`} + + + + ))} + + )} + + + + + diff --git a/frontend/my-react-app/src/api/events.api.ts b/frontend/my-react-app/src/api/events.api.ts index 4797e33c..a8686a6c 100644 --- a/frontend/my-react-app/src/api/events.api.ts +++ b/frontend/my-react-app/src/api/events.api.ts @@ -76,3 +76,24 @@ export const updateEvent = async (eventId: number, eventData: any) => { }); return response.data; }; + +export const getTopEvents = async (): Promise => { + const response = await axiosInstance.get('/events/top'); + + // Transform backend data to match frontend interface + const transformedEvents: Event[] = response.data.map((event: any) => ({ + eventID: event.eventId, + title: event.title, + description: event.description, + category: event.eventType, + image: event.imageUrl ? (event.imageUrl.startsWith('http') || event.imageUrl.startsWith('https') ? [event.imageUrl] : [`http://localhost:8080${event.imageUrl}`]) : [], + price: event.price || 0, + startDate: new Date(event.startDateTime), + endDate: new Date(event.endDateTime), + location: event.location, + capacity: event.capacity, + ticketsSold: event.ticketsSold || 0, + })); + + return transformedEvents; +}; From b747c66b8449d39904a91c2f480c06490a6bd6ea Mon Sep 17 00:00:00 2001 From: Daniel Buta Date: Fri, 14 Nov 2025 14:30:39 -0500 Subject: [PATCH 2/5] Reduce side margins on Top Events section for better layout --- frontend/my-react-app/src/App.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/frontend/my-react-app/src/App.tsx b/frontend/my-react-app/src/App.tsx index a9deb998..8633aec4 100644 --- a/frontend/my-react-app/src/App.tsx +++ b/frontend/my-react-app/src/App.tsx @@ -11,7 +11,6 @@ import { CardContent, Button, CircularProgress, - Container, } from "@mui/material"; //import '@fontsource-variable/cabin'; import './App.css'; @@ -105,8 +104,8 @@ function Home() { - - + + Top Events {loading ? ( @@ -200,7 +199,7 @@ function Home() { View All Events - +
From a690c867691502e58059e9101d538225d129affb Mon Sep 17 00:00:00 2001 From: Daniel Buta Date: Fri, 14 Nov 2025 14:35:00 -0500 Subject: [PATCH 3/5] Add authentication check for Top Events checkout --- frontend/my-react-app/src/App.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/frontend/my-react-app/src/App.tsx b/frontend/my-react-app/src/App.tsx index 8633aec4..cb487fc9 100644 --- a/frontend/my-react-app/src/App.tsx +++ b/frontend/my-react-app/src/App.tsx @@ -74,6 +74,16 @@ function Home() { fetchTopEvents(); }, []); + const handleEventClick = (eventId: number) => { + if (!user) { + // User not logged in, redirect to login + navigate('/login'); + } else { + // User is logged in, proceed to checkout + navigate(`/checkout/${eventId}`); + } + }; + return ( <> @@ -142,7 +152,7 @@ function Home() { boxShadow: '0 12px 24px rgba(0,0,0,0.15)', } }} - onClick={() => navigate(`/checkout/${event.eventID}`)} + onClick={() => handleEventClick(event.eventID)} > {event.image && event.image.length > 0 && event.image[0] && ( Date: Fri, 14 Nov 2025 14:56:15 -0500 Subject: [PATCH 4/5] Add Buy Ticket and Save Event buttons to Top Events section --- frontend/my-react-app/src/App.tsx | 72 ++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/frontend/my-react-app/src/App.tsx b/frontend/my-react-app/src/App.tsx index cb487fc9..ea8976d5 100644 --- a/frontend/my-react-app/src/App.tsx +++ b/frontend/my-react-app/src/App.tsx @@ -9,6 +9,7 @@ import { Card, CardMedia, CardContent, + CardActions, Button, CircularProgress, } from "@mui/material"; @@ -31,7 +32,9 @@ import EditEventPage from "./pages/EditEventPage.tsx"; import ScanTicketPage from "./pages/ScanTicketPage.tsx"; import AdminDashboard from "./pages/AdminDashboard.tsx"; import { getTopEvents } from './api/events.api'; +import { saveEvent, checkIfSaved } from './api/savedEvents.api'; import type { Event } from './types/event.interface'; +import { useSnackbar } from 'notistack'; function MainLayout() { return ( <> @@ -56,8 +59,11 @@ function BlankLayout() { function Home() { const navigate = useNavigate(); const { user } = useAuth(); + const { enqueueSnackbar } = useSnackbar(); const [topEvents, setTopEvents] = useState([]); const [loading, setLoading] = useState(true); + const [savedEventIds, setSavedEventIds] = useState>(new Set()); + const [savingEventId, setSavingEventId] = useState(null); useEffect(() => { const fetchTopEvents = async () => { @@ -74,6 +80,51 @@ function Home() { fetchTopEvents(); }, []); + useEffect(() => { + const checkSavedEvents = async () => { + if (!localStorage.getItem('token')) return; + + const savedIds = new Set(); + for (const event of topEvents) { + try { + const isSaved = await checkIfSaved(event.eventID); + if (isSaved) { + savedIds.add(event.eventID); + } + } catch (error) { + // Ignore errors for individual checks + } + } + setSavedEventIds(savedIds); + }; + + if (topEvents.length > 0) { + checkSavedEvents(); + } + }, [topEvents]); + + const handleAddToFavorites = async (eventId: number) => { + if (!localStorage.getItem('token')) { + navigate('/login'); + return; + } + + setSavingEventId(eventId); + try { + await saveEvent(eventId); + setSavedEventIds(prev => new Set(prev).add(eventId)); + enqueueSnackbar('Event saved!', { variant: 'success' }); + } catch (error: any) { + if (error.response?.status === 401) { + navigate('/login'); + } else { + enqueueSnackbar('Failed to save event', { variant: 'error' }); + } + } finally { + setSavingEventId(null); + } + }; + const handleEventClick = (eventId: number) => { if (!user) { // User not logged in, redirect to login @@ -143,7 +194,6 @@ function Home() { height: '100%', display: 'flex', flexDirection: 'column', - cursor: 'pointer', transition: 'all 0.3s ease-in-out', borderRadius: 3, overflow: 'hidden', @@ -152,7 +202,6 @@ function Home() { boxShadow: '0 12px 24px rgba(0,0,0,0.15)', } }} - onClick={() => handleEventClick(event.eventID)} > {event.image && event.image.length > 0 && event.image[0] && ( + + + + ))} From 81105d9d36c75e641e7679306cf9229ce801922d Mon Sep 17 00:00:00 2001 From: Daniel Buta Date: Tue, 18 Nov 2025 16:56:15 -0500 Subject: [PATCH 5/5] Fix missing closing Box tag in Top Events section --- frontend/my-react-app/src/App.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/my-react-app/src/App.tsx b/frontend/my-react-app/src/App.tsx index a91b8636..05e9b6d5 100644 --- a/frontend/my-react-app/src/App.tsx +++ b/frontend/my-react-app/src/App.tsx @@ -303,6 +303,7 @@ function Home() { View All Events +