From b5af8c0635caaf91c272328b77fed12044aaabe0 Mon Sep 17 00:00:00 2001 From: gavinlin24 Date: Mon, 25 Aug 2025 16:14:33 -0700 Subject: [PATCH 01/16] New Events Page Created new Events page and Event Card components --- frontend/src/App.tsx | 2 +- .../src/components/NewEvents/EventCard.tsx | 83 +++++++++ frontend/src/components/NewEvents/Events.tsx | 160 ++++++++++++++++++ 3 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/NewEvents/EventCard.tsx create mode 100644 frontend/src/components/NewEvents/Events.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 8c341db8..c47d2bc1 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -6,7 +6,7 @@ import Home from './components/Home/Home'; import NavBar from './components/NavBar/NavBar'; import Footer from './components/Footer/Footer'; import About from './components/About/About'; -import Events from './components/Events/Events'; +import Events from './components/NewEvents/Events'; import Opportunities from './components/Opportunities/Opportunities'; import Membership from './components/Membership/Membership'; import Login from './components/Login/Login'; diff --git a/frontend/src/components/NewEvents/EventCard.tsx b/frontend/src/components/NewEvents/EventCard.tsx new file mode 100644 index 00000000..21f031bc --- /dev/null +++ b/frontend/src/components/NewEvents/EventCard.tsx @@ -0,0 +1,83 @@ +import React from "react"; +import { Typography, Card, Box } from "@mui/material"; + +type EventCardProps = { + title: string; + startDate: string; + endDate: string; + location: string; + calendar_link: string; + description: string; + instagram_link: string; + _id: string; +}; + +const EventCard = ({ + title, + startDate, + endDate, + location, + calendar_link, + description, + instagram_link, + _id, + }: EventCardProps) => { + const start = new Date(startDate); + const end = new Date(endDate); + + const formattedDate = start.toLocaleDateString("en-US", { + year: "numeric", + month: "short", + day: "numeric", + }); + + const formattedTime = `${start.toLocaleTimeString("en-US", { + hour: "numeric", + minute: "2-digit", + })} - ${end.toLocaleTimeString("en-US", { + hour: "numeric", + minute: "2-digit", + })}`; + + return ( + + + {title} + + + + {formattedDate} | {formattedTime} + + + {location} + + + + ); + }; + + +export default EventCard; \ No newline at end of file diff --git a/frontend/src/components/NewEvents/Events.tsx b/frontend/src/components/NewEvents/Events.tsx new file mode 100644 index 00000000..6635c015 --- /dev/null +++ b/frontend/src/components/NewEvents/Events.tsx @@ -0,0 +1,160 @@ +import React, { useState, useEffect } from "react"; +import { Box, Grid, Button, Typography } from "@mui/material"; +import axios from "axios"; +import EventCard from "./EventCard"; + +const categories = ["General", "Dev", "Open Source", "Innovate"]; + +const categoryColors: Record = { + General: "orange", + Dev: "teal", + "Open Source": "blue", + Innovate: "purple", +}; + +interface Event { + calendar_link: string; + description: string; + end_time: string; + instagram_link: string; + location: string; + start_time: string; + title: string; + _id: string; +} + +const EventsPage = () => { + const [selectedCategory, setSelectedCategory] = useState(null); + const [upcomingEvents, setUpcomingEvents] = useState([]); + const [pastEvents, setPastEvents] = useState([]); + + useEffect(() => { + const upcomingEventsEndpoint = `${process.env.REACT_APP_BACKEND_URL}/api/v1/events?type=upcoming`; + const pastEventsEndpoint = `${process.env.REACT_APP_BACKEND_URL}/api/v1/events?type=past`; + + const fetchEvents = async () => { + try { + const [upcomingRes, pastRes] = await Promise.all([ + axios.get(upcomingEventsEndpoint), + axios.get(pastEventsEndpoint), + ]); + setUpcomingEvents(upcomingRes.data); + setPastEvents(pastRes.data); + } catch (error) { + console.error("Error fetching events:", error); + } + }; + + fetchEvents(); + }, []); + + const toPST = (iso: string) => + new Date(new Date(iso).toLocaleString("en-US", { timeZone: "America/Los_Angeles" }) + ); + + return ( + + + Events + + + {/* Categories */} + + {categories.map((cat) => { + const isSelected = selectedCategory === cat; + const color = categoryColors[cat]; + return ( + + ); + })} + + {/* Past Events */} + + + Past Events + + + + {pastEvents.length > 0 ? ( + pastEvents.map((event) => ( + + + + )) + ) : ( + + No past events yet. + + )} + + + + ); +}; + +export default EventsPage; + + From 081b0c6bdf0033ab45fc894b4f291a8047a62870 Mon Sep 17 00:00:00 2001 From: gavinlin24 Date: Thu, 28 Aug 2025 14:37:05 -0700 Subject: [PATCH 02/16] Event styling + display order Fixed some styling with event cards. More recent past events are now displayed towards the top. --- frontend/src/components/NewEvents/Events.tsx | 182 +++++++++---------- 1 file changed, 87 insertions(+), 95 deletions(-) diff --git a/frontend/src/components/NewEvents/Events.tsx b/frontend/src/components/NewEvents/Events.tsx index 6635c015..06e7bd76 100644 --- a/frontend/src/components/NewEvents/Events.tsx +++ b/frontend/src/components/NewEvents/Events.tsx @@ -38,8 +38,11 @@ const EventsPage = () => { axios.get(upcomingEventsEndpoint), axios.get(pastEventsEndpoint), ]); - setUpcomingEvents(upcomingRes.data); - setPastEvents(pastRes.data); + const sortedPastEvents = pastRes.data.sort((a, b) => + new Date(b.start_time).getTime() - new Date(a.start_time).getTime() + ); + setUpcomingEvents(upcomingRes.data); + setPastEvents(sortedPastEvents); } catch (error) { console.error("Error fetching events:", error); } @@ -48,113 +51,102 @@ const EventsPage = () => { fetchEvents(); }, []); - const toPST = (iso: string) => - new Date(new Date(iso).toLocaleString("en-US", { timeZone: "America/Los_Angeles" }) - ); - return ( - - - Events - - - {/* Categories */} - - {categories.map((cat) => { - const isSelected = selectedCategory === cat; - const color = categoryColors[cat]; - return ( - - ); - })} - - {/* Past Events */} - + - Past Events + Events + {/* Categories */} - {pastEvents.length > 0 ? ( - pastEvents.map((event) => ( - - - - )) - ) : ( - - No past events yet. + {categories.map((cat) => { + const isSelected = selectedCategory === cat; + const color = categoryColors[cat]; + return ( + + ); + })} + + + {/* Past Events */} + + + Past Events - )} + + + {pastEvents.length > 0 ? ( + pastEvents.map((event) => ( + + + + + + )) + ) : ( + + No past events yet. + + )} + - - ); + ); }; -export default EventsPage; - - +export default EventsPage; \ No newline at end of file From e84facd1dc9198a50873da7d4cae007108c03a44 Mon Sep 17 00:00:00 2001 From: gavinlin24 Date: Fri, 12 Sep 2025 22:54:08 -0700 Subject: [PATCH 03/16] Upcoming Events Added upcoming events carousel. Fixed styling for both event sections. --- frontend/src/components/NewEvents/Events.tsx | 210 +++++++++++++++---- 1 file changed, 172 insertions(+), 38 deletions(-) diff --git a/frontend/src/components/NewEvents/Events.tsx b/frontend/src/components/NewEvents/Events.tsx index 06e7bd76..58b122fd 100644 --- a/frontend/src/components/NewEvents/Events.tsx +++ b/frontend/src/components/NewEvents/Events.tsx @@ -1,15 +1,16 @@ import React, { useState, useEffect } from "react"; -import { Box, Grid, Button, Typography } from "@mui/material"; +import { Box, Grid, Button, Typography, IconButton, useTheme, useMediaQuery } from "@mui/material"; +import { ArrowBackIosNewRounded, ArrowForwardIosRounded } from "@mui/icons-material"; import axios from "axios"; import EventCard from "./EventCard"; const categories = ["General", "Dev", "Open Source", "Innovate"]; const categoryColors: Record = { - General: "orange", - Dev: "teal", - "Open Source": "blue", - Innovate: "purple", + General: "#EBB111", + Dev: "#5DF0C4", + "Open Source": "#64C3E3", + Innovate: "#725DF0", }; interface Event { @@ -27,22 +28,27 @@ const EventsPage = () => { const [selectedCategory, setSelectedCategory] = useState(null); const [upcomingEvents, setUpcomingEvents] = useState([]); const [pastEvents, setPastEvents] = useState([]); + const [currentIndex, setCurrentIndex] = useState(0); useEffect(() => { - const upcomingEventsEndpoint = `${process.env.REACT_APP_BACKEND_URL}/api/v1/events?type=upcoming`; - const pastEventsEndpoint = `${process.env.REACT_APP_BACKEND_URL}/api/v1/events?type=past`; - const fetchEvents = async () => { try { const [upcomingRes, pastRes] = await Promise.all([ - axios.get(upcomingEventsEndpoint), - axios.get(pastEventsEndpoint), + axios.get(`${process.env.REACT_APP_BACKEND_URL}/api/v1/events?type=upcoming`), + axios.get(`${process.env.REACT_APP_BACKEND_URL}/api/v1/events?type=past`), ]); - const sortedPastEvents = pastRes.data.sort((a, b) => - new Date(b.start_time).getTime() - new Date(a.start_time).getTime() + + const sortedUpcoming = upcomingRes.data.sort( + (a, b) => + new Date(a.start_time).getTime() - new Date(b.start_time).getTime() ); - setUpcomingEvents(upcomingRes.data); - setPastEvents(sortedPastEvents); + const sortedPast = pastRes.data.sort( + (a, b) => + new Date(b.start_time).getTime() - new Date(a.start_time).getTime() + ); + + setUpcomingEvents(sortedUpcoming); + setPastEvents(sortedPast); } catch (error) { console.error("Error fetching events:", error); } @@ -51,27 +57,64 @@ const EventsPage = () => { fetchEvents(); }, []); + const theme = useTheme(); + const isXs = useMediaQuery(theme.breakpoints.down("sm")); + const isSm = useMediaQuery(theme.breakpoints.between("sm", "md")); + const isMd = useMediaQuery(theme.breakpoints.between("md", "lg")); + + const VISIBLE_COUNT = isXs ? 1 : isSm ? 2 : isMd ? 3 : 4; + + const handlePrev = () => { + setCurrentIndex((prev) => + prev === 0 ? Math.max(upcomingEvents.length - VISIBLE_COUNT, 0) : prev - 1 + ); + }; + + const handleNext = () => { + setCurrentIndex((prev) => + prev >= Math.max(upcomingEvents.length - VISIBLE_COUNT, 0) ? 0 : prev + 1 + ); + }; + + let visibleEvents: Event[] = []; + if (upcomingEvents.length <= VISIBLE_COUNT) { + visibleEvents = upcomingEvents; + } else { + visibleEvents = upcomingEvents.slice(currentIndex, currentIndex + VISIBLE_COUNT); + + if (visibleEvents.length < VISIBLE_COUNT) { + const wrapCount = VISIBLE_COUNT - visibleEvents.length; + visibleEvents = visibleEvents.concat(upcomingEvents.slice(0, wrapCount)); + } + } + return ( - + Events - {/* Categories */} + {/* Category Buttons */} {categories.map((cat) => { @@ -80,21 +123,14 @@ const EventsPage = () => { return ( + ); + })} + + {/* Past Events */} + + + Past Events + + + + {pastEvents.length > 0 ? ( + pastEvents.map((event) => ( + + + + )) + ) : ( + + No past events yet. + + )} + + + + ); +}; + +export default EventsPage; + + From 6d1c65ce93980c264fc81b0f385d6a51a7809edb Mon Sep 17 00:00:00 2001 From: gavinlin24 Date: Thu, 28 Aug 2025 14:37:05 -0700 Subject: [PATCH 08/16] Event styling + display order Fixed some styling with event cards. More recent past events are now displayed towards the top. --- frontend/src/components/NewEvents/Events.tsx | 182 +++++++++---------- 1 file changed, 87 insertions(+), 95 deletions(-) diff --git a/frontend/src/components/NewEvents/Events.tsx b/frontend/src/components/NewEvents/Events.tsx index 6635c015..06e7bd76 100644 --- a/frontend/src/components/NewEvents/Events.tsx +++ b/frontend/src/components/NewEvents/Events.tsx @@ -38,8 +38,11 @@ const EventsPage = () => { axios.get(upcomingEventsEndpoint), axios.get(pastEventsEndpoint), ]); - setUpcomingEvents(upcomingRes.data); - setPastEvents(pastRes.data); + const sortedPastEvents = pastRes.data.sort((a, b) => + new Date(b.start_time).getTime() - new Date(a.start_time).getTime() + ); + setUpcomingEvents(upcomingRes.data); + setPastEvents(sortedPastEvents); } catch (error) { console.error("Error fetching events:", error); } @@ -48,113 +51,102 @@ const EventsPage = () => { fetchEvents(); }, []); - const toPST = (iso: string) => - new Date(new Date(iso).toLocaleString("en-US", { timeZone: "America/Los_Angeles" }) - ); - return ( - - - Events - - - {/* Categories */} - - {categories.map((cat) => { - const isSelected = selectedCategory === cat; - const color = categoryColors[cat]; - return ( - - ); - })} - - {/* Past Events */} - + - Past Events + Events + {/* Categories */} - {pastEvents.length > 0 ? ( - pastEvents.map((event) => ( - - - - )) - ) : ( - - No past events yet. + {categories.map((cat) => { + const isSelected = selectedCategory === cat; + const color = categoryColors[cat]; + return ( + + ); + })} + + + {/* Past Events */} + + + Past Events - )} + + + {pastEvents.length > 0 ? ( + pastEvents.map((event) => ( + + + + + + )) + ) : ( + + No past events yet. + + )} + - - ); + ); }; -export default EventsPage; - - +export default EventsPage; \ No newline at end of file From 53bacf8beb8febc64695e47d87dfe92d914f6486 Mon Sep 17 00:00:00 2001 From: gavinlin24 Date: Fri, 12 Sep 2025 22:54:08 -0700 Subject: [PATCH 09/16] Upcoming Events Added upcoming events carousel. Fixed styling for both event sections. --- frontend/src/components/NewEvents/Events.tsx | 210 +++++++++++++++---- 1 file changed, 172 insertions(+), 38 deletions(-) diff --git a/frontend/src/components/NewEvents/Events.tsx b/frontend/src/components/NewEvents/Events.tsx index 06e7bd76..58b122fd 100644 --- a/frontend/src/components/NewEvents/Events.tsx +++ b/frontend/src/components/NewEvents/Events.tsx @@ -1,15 +1,16 @@ import React, { useState, useEffect } from "react"; -import { Box, Grid, Button, Typography } from "@mui/material"; +import { Box, Grid, Button, Typography, IconButton, useTheme, useMediaQuery } from "@mui/material"; +import { ArrowBackIosNewRounded, ArrowForwardIosRounded } from "@mui/icons-material"; import axios from "axios"; import EventCard from "./EventCard"; const categories = ["General", "Dev", "Open Source", "Innovate"]; const categoryColors: Record = { - General: "orange", - Dev: "teal", - "Open Source": "blue", - Innovate: "purple", + General: "#EBB111", + Dev: "#5DF0C4", + "Open Source": "#64C3E3", + Innovate: "#725DF0", }; interface Event { @@ -27,22 +28,27 @@ const EventsPage = () => { const [selectedCategory, setSelectedCategory] = useState(null); const [upcomingEvents, setUpcomingEvents] = useState([]); const [pastEvents, setPastEvents] = useState([]); + const [currentIndex, setCurrentIndex] = useState(0); useEffect(() => { - const upcomingEventsEndpoint = `${process.env.REACT_APP_BACKEND_URL}/api/v1/events?type=upcoming`; - const pastEventsEndpoint = `${process.env.REACT_APP_BACKEND_URL}/api/v1/events?type=past`; - const fetchEvents = async () => { try { const [upcomingRes, pastRes] = await Promise.all([ - axios.get(upcomingEventsEndpoint), - axios.get(pastEventsEndpoint), + axios.get(`${process.env.REACT_APP_BACKEND_URL}/api/v1/events?type=upcoming`), + axios.get(`${process.env.REACT_APP_BACKEND_URL}/api/v1/events?type=past`), ]); - const sortedPastEvents = pastRes.data.sort((a, b) => - new Date(b.start_time).getTime() - new Date(a.start_time).getTime() + + const sortedUpcoming = upcomingRes.data.sort( + (a, b) => + new Date(a.start_time).getTime() - new Date(b.start_time).getTime() ); - setUpcomingEvents(upcomingRes.data); - setPastEvents(sortedPastEvents); + const sortedPast = pastRes.data.sort( + (a, b) => + new Date(b.start_time).getTime() - new Date(a.start_time).getTime() + ); + + setUpcomingEvents(sortedUpcoming); + setPastEvents(sortedPast); } catch (error) { console.error("Error fetching events:", error); } @@ -51,27 +57,64 @@ const EventsPage = () => { fetchEvents(); }, []); + const theme = useTheme(); + const isXs = useMediaQuery(theme.breakpoints.down("sm")); + const isSm = useMediaQuery(theme.breakpoints.between("sm", "md")); + const isMd = useMediaQuery(theme.breakpoints.between("md", "lg")); + + const VISIBLE_COUNT = isXs ? 1 : isSm ? 2 : isMd ? 3 : 4; + + const handlePrev = () => { + setCurrentIndex((prev) => + prev === 0 ? Math.max(upcomingEvents.length - VISIBLE_COUNT, 0) : prev - 1 + ); + }; + + const handleNext = () => { + setCurrentIndex((prev) => + prev >= Math.max(upcomingEvents.length - VISIBLE_COUNT, 0) ? 0 : prev + 1 + ); + }; + + let visibleEvents: Event[] = []; + if (upcomingEvents.length <= VISIBLE_COUNT) { + visibleEvents = upcomingEvents; + } else { + visibleEvents = upcomingEvents.slice(currentIndex, currentIndex + VISIBLE_COUNT); + + if (visibleEvents.length < VISIBLE_COUNT) { + const wrapCount = VISIBLE_COUNT - visibleEvents.length; + visibleEvents = visibleEvents.concat(upcomingEvents.slice(0, wrapCount)); + } + } + return ( - + Events - {/* Categories */} + {/* Category Buttons */} {categories.map((cat) => { @@ -80,21 +123,14 @@ const EventsPage = () => { return (