1+ /** @jsxImportSource theme-ui */
2+ import Meta from '@hackclub/meta'
3+ import Head from 'next/head'
4+ import { Box , Heading , Text , Link as ThemeLink , Input } from 'theme-ui'
5+ import { useState , useMemo } from 'react'
6+ import channels from '../channels.json'
7+ import Footer from '../components/footer'
8+ import ForceTheme from '../components/force-theme'
9+ import Nav from '../components/nav'
10+
11+ const formatName = ( str ) =>
12+ str . replace ( / - / g, ' ' ) . replace ( / \b \w / g, ( c ) => c . toUpperCase ( ) )
13+
14+ const getTypeLabel = ( type ) => {
15+ if ( type === 'us-state' ) return 'US State'
16+ if ( type === 'island' ) return 'Island'
17+ return 'Country'
18+ }
19+
20+ const ChannelCard = ( { channel } ) => (
21+ < Box
22+ as = "a"
23+ href = { channel . url }
24+ target = "_blank"
25+ rel = "noopener noreferrer"
26+ sx = { {
27+ display : 'flex' ,
28+ flexDirection : 'column' ,
29+ alignItems : 'center' ,
30+ justifyContent : 'center' ,
31+ gap : 2 ,
32+ p : 3 ,
33+ bg : 'white' ,
34+ borderRadius : '12px' ,
35+ border : '1px solid' ,
36+ borderColor : 'smoke' ,
37+ borderTop : '4px solid' ,
38+ borderTopColor : 'primary' ,
39+ textDecoration : 'none' ,
40+ transition : 'all 0.2s ease-in-out' ,
41+ cursor : 'pointer' ,
42+ '&:hover' : {
43+ transform : 'translateY(-4px)' ,
44+ boxShadow : '0 8px 24px rgba(236, 55, 80, 0.15)' ,
45+ borderTopColor : 'orange'
46+ }
47+ } }
48+ >
49+ < Text
50+ sx = { {
51+ fontWeight : 800 ,
52+ fontSize : '1rem' ,
53+ color : 'primary' ,
54+ textAlign : 'center' ,
55+ lineHeight : 1.3
56+ } }
57+ >
58+ #{ channel . channel }
59+ </ Text >
60+ < Text
61+ sx = { {
62+ fontSize : '0.75rem' ,
63+ color : 'muted' ,
64+ fontWeight : 500 ,
65+ textTransform : 'uppercase' ,
66+ letterSpacing : '0.05em'
67+ } }
68+ >
69+ { getTypeLabel ( channel . type ) }
70+ </ Text >
71+ </ Box >
72+ )
73+
74+ const FilterButton = ( { active, onClick, children } ) => (
75+ < Box
76+ as = "button"
77+ onClick = { onClick }
78+ sx = { {
79+ px : 4 ,
80+ py : 2 ,
81+ borderRadius : '999px' ,
82+ border : '2px solid' ,
83+ borderColor : active ? 'primary' : 'smoke' ,
84+ bg : active ? 'primary' : 'white' ,
85+ color : active ? 'white' : 'slate' ,
86+ fontWeight : 700 ,
87+ fontSize : '0.9rem' ,
88+ cursor : 'pointer' ,
89+ fontFamily : 'inherit' ,
90+ transition : 'all 0.15s ease' ,
91+ '&:hover' : {
92+ borderColor : 'primary' ,
93+ color : active ? 'white' : 'primary'
94+ }
95+ } }
96+ >
97+ { children }
98+ </ Box >
99+ )
100+
101+ const ChannelsPage = ( ) => {
102+ const [ search , setSearch ] = useState ( '' )
103+ const [ filter , setFilter ] = useState ( 'all' )
104+
105+ const filtered = useMemo ( ( ) => {
106+ return channels . filter ( ( c ) => {
107+ const matchesSearch =
108+ c . channel . includes ( search . toLowerCase ( ) ) ||
109+ c . match . includes ( search . toLowerCase ( ) )
110+ const matchesFilter =
111+ filter === 'all' ||
112+ ( filter === 'country' && c . type === 'country' ) ||
113+ ( filter === 'us-state' && c . type === 'us-state' ) ||
114+ ( filter === 'island' && c . type === 'island' )
115+ return matchesSearch && matchesFilter
116+ } )
117+ } , [ search , filter ] )
118+
119+ const counts = useMemo ( ( ) => ( {
120+ all : channels . length ,
121+ country : channels . filter ( ( c ) => c . type === 'country' ) . length ,
122+ 'us-state' : channels . filter ( ( c ) => c . type === 'us-state' ) . length ,
123+ island : channels . filter ( ( c ) => c . type === 'island' ) . length
124+ } ) , [ ] )
125+
126+ return (
127+ < Box
128+ sx = { {
129+ backgroundImage : 'url(/pattern.svg)' ,
130+ backgroundRepeat : 'repeat' ,
131+ backgroundAttachment : 'fixed' ,
132+ minHeight : '100vh' ,
133+ backgroundColor : 'snow'
134+ } }
135+ >
136+ < Meta
137+ as = { Head }
138+ name = "Regional Channels – Hack Club Slack"
139+ description = "Browse all regional Hack Club Slack channels by country and US state. Find and join your local community!"
140+ />
141+ < ForceTheme theme = "light" />
142+ < Nav />
143+
144+ { /* Hero */ }
145+ < Box
146+ sx = { {
147+ backgroundImage : ( t ) => t . util . gx ( 'orange' , 'red' ) ,
148+ pt : [ '7rem' , '8rem' ] ,
149+ pb : [ '3rem' , '4rem' ] ,
150+ px : [ '1.5rem' , '3rem' ] ,
151+ textAlign : 'center'
152+ } }
153+ >
154+ < Heading
155+ as = "h1"
156+ sx = { {
157+ fontSize : [ '2.5rem' , '4rem' ] ,
158+ color : 'white' ,
159+ fontWeight : 800 ,
160+ mb : 2 ,
161+ textShadow : '0 2px 12px rgba(0,0,0,0.2)'
162+ } }
163+ >
164+ Regional Channels
165+ </ Heading >
166+ < Text
167+ sx = { {
168+ color : 'white' ,
169+ fontSize : [ '1rem' , '1.25rem' ] ,
170+ opacity : 0.9 ,
171+ mb : 3
172+ } }
173+ >
174+ Find and join your local Hack Club community on Slack
175+ </ Text >
176+ < Text
177+ sx = { {
178+ color : 'white' ,
179+ fontSize : '0.95rem' ,
180+ opacity : 0.75
181+ } }
182+ >
183+ { channels . length } channels across { counts . country } countries & { counts [ 'us-state' ] } US states
184+ </ Text >
185+ </ Box >
186+
187+ { /* Search + Filter */ }
188+ < Box
189+ sx = { {
190+ maxWidth : '1200px' ,
191+ mx : 'auto' ,
192+ px : [ '1.5rem' , '3rem' ] ,
193+ py : [ '1.5rem' , '2rem' ]
194+ } }
195+ >
196+ < Box
197+ sx = { {
198+ bg : 'white' ,
199+ borderRadius : '16px' ,
200+ p : [ '1.25rem' , '1.75rem' ] ,
201+ boxShadow : 'card' ,
202+ border : '1px solid' ,
203+ borderColor : 'smoke' ,
204+ mb : [ '1.5rem' , '2rem' ]
205+ } }
206+ >
207+ < Input
208+ placeholder = "Search channels..."
209+ value = { search }
210+ onChange = { ( e ) => setSearch ( e . target . value ) }
211+ sx = { {
212+ mb : 3 ,
213+ fontSize : '1rem' ,
214+ borderRadius : '8px' ,
215+ border : '2px solid' ,
216+ borderColor : 'smoke' ,
217+ px : 3 ,
218+ py : 2 ,
219+ fontFamily : 'inherit' ,
220+ '&:focus' : {
221+ borderColor : 'primary' ,
222+ outline : 'none'
223+ }
224+ } }
225+ />
226+ < Box sx = { { display : 'flex' , gap : 2 , flexWrap : 'wrap' } } >
227+ < FilterButton active = { filter === 'all' } onClick = { ( ) => setFilter ( 'all' ) } >
228+ All ({ counts . all } )
229+ </ FilterButton >
230+ < FilterButton active = { filter === 'country' } onClick = { ( ) => setFilter ( 'country' ) } >
231+ Countries ({ counts . country } )
232+ </ FilterButton >
233+ < FilterButton active = { filter === 'us-state' } onClick = { ( ) => setFilter ( 'us-state' ) } >
234+ US States ({ counts [ 'us-state' ] } )
235+ </ FilterButton >
236+ < FilterButton active = { filter === 'island' } onClick = { ( ) => setFilter ( 'island' ) } >
237+ Islands ({ counts . island } )
238+ </ FilterButton >
239+ </ Box >
240+ </ Box >
241+
242+ { /* Results count */ }
243+ < Text sx = { { color : 'muted' , fontSize : '0.9rem' , mb : 3 , fontWeight : 500 } } >
244+ { filtered . length === 0
245+ ? 'No channels found'
246+ : `Showing ${ filtered . length } channel${ filtered . length !== 1 ? 's' : '' } ` }
247+ </ Text >
248+
249+ { /* Grid */ }
250+ { filtered . length > 0 ? (
251+ < Box
252+ sx = { {
253+ display : 'grid' ,
254+ gridTemplateColumns : [
255+ 'repeat(2, 1fr)' ,
256+ 'repeat(3, 1fr)' ,
257+ 'repeat(4, 1fr)' ,
258+ 'repeat(5, 1fr)'
259+ ] ,
260+ gap : [ '0.75rem' , '1rem' ]
261+ } }
262+ >
263+ { filtered . map ( ( channel ) => (
264+ < ChannelCard key = { channel . id } channel = { channel } />
265+ ) ) }
266+ </ Box >
267+ ) : (
268+ < Box
269+ sx = { {
270+ textAlign : 'center' ,
271+ py : '4rem' ,
272+ color : 'muted'
273+ } }
274+ >
275+ < Text sx = { { fontSize : '1.15rem' , fontWeight : 600 } } >
276+ No channels match "{ search } "
277+ </ Text >
278+ < Text sx = { { fontSize : '0.95rem' , mt : 1 } } >
279+ Try a different search term
280+ </ Text >
281+ </ Box >
282+ ) }
283+
284+ { /* Back link */ }
285+ < Box sx = { { textAlign : 'center' , mt : [ '3rem' , '4rem' ] , mb : '2rem' } } >
286+ < ThemeLink
287+ href = "/"
288+ sx = { {
289+ color : 'primary' ,
290+ fontWeight : 700 ,
291+ fontSize : '1rem' ,
292+ textDecoration : 'none' ,
293+ '&:hover' : { textDecoration : 'underline' }
294+ } }
295+ >
296+ Back to Hack Club Slack
297+ </ ThemeLink >
298+ </ Box >
299+ </ Box >
300+
301+ < Footer />
302+ </ Box >
303+ )
304+ }
305+
306+ export default ChannelsPage
0 commit comments