Skip to content

Commit 41759c0

Browse files
feat(web): Add past sponsors section with toggle visibility
1 parent 51b6fb7 commit 41759c0

File tree

1 file changed

+240
-88
lines changed

1 file changed

+240
-88
lines changed

apps/web/src/app/(home)/_components/sponsors-section.tsx

Lines changed: 240 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
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";
29
import Image from "next/image";
310
import { useEffect, useState } from "react";
411
import type { Sponsor } from "@/lib/types";
@@ -7,6 +14,7 @@ export default function SponsorsSection() {
714
const [sponsors, setSponsors] = useState<Sponsor[]>([]);
815
const [loadingSponsors, setLoadingSponsors] = useState(true);
916
const [sponsorError, setSponsorError] = useState<string | null>(null);
17+
const [showPastSponsors, setShowPastSponsors] = useState(false);
1018

1119
useEffect(() => {
1220
fetch("https://sponsors.amanv.dev/sponsors.json")
@@ -31,6 +39,15 @@ export default function SponsorsSection() {
3139
return 0;
3240
};
3341

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+
3451
const aIsMonthly = !a.isOneTime;
3552
const bIsMonthly = !b.isOneTime;
3653

@@ -49,6 +66,13 @@ export default function SponsorsSection() {
4966
});
5067
}, []);
5168

69+
const currentSponsors = sponsors.filter(
70+
(sponsor) => sponsor.monthlyDollars !== -1,
71+
);
72+
const pastSponsors = sponsors.filter(
73+
(sponsor) => sponsor.monthlyDollars === -1,
74+
);
75+
5276
return (
5377
<div className="mb-12">
5478
<div className="mb-6 flex flex-wrap items-center justify-between gap-2 sm:flex-nowrap">
@@ -124,99 +148,227 @@ export default function SponsorsSection() {
124148
</div>
125149
</div>
126150
) : (
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>
161186
</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(/^https?:\/\//, "")
240+
?.replace(/\/$/, "")}
241+
</span>
242+
</a>
243+
)}
244+
</div>
245+
</div>
172246
</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+
183296
</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(/^https?:\/\//, "")
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(/^https?:\/\//, "")
357+
?.replace(/\/$/, "")}
358+
</span>
359+
</a>
360+
)}
361+
</div>
362+
</div>
363+
</div>
212364
</div>
213365
</div>
214-
</div>
215-
</div>
366+
);
367+
})}
216368
</div>
217-
);
218-
})}
219-
</div>
369+
)}
370+
</div>
371+
)}
220372

221373
<div className="rounded border border-border p-4">
222374
<a

0 commit comments

Comments
 (0)