1
- import { Github , Globe , Heart , Terminal } from "lucide-react" ;
1
+ import {
2
+ ChevronDown ,
3
+ ChevronUp ,
4
+ Github ,
5
+ Globe ,
6
+ Heart ,
7
+ Terminal ,
8
+ } from "lucide-react" ;
2
9
import Image from "next/image" ;
3
10
import { useEffect , useState } from "react" ;
4
11
import type { Sponsor } from "@/lib/types" ;
@@ -7,6 +14,7 @@ export default function SponsorsSection() {
7
14
const [ sponsors , setSponsors ] = useState < Sponsor [ ] > ( [ ] ) ;
8
15
const [ loadingSponsors , setLoadingSponsors ] = useState ( true ) ;
9
16
const [ sponsorError , setSponsorError ] = useState < string | null > ( null ) ;
17
+ const [ showPastSponsors , setShowPastSponsors ] = useState ( false ) ;
10
18
11
19
useEffect ( ( ) => {
12
20
fetch ( "https://sponsors.amanv.dev/sponsors.json" )
@@ -31,6 +39,15 @@ export default function SponsorsSection() {
31
39
return 0 ;
32
40
} ;
33
41
42
+ if ( a . monthlyDollars === - 1 && b . monthlyDollars !== - 1 ) return 1 ;
43
+ if ( a . monthlyDollars !== - 1 && b . monthlyDollars === - 1 ) return - 1 ;
44
+
45
+ if ( a . monthlyDollars === - 1 && b . monthlyDollars === - 1 ) {
46
+ return (
47
+ new Date ( b . createdAt ) . getTime ( ) - new Date ( a . createdAt ) . getTime ( )
48
+ ) ;
49
+ }
50
+
34
51
const aIsMonthly = ! a . isOneTime ;
35
52
const bIsMonthly = ! b . isOneTime ;
36
53
@@ -49,6 +66,13 @@ export default function SponsorsSection() {
49
66
} ) ;
50
67
} , [ ] ) ;
51
68
69
+ const currentSponsors = sponsors . filter (
70
+ ( sponsor ) => sponsor . monthlyDollars !== - 1 ,
71
+ ) ;
72
+ const pastSponsors = sponsors . filter (
73
+ ( sponsor ) => sponsor . monthlyDollars === - 1 ,
74
+ ) ;
75
+
52
76
return (
53
77
< div className = "mb-12" >
54
78
< div className = "mb-6 flex flex-wrap items-center justify-between gap-2 sm:flex-nowrap" >
@@ -124,99 +148,227 @@ export default function SponsorsSection() {
124
148
</ div >
125
149
</ div >
126
150
) : (
127
- < div className = "space-y-6" >
128
- < div className = "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4" >
129
- { sponsors . map ( ( entry , index ) => {
130
- const since = new Date ( entry . createdAt ) . toLocaleDateString (
131
- undefined ,
132
- { year : "numeric" , month : "short" } ,
133
- ) ;
134
- return (
135
- < div
136
- key = { entry . sponsor . login }
137
- className = "rounded border border-border"
138
- style = { { animationDelay : `${ index * 50 } ms` } }
139
- >
140
- < div className = "border-border border-b px-3 py-2" >
141
- < div className = "flex items-center gap-2" >
142
- < span className = "text-primary text-xs" > ▶</ span >
143
- < div className = "ml-auto flex items-center gap-2 text-muted-foreground text-xs" >
144
- < span > { entry . isOneTime ? "ONE-TIME" : "MONTHLY" } </ span >
145
- < span > •</ span >
146
- < span > SINCE { since . toUpperCase ( ) } </ span >
147
- </ div >
148
- </ div >
149
- </ div >
150
- < div className = "p-4" >
151
- < div className = "flex items-center gap-4" >
152
- < div className = "flex-shrink-0" >
153
- < Image
154
- src = { entry . sponsor . avatarUrl }
155
- alt = { entry . sponsor . name || entry . sponsor . login }
156
- width = { 100 }
157
- height = { 100 }
158
- className = "rounded border border-border transition-colors duration-300"
159
- unoptimized
160
- />
151
+ < div className = "space-y-8" >
152
+ { currentSponsors . length > 0 && (
153
+ < div className = "space-y-4" >
154
+ < div className = "flex items-center gap-2" >
155
+ < span className = "text-primary text-sm" > ▶</ span >
156
+ < span className = "font-semibold text-foreground text-sm" >
157
+ ACTIVE_SPONSORS.EXE
158
+ </ span >
159
+ < span className = "text-muted-foreground text-xs" >
160
+ ({ currentSponsors . length } )
161
+ </ span >
162
+ </ div >
163
+ < div className = "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4" >
164
+ { currentSponsors . map ( ( entry , index ) => {
165
+ const since = new Date ( entry . createdAt ) . toLocaleDateString (
166
+ undefined ,
167
+ { year : "numeric" , month : "short" } ,
168
+ ) ;
169
+ return (
170
+ < div
171
+ key = { entry . sponsor . login }
172
+ className = "rounded border border-border"
173
+ style = { { animationDelay : `${ index * 50 } ms` } }
174
+ >
175
+ < div className = "border-border border-b px-3 py-2" >
176
+ < div className = "flex items-center gap-2" >
177
+ < span className = "text-primary text-xs" > ▶</ span >
178
+ < div className = "ml-auto flex items-center gap-2 text-muted-foreground text-xs" >
179
+ < span >
180
+ { entry . isOneTime ? "ONE-TIME" : "MONTHLY" }
181
+ </ span >
182
+ < span > •</ span >
183
+ < span > SINCE { since . toUpperCase ( ) } </ span >
184
+ </ div >
185
+ </ div >
161
186
</ div >
162
- < div className = "min-w-0 flex-1 space-y-2" >
163
- < div >
164
- < h3 className = "truncate font-semibold text-foreground text-sm" >
165
- { entry . sponsor . name || entry . sponsor . login }
166
- </ h3 >
167
- { entry . tierName && (
168
- < p className = " text-primary text-xs" >
169
- { entry . tierName }
170
- </ p >
171
- ) }
187
+ < div className = "p-4" >
188
+ < div className = "flex items-center gap-4" >
189
+ < div className = "flex-shrink-0" >
190
+ < Image
191
+ src = { entry . sponsor . avatarUrl }
192
+ alt = { entry . sponsor . name || entry . sponsor . login }
193
+ width = { 100 }
194
+ height = { 100 }
195
+ className = "rounded border border-border transition-colors duration-300"
196
+ unoptimized
197
+ />
198
+ </ div >
199
+ < div className = "min-w-0 flex-1 space-y-2" >
200
+ < div >
201
+ < h3 className = "truncate font-semibold text-foreground text-sm" >
202
+ { entry . sponsor . name || entry . sponsor . login }
203
+ </ h3 >
204
+ { entry . tierName && (
205
+ < p className = " text-primary text-xs" >
206
+ { entry . tierName }
207
+ </ p >
208
+ ) }
209
+ </ div >
210
+ < div className = "flex flex-col gap-1" >
211
+ < a
212
+ href = { `https://github.com/${ entry . sponsor . login } ` }
213
+ target = "_blank"
214
+ rel = "noopener noreferrer"
215
+ className = "group flex items-center gap-2 text-muted-foreground text-xs transition-colors hover:text-primary"
216
+ >
217
+ < Github className = "h-4 w-4" />
218
+ < span className = "truncate" >
219
+ { entry . sponsor . login }
220
+ </ span >
221
+ </ a >
222
+ { ( entry . sponsor . websiteUrl ||
223
+ entry . sponsor . linkUrl ) && (
224
+ < a
225
+ href = {
226
+ entry . sponsor . websiteUrl ||
227
+ entry . sponsor . linkUrl
228
+ }
229
+ target = "_blank"
230
+ rel = "noopener noreferrer"
231
+ className = "group flex items-center gap-2 text-muted-foreground text-xs transition-colors hover:text-primary"
232
+ >
233
+ < Globe className = "h-4 w-4" />
234
+ < span className = "truncate" >
235
+ { (
236
+ entry . sponsor . websiteUrl ||
237
+ entry . sponsor . linkUrl
238
+ )
239
+ ?. replace ( / ^ h t t p s ? : \/ \/ / , "" )
240
+ ?. replace ( / \/ $ / , "" ) }
241
+ </ span >
242
+ </ a >
243
+ ) }
244
+ </ div >
245
+ </ div >
172
246
</ div >
173
- < div className = "flex flex-col gap-1" >
174
- < a
175
- href = { `https://github.com/${ entry . sponsor . login } ` }
176
- target = "_blank"
177
- rel = "noopener noreferrer"
178
- className = "group flex items-center gap-2 text-muted-foreground text-xs transition-colors hover:text-primary"
179
- >
180
- < Github className = "h-4 w-4" />
181
- < span className = "truncate" >
182
- { entry . sponsor . login }
247
+ </ div >
248
+ </ div >
249
+ ) ;
250
+ } ) }
251
+ </ div >
252
+ </ div >
253
+ ) }
254
+
255
+ { pastSponsors . length > 0 && (
256
+ < div className = "space-y-4" >
257
+ < button
258
+ type = "button"
259
+ onClick = { ( ) => setShowPastSponsors ( ! showPastSponsors ) }
260
+ className = "flex w-full items-center gap-2 rounded p-2 text-left transition-colors hover:bg-muted/50"
261
+ >
262
+ { showPastSponsors ? (
263
+ < ChevronUp className = "h-4 w-4 text-muted-foreground" />
264
+ ) : (
265
+ < ChevronDown className = "h-4 w-4 text-muted-foreground" />
266
+ ) }
267
+ < span className = "font-semibold text-muted-foreground text-sm" >
268
+ PAST_SPONSORS.ARCHIVE
269
+ </ span >
270
+ < span className = "text-muted-foreground text-xs" >
271
+ ({ pastSponsors . length } )
272
+ </ span >
273
+ < div className = "mx-2 h-px flex-1 bg-border" />
274
+ < span className = "text-muted-foreground text-xs" >
275
+ { showPastSponsors ? "HIDE" : "SHOW" }
276
+ </ span >
277
+ </ button >
278
+
279
+ { showPastSponsors && (
280
+ < div className = "slide-in-from-top-2 grid animate-in grid-cols-1 gap-4 duration-300 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4" >
281
+ { pastSponsors . map ( ( entry , index ) => {
282
+ const since = new Date ( entry . createdAt ) . toLocaleDateString (
283
+ undefined ,
284
+ { year : "numeric" , month : "short" } ,
285
+ ) ;
286
+ return (
287
+ < div
288
+ key = { entry . sponsor . login }
289
+ className = "rounded border border-border/70 bg-muted/20"
290
+ style = { { animationDelay : `${ index * 50 } ms` } }
291
+ >
292
+ < div className = "border-border/70 border-b px-3 py-2" >
293
+ < div className = "flex items-center gap-2" >
294
+ < span className = "text-muted-foreground text-xs" >
295
+ ◆
183
296
</ span >
184
- </ a >
185
- { ( entry . sponsor . websiteUrl ||
186
- entry . sponsor . linkUrl ) && (
187
- < a
188
- href = {
189
- entry . sponsor . websiteUrl ||
190
- entry . sponsor . linkUrl
191
- }
192
- target = "_blank"
193
- rel = "noopener noreferrer"
194
- className = "group flex items-center gap-2 text-muted-foreground text-xs transition-colors hover:text-primary"
195
- >
196
- < Globe className = "h-4 w-4" />
197
- < span className = "truncate" >
198
- { (
199
- entry . sponsor . websiteUrl ||
200
- entry . sponsor . linkUrl
201
- )
202
- ?. replace ( / ^ h t t p s ? : \/ \/ / , "" )
203
- ?. replace ( / \/ $ / , "" ) }
204
- </ span >
205
- </ a >
206
- ) }
207
-
208
- { /* <div className="flex items-center gap-2 text-muted-foreground text-xs">
209
- <span className="text-xs">👤</span>
210
- <span>{entry.sponsor.type.toUpperCase()}</span>
211
- </div> */ }
297
+ < div className = "ml-auto flex items-center gap-2 text-muted-foreground text-xs" >
298
+ < span > PAST</ span >
299
+ < span > •</ span >
300
+ < span > SINCE { since . toUpperCase ( ) } </ span >
301
+ </ div >
302
+ </ div >
303
+ </ div >
304
+ < div className = "p-4" >
305
+ < div className = "flex items-center gap-4" >
306
+ < div className = "flex-shrink-0" >
307
+ < Image
308
+ src = { entry . sponsor . avatarUrl }
309
+ alt = { entry . sponsor . name || entry . sponsor . login }
310
+ width = { 80 }
311
+ height = { 80 }
312
+ className = "rounded border border-border/70 transition-colors duration-300"
313
+ unoptimized
314
+ />
315
+ </ div >
316
+ < div className = "min-w-0 flex-1 space-y-2" >
317
+ < div >
318
+ < h3 className = "truncate font-semibold text-muted-foreground text-sm" >
319
+ { entry . sponsor . name || entry . sponsor . login }
320
+ </ h3 >
321
+ { entry . tierName && (
322
+ < p className = "text-muted-foreground/70 text-xs" >
323
+ { entry . tierName }
324
+ </ p >
325
+ ) }
326
+ </ div >
327
+ < div className = "flex flex-col gap-1" >
328
+ < a
329
+ href = { `https://github.com/${ entry . sponsor . login } ` }
330
+ target = "_blank"
331
+ rel = "noopener noreferrer"
332
+ className = "group flex items-center gap-2 text-muted-foreground/70 text-xs transition-colors hover:text-muted-foreground"
333
+ >
334
+ < Github className = "h-4 w-4" />
335
+ < span className = "truncate" >
336
+ { entry . sponsor . login }
337
+ </ span >
338
+ </ a >
339
+ { ( entry . sponsor . websiteUrl ||
340
+ entry . sponsor . linkUrl ) && (
341
+ < a
342
+ href = {
343
+ entry . sponsor . websiteUrl ||
344
+ entry . sponsor . linkUrl
345
+ }
346
+ target = "_blank"
347
+ rel = "noopener noreferrer"
348
+ className = "group flex items-center gap-2 text-muted-foreground/70 text-xs transition-colors hover:text-muted-foreground"
349
+ >
350
+ < Globe className = "h-4 w-4" />
351
+ < span className = "truncate" >
352
+ { (
353
+ entry . sponsor . websiteUrl ||
354
+ entry . sponsor . linkUrl
355
+ )
356
+ ?. replace ( / ^ h t t p s ? : \/ \/ / , "" )
357
+ ?. replace ( / \/ $ / , "" ) }
358
+ </ span >
359
+ </ a >
360
+ ) }
361
+ </ div >
362
+ </ div >
363
+ </ div >
212
364
</ div >
213
365
</ div >
214
- </ div >
215
- </ div >
366
+ ) ;
367
+ } ) }
216
368
</ div >
217
- ) ;
218
- } ) }
219
- </ div >
369
+ ) }
370
+ </ div >
371
+ ) }
220
372
221
373
< div className = "rounded border border-border p-4" >
222
374
< a
0 commit comments