From 5c1d54e1d1cdadf713cc6b00a6645388067c598b Mon Sep 17 00:00:00 2001 From: satasatalight Date: Fri, 19 Jun 2026 20:51:19 -0400 Subject: [PATCH 1/2] feat: add winners section --- components/WinnersSection.tsx | 113 ++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 components/WinnersSection.tsx diff --git a/components/WinnersSection.tsx b/components/WinnersSection.tsx new file mode 100644 index 0000000..5b1bc55 --- /dev/null +++ b/components/WinnersSection.tsx @@ -0,0 +1,113 @@ +'use client' + +import { useEffect, useState } from "react"; + +export interface Winner { + name: string; + challengeTitle: string; + projectTitle: string; + deadline: Date; + placement: number; +} + +const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; + +export default function WinnersSection({winners} : {winners: Array}) { + const [recentWinners, setRecentWinners] = useState>([]); + const [prevWinners, setPrevWinners] = useState>([]); + const [mostRecentDate, setRecentDate] = useState(new Date(0)); + + useEffect(() => { + let mostRecentDateNow = mostRecentDate; + + for(const winner of winners) + if(winner.deadline > mostRecentDateNow) + mostRecentDateNow = winner.deadline; + + const recentFilter = (winner: Winner) => { + if(winner.deadline >= mostRecentDateNow) + return winner; + } + + const prevFilter = (winner: Winner) => { + if(winner.deadline < mostRecentDateNow) + return winner; + } + + setRecentWinners(winners.filter(recentFilter)); + setPrevWinners(winners.filter(prevFilter)); + setRecentDate(mostRecentDateNow); + + }, [winners]); + + return
+

+ {monthNames[mostRecentDate.getMonth()]}'s Winners +

+ +
+ {recentWinners.map((winner, i) => + + )} +
+ +
+

+ 🕒 Previous Winners: +

+
+ {prevWinners.map((winner) => + + )} +
+
+ +
+} + +function WinnerCard({placement, name, projectTitle}: Winner){ + const placementColors = ["text-gold", "text-slate-400", "text-orange-800"]; + const placementColor = placement > 3 ? "" : placementColors[placement - 1]; + const [initals, setInitials] = useState(""); + + useEffect(() => { + const splitIndicators = [' ', '_', '-']; + let splitName: Array = []; + + // try each split indicator for finding initials + for(let i = 0; i < splitIndicators.length && splitName.length < 2; i++) + splitName = name.split(splitIndicators[i]); + + if(splitName.length < 2) + setInitials(name.charAt(0).toUpperCase()); + else + setInitials(splitName[0].charAt(0).toUpperCase() + splitName[1].charAt(0).toUpperCase()); + }, []) + + return
+
+
+ {initals} +
+
+ {name} +
+
+ {projectTitle} +
+
+ +
+ 🏆#{placement} +
+
; +} + +function PrevWinnerEntry({name, challengeTitle, deadline}: Winner){ + return
+

{monthNames[deadline.getMonth()]} {deadline.getFullYear()}

+

{name}

+

{challengeTitle}

+
; +} \ No newline at end of file From e7750377fdcb4782d498677813051ea24c98190e Mon Sep 17 00:00:00 2001 From: satasatalight Date: Sat, 20 Jun 2026 10:52:00 -0400 Subject: [PATCH 2/2] fix: address linter errors --- components/WinnersSection.tsx | 58 ++++++++++++++++------------------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/components/WinnersSection.tsx b/components/WinnersSection.tsx index 5b1bc55..3c86aed 100644 --- a/components/WinnersSection.tsx +++ b/components/WinnersSection.tsx @@ -1,6 +1,6 @@ 'use client' -import { useEffect, useState } from "react"; +import { useMemo } from "react"; export interface Winner { name: string; @@ -13,40 +13,41 @@ export interface Winner { const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; export default function WinnersSection({winners} : {winners: Array}) { - const [recentWinners, setRecentWinners] = useState>([]); - const [prevWinners, setPrevWinners] = useState>([]); - const [mostRecentDate, setRecentDate] = useState(new Date(0)); - - useEffect(() => { - let mostRecentDateNow = mostRecentDate; + const mostRecentDate = useMemo(() => { + let mostRecentDateNow = new Date(0); for(const winner of winners) if(winner.deadline > mostRecentDateNow) mostRecentDateNow = winner.deadline; + return mostRecentDateNow; + + }, [winners]); + const recentWinners = useMemo(() => { const recentFilter = (winner: Winner) => { - if(winner.deadline >= mostRecentDateNow) + if(winner.deadline >= mostRecentDate) return winner; } + return winners.filter(recentFilter); + + }, [winners, mostRecentDate]); + const prevWinners = useMemo(() => { const prevFilter = (winner: Winner) => { - if(winner.deadline < mostRecentDateNow) + if(winner.deadline < mostRecentDate) return winner; } + return winners.filter(prevFilter); - setRecentWinners(winners.filter(recentFilter)); - setPrevWinners(winners.filter(prevFilter)); - setRecentDate(mostRecentDateNow); - - }, [winners]); + }, [winners, mostRecentDate]); return

- {monthNames[mostRecentDate.getMonth()]}'s Winners + {monthNames[mostRecentDate.getMonth()]}'s Winners

- {recentWinners.map((winner, i) => + {recentWinners.map((winner) => )}
@@ -68,26 +69,19 @@ export default function WinnersSection({winners} : {winners: Array}) { function WinnerCard({placement, name, projectTitle}: Winner){ const placementColors = ["text-gold", "text-slate-400", "text-orange-800"]; const placementColor = placement > 3 ? "" : placementColors[placement - 1]; - const [initals, setInitials] = useState(""); - - useEffect(() => { - const splitIndicators = [' ', '_', '-']; - let splitName: Array = []; - - // try each split indicator for finding initials - for(let i = 0; i < splitIndicators.length && splitName.length < 2; i++) - splitName = name.split(splitIndicators[i]); - - if(splitName.length < 2) - setInitials(name.charAt(0).toUpperCase()); - else - setInitials(splitName[0].charAt(0).toUpperCase() + splitName[1].charAt(0).toUpperCase()); - }, []) + + // regex setup for initials + const clean = name.replace(/( |_|-|\/|\\|#|\.)/, " "); // remove common name seperators + const match = clean.match(/([a-z A-Z])\w+\s([a-z A-Z])\w*\s*([a-z A-Z])*(?=,*)/); // find and place initials in array + + // if match exists, it MUST have at least 3 elements in array + // 0 index of match is original string + const initials = ((match) ? match[1] + match[2] : name.charAt(0)).toUpperCase(); return
- {initals} + {initials}
{name}