Skip to content

Commit 6124ea0

Browse files
authored
Merge branch 'development' into matchFormUpdate
2 parents 617ec4a + 3558577 commit 6124ea0

13 files changed

Lines changed: 788 additions & 171 deletions

File tree

app/api/proxy-html/route.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { NextResponse } from 'next/server'
2+
3+
export async function GET(request) {
4+
const { searchParams } = new URL(request.url)
5+
const url = searchParams.get('url')
6+
7+
if (!url) {
8+
return NextResponse.json(
9+
{ error: 'URL parameter is required' },
10+
{ status: 400 }
11+
)
12+
}
13+
14+
// Validate that it's a Firebase Storage URL for security
15+
if (!url.includes('firebasestorage.googleapis.com')) {
16+
return NextResponse.json({ error: 'Invalid URL' }, { status: 403 })
17+
}
18+
19+
try {
20+
const response = await fetch(url)
21+
if (!response.ok) {
22+
return NextResponse.json(
23+
{ error: `Failed to fetch: ${response.status}` },
24+
{ status: response.status }
25+
)
26+
}
27+
28+
const html = await response.text()
29+
return new NextResponse(html, {
30+
status: 200,
31+
headers: {
32+
'Content-Type': 'text/html'
33+
}
34+
})
35+
} catch (error) {
36+
console.error('Proxy fetch error:', error)
37+
return NextResponse.json(
38+
{ error: 'Failed to fetch content' },
39+
{ status: 500 }
40+
)
41+
}
42+
}

app/components/Dashboard.js

Lines changed: 134 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,53 @@ const formatMatches = (matches) => {
3939
.sort((a, b) => new Date(b.matchDate) - new Date(a.matchDate))
4040
}
4141

42+
// Helper function to determine which season a match belongs to
43+
const getMatchSeason = (matchDate) => {
44+
const date = new Date(matchDate)
45+
const month = date.getMonth() + 1 // 1-12
46+
const year = date.getFullYear()
47+
48+
// College tennis seasons:
49+
// Fall (Aug-Nov): part of next year's season (e.g., Aug 2024 = 2024-2025)
50+
// Spring (Jan-May): part of current year's season (e.g., Jan 2025 = 2024-2025)
51+
52+
if (month >= 8) {
53+
// Aug-Dec: part of current-next season
54+
return `${year}-${year + 1}`
55+
} else if (month <= 5) {
56+
// Jan-May: part of previous-current season
57+
return `${year - 1}-${year}`
58+
}
59+
// June-July: off-season, still count as previous-current season
60+
return `${year - 1}-${year}`
61+
}
62+
63+
// Get current season based on today's date
64+
const getCurrentSeason = () => {
65+
return getMatchSeason(new Date().toISOString().split('T')[0])
66+
}
67+
68+
// Get all unique seasons from matches
69+
const getAvailableSeasons = (matches) => {
70+
const seasons = new Set()
71+
const currentYear = new Date().getFullYear()
72+
73+
matches.forEach((match) => {
74+
const season = getMatchSeason(match.matchDate)
75+
76+
// Only include seasons from the last 10 years
77+
const seasonStartYear = parseInt(season.split('-')[0])
78+
if (
79+
seasonStartYear >= currentYear - 30 &&
80+
seasonStartYear <= currentYear + 1
81+
) {
82+
seasons.add(season)
83+
}
84+
})
85+
86+
return Array.from(seasons).sort().reverse() // Most recent first
87+
}
88+
4289
// Memoized CarouselItem with fixed dimensions but original logo container
4390
const CarouselItem = React.memo(({ match, isSelected, onClick, logo }) => {
4491
const matchKey = match.matchDetails.duel
@@ -116,10 +163,42 @@ const SearchBox = React.memo(({ searchTerm, onSearch, onClear }) => {
116163

117164
SearchBox.displayName = 'SearchBox'
118165

166+
// Season Filter Component
167+
const SeasonFilter = React.memo(
168+
({ availableSeasons, selectedSeason, onSeasonChange }) => {
169+
return (
170+
<div className={styles.seasonFilterContainer}>
171+
<div className={styles.seasonButtons}>
172+
<button
173+
className={`${styles.seasonButton} ${selectedSeason === 'all' ? styles.activeSeason : ''}`}
174+
onClick={() => onSeasonChange('all')}
175+
>
176+
All Seasons
177+
</button>
178+
{availableSeasons
179+
.slice()
180+
.reverse()
181+
.map((season) => (
182+
<button
183+
key={season}
184+
className={`${styles.seasonButton} ${selectedSeason === season ? styles.activeSeason : ''}`}
185+
onClick={() => onSeasonChange(season)}
186+
>
187+
{season}
188+
</button>
189+
))}
190+
</div>
191+
</div>
192+
)
193+
}
194+
)
195+
196+
SeasonFilter.displayName = 'SeasonFilter'
197+
119198
// MatchSection with placeholder heights
120199
const MatchSection = React.memo(
121-
({ matchKey, formattedMatches, onTileClick }) => {
122-
const singlesMatches = formattedMatches.filter(
200+
({ matchKey, seasonFilteredMatches, onTileClick }) => {
201+
const singlesMatches = seasonFilteredMatches.filter(
123202
(match) =>
124203
match.singles &&
125204
((match.matchDetails.duel &&
@@ -129,7 +208,7 @@ const MatchSection = React.memo(
129208
matchKey === `_#${match.matchDetails.event}`))
130209
)
131210

132-
const doublesMatches = formattedMatches.filter(
211+
const doublesMatches = seasonFilteredMatches.filter(
133212
(match) =>
134213
!match.singles &&
135214
((match.matchDetails.duel &&
@@ -142,7 +221,7 @@ const MatchSection = React.memo(
142221
const [matchName] = matchKey.split('#')
143222
const displayName =
144223
matchName === '_'
145-
? formattedMatches.find(
224+
? seasonFilteredMatches.find(
146225
(match) =>
147226
!match.matchDetails.duel && match.matchDetails.event === matchName
148227
)?.matchDetails.event || matchName
@@ -231,6 +310,7 @@ const Dashboard = () => {
231310
const [selectedMatchSets, setSelectedMatchSets] = useState([])
232311
const [isMobile, setIsMobile] = useState(false)
233312
const [isLoaded, setIsLoaded] = useState(false)
313+
const [selectedSeason, setSelectedSeason] = useState(getCurrentSeason())
234314

235315
const [searchIndex, setSearchIndex] = useState(null)
236316
// Calculate formatted matches once
@@ -243,11 +323,33 @@ const Dashboard = () => {
243323
return formatted
244324
}, [matches, isLoaded])
245325

246-
const uniqueMatches = useMemo(
247-
() => getUniqueMatches(formattedMatches, cleanTeamName),
248-
[formattedMatches]
326+
// Filter matches by selected season
327+
const seasonFilteredMatches = useMemo(() => {
328+
if (selectedSeason === 'all') {
329+
return formattedMatches
330+
}
331+
return formattedMatches.filter(
332+
(match) => getMatchSeason(match.matchDate) === selectedSeason
333+
)
334+
}, [formattedMatches, selectedSeason])
335+
336+
const handleSeasonChange = useCallback(
337+
(season) => {
338+
setSelectedSeason(season)
339+
setSelectedMatchSets([]) // Clear carousel selections when season changes
340+
},
341+
[setSelectedSeason, setSelectedMatchSets]
249342
)
250343

344+
// Get available seasons from all matches
345+
const availableSeasons = useMemo(() => {
346+
return getAvailableSeasons(formattedMatches)
347+
}, [formattedMatches])
348+
349+
const uniqueMatches = useMemo(() => {
350+
return getUniqueMatches(seasonFilteredMatches, cleanTeamName)
351+
}, [seasonFilteredMatches])
352+
251353
// Mobile detection useEffect
252354
useEffect(() => {
253355
const checkIfMobile = () => setIsMobile(window.innerWidth < 400)
@@ -259,11 +361,11 @@ const Dashboard = () => {
259361
return () => resizeObserver.disconnect()
260362
}, [])
261363

262-
// Create search index after component mounts and formattedMatches is available
364+
// Create search index after component mounts and seasonFilteredMatches is available
263365
useEffect(() => {
264-
if (formattedMatches.length && !searchIndex) {
366+
if (seasonFilteredMatches.length && !searchIndex) {
265367
const timer = setTimeout(() => {
266-
const fuse = new Fuse(formattedMatches, {
368+
const fuse = new Fuse(seasonFilteredMatches, {
267369
keys: searchableProperties,
268370
threshold: 0.4
269371
})
@@ -272,7 +374,17 @@ const Dashboard = () => {
272374

273375
return () => clearTimeout(timer)
274376
}
275-
}, [formattedMatches, searchIndex]) // Now formattedMatches is defined before this useEffect
377+
}, [seasonFilteredMatches, searchIndex, searchableProperties])
378+
379+
useEffect(() => {
380+
if (seasonFilteredMatches.length) {
381+
const fuse = new Fuse(seasonFilteredMatches, {
382+
keys: searchableProperties,
383+
threshold: 0.4
384+
})
385+
setSearchIndex(fuse)
386+
}
387+
}, [selectedSeason, searchableProperties])
276388

277389
// Filtered match sets calculation using the searchIndex from state
278390
const filteredMatchSets = useMemo(() => {
@@ -304,14 +416,14 @@ const Dashboard = () => {
304416
if (selectedMatchSets.length > 0) return selectedMatchSets
305417
return [
306418
...new Set(
307-
formattedMatches.map((match) => {
419+
seasonFilteredMatches.map((match) => {
308420
return match.matchDetails.duel
309421
? `${match.matchDate}#${match.teams.opponentTeam}`
310422
: `_#${match.matchDetails.event}`
311423
})
312424
)
313425
]
314-
}, [searchTerm, filteredMatchSets, selectedMatchSets, formattedMatches])
426+
}, [searchTerm, filteredMatchSets, selectedMatchSets])
315427

316428
const handleTileClick = useCallback(
317429
(videoId) => {
@@ -347,8 +459,14 @@ const Dashboard = () => {
347459
</div>
348460
</header>
349461

462+
<SeasonFilter
463+
availableSeasons={availableSeasons}
464+
selectedSeason={selectedSeason}
465+
onSeasonChange={handleSeasonChange}
466+
/>
467+
350468
<div className={styles.carousel}>
351-
{!formattedMatches.length
469+
{!seasonFilteredMatches.length
352470
? Array(10)
353471
.fill(0)
354472
.map((_, i) => (
@@ -386,7 +504,7 @@ const Dashboard = () => {
386504
<div className={styles.noMatches}>No matches found</div>
387505
) : (
388506
displayMatchSets.map((matchKey, index) => {
389-
const singlesMatches = formattedMatches.filter(
507+
const singlesMatches = seasonFilteredMatches.filter(
390508
(match) =>
391509
match.singles &&
392510
((match.matchDetails.duel &&
@@ -396,7 +514,7 @@ const Dashboard = () => {
396514
matchKey === `_#${match.matchDetails.event}`))
397515
)
398516

399-
const doublesMatches = formattedMatches.filter(
517+
const doublesMatches = seasonFilteredMatches.filter(
400518
(match) =>
401519
!match.singles &&
402520
((match.matchDetails.duel &&

0 commit comments

Comments
 (0)