diff --git a/src/resources/data/events.ts b/src/resources/data/events.ts index 2b3f0ec..f635dec 100644 --- a/src/resources/data/events.ts +++ b/src/resources/data/events.ts @@ -10,6 +10,9 @@ import examCram from "@/resources/images/events/ExamCram.jpg"; import devchamps from "@/resources/images/events/devchamps.jpeg"; import labtours from "@/resources/images/events/Labtours.jpg"; import codingKickoff from "@/resources/images/events/Coding Kickoff.png"; +import codingKickoffUpcoming from "@/resources/images/events/CodingKickoffUpcoming.png"; +import devgames1 from "@/resources/images/events/devgames1.png"; +import devgames2 from "@/resources/images/events/devgames2.png"; export { TERMS_ORDER } from "./types"; @@ -20,7 +23,7 @@ export const EVENTS: EventData[] = [ description: "Each term kicks off with a general meeting where we update students on .devClub's vision, goals, and planned events.", term: ["Summer", "Fall", "Winter"], - images: generalmeeting, + image: generalmeeting, recurring: true, }, { @@ -29,7 +32,7 @@ export const EVENTS: EventData[] = [ description: "Summer means a .devClub leisure trip: a chance to decompress after exams. We've gone to Gimli, Kenora, and, most recently, Pinawa.", term: ["Summer"], - images: AWTY, + image: AWTY, recurring: true, }, { @@ -38,8 +41,12 @@ export const EVENTS: EventData[] = [ description: "Kickstart your coding journey with a hands-on workshop! Learn the basics, set up your environment, and explore tech career paths while meeting fellow students.", term: ["Fall"], - images: codingKickoff, + image: codingKickoff, recurring: true, + date: "2025-09-18T17:00:00", + location: "EITC E2-125", + rsvp: "https://docs.google.com/forms/d/e/1FAIpQLSfSVtcqE3yz4dnmpBs7bLpXAj7nZ2VNsDoeZnFIO5IKo1hX7g/viewform", + upcomingImage: codingKickoffUpcoming, }, { id: "workshops", @@ -47,46 +54,51 @@ export const EVENTS: EventData[] = [ description: ".devClub runs workshops every semester on all kinds of cool topics! We've played around with GitHub, built games in Unity, designed in Figma, hosted résumés online, and plenty more!", term: ["Fall", "Winter"], - images: workshop, + image: workshop, recurring: true, }, - { id: "rendezvous", title: "renDEVous", description: "Every month we host renDEVous, a fun hangout where you can game, present cool projects, or just kick back with the squad.", term: ["Summer", "Fall", "Winter"], - images: rendevouz, + image: rendevouz, recurring: true, }, { id: "devgames", - title: ".devGames", + title: "devGames", description: - "Our new game development workshop series runs every month, offering students a chance to learn the fundamentals of game design and development step by step. ", + "Our new game development workshop series runs every month, offering students a chance to learn the fundamentals of game design and development step by step.", term: ["Fall"], - images: devgames, + image: devgames, recurring: true, + upcomingTitle: [".devGames Level 1", ".devGames Level 2"], + upcomingDescription: [ + "Level 1: Learn the basics of game development in Unity. We will be building a simple 2D game from scratch.", + "Level 2: Join us for this special workshop in collaboration with UBISOFT and CSSA to learn about the fundamentals of Game Design.", + ], + upcomingImage: [devgames1, devgames2], + date: ["2025-09-25T18:00:00", "2025-10-01T18:00:00"], + location: ["EITC E2-110", "EITC E3-270"], }, - { id: "exam-crams", title: "Exam Crams", description: "Need a boost before finals? Our Fall Exam Crams offer guided study support for first and second year courses so you can prep smarter, not harder.", term: ["Fall"], - images: examCram, + image: examCram, recurring: true, }, - { id: "devhacks", title: ".devHacks", description: ".devHacks is an annual hackathon hosted by .devClub for students to showcase their creativity and endurance over a period of 24 hours of fun filled development!", term: ["Winter"], - images: devhacks, + image: devhacks, path: "/devhacks", recurring: true, }, @@ -96,24 +108,23 @@ export const EVENTS: EventData[] = [ description: "Take a peek inside CS labs, meet profs, and see cool research in action!", term: ["Fall"], - images: labtours, + image: labtours, }, { id: "devchamps", - title: ".devChamps ", + title: ".devChamps", description: ".devChamps is a two to three month project-based programming battle featuring a new challenge every season.", term: ["Fall", "Winter"], - images: devchamps, + image: devchamps, path: "/devchamps", }, - { id: "battlesnake", title: "Battle Snake", description: "Code your own snake, outsmart your friends, and see who slithers to the top!", term: ["Fall"], - images: battlesnake, + image: battlesnake, }, ]; diff --git a/src/resources/data/types.ts b/src/resources/data/types.ts index 6a17c61..585b4ae 100644 --- a/src/resources/data/types.ts +++ b/src/resources/data/types.ts @@ -54,8 +54,15 @@ export interface EventData { title: string; term?: Term[]; description: string; - images: string; + image: string; path?: string; recurring?: boolean; + //use this for upcoming events in Events.ts, can be array or just string to setup multiple events of the same thing + upcomingTitle?: string | string[]; + upcomingDescription?: string | string[]; + upcomingImage?: string | string[]; + date?: string | string[]; + location?: string | string[]; + rsvp?: string | string[]; } export const TERMS_ORDER: Term[] = ["Summer", "Fall", "Winter"]; diff --git a/src/resources/images/events/CodingKickoffUpcoming.png b/src/resources/images/events/CodingKickoffUpcoming.png new file mode 100644 index 0000000..832502d Binary files /dev/null and b/src/resources/images/events/CodingKickoffUpcoming.png differ diff --git a/src/resources/images/events/devgames1.png b/src/resources/images/events/devgames1.png new file mode 100644 index 0000000..e4f02ca Binary files /dev/null and b/src/resources/images/events/devgames1.png differ diff --git a/src/resources/images/events/devgames2.png b/src/resources/images/events/devgames2.png new file mode 100644 index 0000000..051037f Binary files /dev/null and b/src/resources/images/events/devgames2.png differ diff --git a/src/routes/Events.tsx b/src/routes/Events.tsx index 87f1be5..2c8aba9 100644 --- a/src/routes/Events.tsx +++ b/src/routes/Events.tsx @@ -6,16 +6,126 @@ import "@/styles/Events.scss"; const TERMS: Term[] = ["Summer", "Fall", "Winter"]; function Events() { - const [active, setActive] = useState("All"); + const [active, setActive] = useState("All"); const isAll = active === "All"; const matches = (evTerms: Term[] | undefined, term: Term | "All") => { - if (term === "All") return true; - if (!evTerms || evTerms.length === 0) return true; + if (term === "All") { + return true; + } + if (!evTerms || evTerms.length === 0) { + return true; + } return evTerms.includes(term as Term); }; + // Build filtered events const filteredEvents = useMemo(() => { + if (active === "Upcoming") { + const expanded = EVENTS.flatMap((ev) => { + // Titles + let titles: string[]; + if (Array.isArray(ev.upcomingTitle)) { + titles = ev.upcomingTitle; + } else if (ev.upcomingTitle) { + titles = [ev.upcomingTitle]; + } else { + titles = [ev.title]; + } + + // Descriptions + let descriptions: string[]; + if (Array.isArray(ev.upcomingDescription)) { + descriptions = ev.upcomingDescription; + } else if (ev.upcomingDescription) { + descriptions = [ev.upcomingDescription]; + } else { + descriptions = [ev.description]; + } + + // Images + let images: string[]; + if (Array.isArray(ev.upcomingImage)) { + images = ev.upcomingImage; + } else if (ev.upcomingImage) { + images = [ev.upcomingImage]; + } else { + images = [ev.image]; + } + + // Dates + let dates: string[]; + if (Array.isArray(ev.date)) { + dates = ev.date; + } else if (ev.date) { + dates = [ev.date]; + } else { + dates = []; + } + + // Locations + let locations: string[]; + if (Array.isArray(ev.location)) { + locations = ev.location; + } else if (ev.location) { + locations = [ev.location]; + } else { + locations = []; + } + + // RSVPs + let rsvps: string[]; + if (Array.isArray(ev.rsvp)) { + rsvps = ev.rsvp; + } else if (ev.rsvp) { + rsvps = [ev.rsvp]; + } else { + rsvps = []; + } + + const maxLen = Math.max( + titles.length, + descriptions.length, + images.length, + dates.length, + locations.length, + rsvps.length + ); + + return Array.from({ length: maxLen }).map((_, i) => { + let title = titles[i] || titles[0]; + let description = descriptions[i] || descriptions[0]; + let image = images[i] || images[0]; + let date: string | undefined = dates[i]; + let location: string | undefined = locations[i]; + let rsvp: string | undefined = rsvps[i]; + + return { + ...ev, + title, + description, + image, + date, + location, + rsvp, + }; + }); + }); + + const now = new Date(); + const upcomingOnly = expanded.filter((ev) => { + if (!ev.date) return false; + return new Date(ev.date) > now; + }); + + return upcomingOnly.sort((a, b) => { + if (a.date && b.date) { + return new Date(a.date).getTime() - new Date(b.date).getTime(); + } + return 0; + }); + } + return EVENTS.filter((ev) => matches(ev.term as Term[] | undefined, active) ); @@ -45,14 +155,19 @@ function Events() { }; const headingText = () => { - if (isAll) return "Events"; + if (active === "Upcoming") { + return "Upcoming Events"; + } + if (isAll) { + return "Events"; + } return active as Term; }; return (
- {(["All", ...TERMS] as const).map((term) => { + {(["Upcoming", "All", ...TERMS] as const).map((term) => { const isActive = active === term; let chipClass = "chip"; @@ -79,16 +194,16 @@ function Events() {
- {filteredEvents.map((ev) => { + {filteredEvents.map((ev, idx) => { const terms = (ev.term as Term[] | undefined) ?? []; let media: React.ReactNode; - if (ev.images) { + if (ev.image) { media = ( {ev.title} ); @@ -96,41 +211,57 @@ function Events() { media =
; } - let termTags: React.ReactNode = null; - if (terms.length > 0) { - termTags = ( -
- {terms.map((t) => ( - - {t} - - ))} -
- ); - } - - let detailsBtn: React.ReactNode = null; - if (ev.path) { - detailsBtn = ( - - Details - - ); - } - - let badge: React.ReactNode = null; - if (ev.recurring) { - badge = ✔️; - } - return ( -
- {badge} +
+ {active !== "Upcoming" && ev.recurring && ( + ✔️ + )} {media} +

{ev.title}

- {termTags} + + {active !== "Upcoming" && terms.length > 0 && ( +
+ {terms.map((t) => ( + + {t} + + ))} +
+ )} + + {active === "Upcoming" && + ev.date && + typeof ev.date === "string" && ( +
+

+ 📍 {ev.location ? ev.location : "TBA"}
+ 🗓 {new Date(ev.date).toLocaleDateString()}{" "} + {new Date(ev.date).toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + })} +

+ {ev.rsvp && typeof ev.rsvp === "string" && ( + + RSVP + + )} +
+ )} +

{ev.description}

- {detailsBtn} + + {active !== "Upcoming" && ev.path && ( + + Details + + )}
); })}