-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.tsx
280 lines (241 loc) · 8.4 KB
/
index.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
import { useEffect, useState } from 'react'
import { GetStaticProps } from 'next'
import { ShowProps } from '~/utils/types'
import generateRssFeed from '~/utils/generate-feed'
import { allShows } from '~/data/allShows'
import { SiteMeta } from '~/components/meta'
import { VenueFilter } from '~/components/venue-filter'
import { ShowCard } from '~/components/show-card'
type ShowsByWeekProps = {
[weekStartDate: string]: ShowProps[]
}
type GroupedShowsProps = {
weekStartDate: Date
shows: ShowProps[]
}
type PageProps = {
shows: ShowProps[]
}
export const getStaticProps: GetStaticProps<PageProps> = async () => {
generateRssFeed()
return {
props: {
shows: allShows,
},
}
}
export default function Page({ shows }: PageProps) {
// Animate header elements on scroll
const [animateOnScroll, setAnimateOnScroll] = useState(false)
useEffect(() => {
const handleScroll = () => {
const scrollThreshold = window.innerWidth < 480 ? 25 : 30
setAnimateOnScroll(window.scrollY > scrollThreshold)
}
window.addEventListener('scroll', handleScroll)
return () => {
window.removeEventListener('scroll', handleScroll)
}
}, [])
// Overwrite specific venue names to group them together
const venueMapping: { [key: string]: string } = {
'Beachland Ballroom': 'Beachland',
'Beachland Tavern': 'Beachland',
'Mahall’s Apartment': 'Mahall’s',
'The Roxy at Mahall’s': 'Mahall’s',
}
// Create an array of all unique venues
const allVenues = Array.from(
new Set(shows.map((show) => venueMapping[show.venue] || show.venue)),
)
// Initialize state for selected venues
const [selectedVenues, setSelectedVenues] = useState<string[]>(allVenues)
const handleVenueToggle = (venue: string) => {
const mappedVenue = venueMapping[venue] || venue
setSelectedVenues((prevSelectedVenues) =>
prevSelectedVenues.includes(mappedVenue)
? prevSelectedVenues.filter((v) => v !== mappedVenue)
: [...prevSelectedVenues, mappedVenue],
)
}
// Select and deselect all button logic
const handleSelectAll = () => {
setSelectedVenues([...allVenues])
}
const handleDeselectAll = () => {
setSelectedVenues([])
}
// Filter shows by selected venues
const filteredShows = shows.filter(
(show) =>
show.date !== '' &&
selectedVenues.includes(venueMapping[show.venue] || show.venue),
)
// Ignore shows that have already happened
const filteredCurrentShows = filteredShows.filter((show) => {
const showDate = new Date(show.date)
// Get the current date at 4am ET
const currentDate = new Date()
currentDate.setHours(4, 0, 0, 0)
currentDate.toLocaleString('en-US', { timeZone: 'America/New_York' })
// Convert the show date to US Eastern Time
const showDateInET = new Date(
showDate.toLocaleString('en-US', { timeZone: 'America/New_York' }),
)
return showDateInET >= currentDate
})
// Sort filtered shows chronologically by show date
const sortedFilteredShows = filteredCurrentShows.sort(
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(),
)
// Reduce the sorted, filtered shows
const days = 7
const monday = 1 // zero-indexed
const showsByWeek = sortedFilteredShows.reduce((acc, show) => {
// Get a show’s first day of the week (starting on Monday)
const weekStartDate = new Date(show.date)
weekStartDate.setHours(0, 0, 0, 0)
// Calculate # of days to subtract to get to Monday
const daysToSubtract = (weekStartDate.getDay() + (days - monday)) % days
// Subtract that many days to always get Mondays
weekStartDate.setDate(weekStartDate.getDate() - daysToSubtract)
// Convert the first day of the week to ISO
const weekStartDateAsString = weekStartDate.toISOString()
// Make an empty array if the first day of the week hasn’t happened yet
// Assert expected type of the accumulator
if (!acc[weekStartDateAsString]) {
acc[weekStartDateAsString] = [] as ShowProps[]
}
// Push the show into its corresponding week array
acc[weekStartDateAsString]!.push(show)
// Return the accumulator (that *accumulates* the grouped-by-week shows)
return acc
}, {} as ShowsByWeekProps)
// Convert the grouped shows into an array of `{ weekStartDate, shows }` objects
const groupedShows: GroupedShowsProps[] = Object.entries(showsByWeek).map(
([weekStart, weekShows]) => ({
weekStartDate: new Date(weekStart),
shows: weekShows,
}),
)
const renderGroupedShows = () => {
if (groupedShows.length === 0) {
return (
<div className='empty-state'>
<h2>No shows available</h2>
<p>Please select at least one venue to view upcoming shows.</p>
</div>
)
} else {
return groupedShows.map(({ weekStartDate, shows }) => {
const today = new Date()
const dayOfWeek = today.getDay()
const weekStartToTodayDiff = Math.floor(
(weekStartDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24),
)
let groupPrefix = ''
let groupLabel = weekStartDate.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
})
if (
(dayOfWeek === 5 || dayOfWeek === 6 || dayOfWeek === 0) &&
weekStartToTodayDiff >= -7 &&
weekStartToTodayDiff < 0
) {
groupLabel = 'This weekend'
} else if (weekStartToTodayDiff >= -6 && weekStartToTodayDiff < 0) {
groupLabel = 'This week'
} else if (weekStartToTodayDiff >= 0 && weekStartToTodayDiff < 6) {
groupLabel = 'Next week'
} else {
groupPrefix = 'Week of'
}
return (
<section key={weekStartDate.toISOString()} className='show-grouping'>
<h2>
{groupPrefix && (
<span className='font-mono text-sm font-medium uppercase md:text-lg'>
{groupPrefix}
</span>
)}
<span className='font-medium text-zinc-500 dark:text-zinc-400'>
{groupLabel}
</span>
</h2>
<ul>
{shows.map((show, i) => (
<ShowCard key={i} show={show} i={0} />
))}
</ul>
</section>
)
})
}
}
const dateParts =
process.env.NEXT_PUBLIC_LAST_UPDATED_AT?.split('-').map(Number) || []
const year = dateParts[0] ?? 0
const month = dateParts[1] ? dateParts[1] - 1 : 0
const day = dateParts[2] ?? 0
const dateObj = new Date(year, month, day)
const isValidDate = (dateStr: string) => {
const regex = /^\d{4}-\d{2}-\d{2}$/
return regex.test(dateStr)
}
return (
<div className='body'>
<SiteMeta />
<header>
<div
className={`${
animateOnScroll
? 'translate-y-[-6em] opacity-0'
: 'mt-0.5 opacity-100 md:mt-1'
}`}
>
<h1 className='inline'>Upcoming shows in CLE</h1>
{process.env.NEXT_PUBLIC_LAST_UPDATED_AT &&
isValidDate(process.env.NEXT_PUBLIC_LAST_UPDATED_AT.trim()) && (
<span>
{' '}
· Last updated{' '}
<time dateTime={dateObj.toISOString()}>
{dateObj.toLocaleString('en-US', {
timeZone: 'America/New_York',
month: 'short',
day: 'numeric',
})}
</time>
</span>
)}
</div>
</header>
<div
className={`dropdown-container ${
animateOnScroll
? 'translate-y-0'
: 'translate-y-[1.25em] md:translate-y-[2em]'
}`}
>
<VenueFilter
venues={allVenues} // array of all unique venues names
selectedVenues={selectedVenues} // array of selected/checked
onVenueToggle={handleVenueToggle} // function to handle toggling
onSelectAll={handleSelectAll}
onDeselectAll={handleDeselectAll}
checked={false}
/>
</div>
<main className='main'>{renderGroupedShows()}</main>
<footer>
<p>
All data is pulled from the venues’ individual websites and aggregated
here. No ownership of information is claimed nor implied.
</p>
<p>Support your scene and take care of each other.</p>
</footer>
</div>
)
}