Skip to content

Commit 16518cc

Browse files
feat(web): improve sponsors section, add special sponsor highlighting, update README, add X icon (#426)
1 parent 1b37f34 commit 16518cc

File tree

7 files changed

+201
-50
lines changed

7 files changed

+201
-50
lines changed

README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ bun create better-t-stack@latest
1717
pnpm create better-t-stack@latest
1818
```
1919

20+
## Sponsors
21+
22+
<p align="center">
23+
<img src="https://sponsors.amanv.dev/sponsors.png" alt="Sponsors" width="300">
24+
</p>
25+
26+
2027
## Features
2128

2229
- **Zero-config setup** with interactive CLI wizard
@@ -61,12 +68,6 @@ bun dev:web
6168

6269
Just fork the repository and submit a pull request!
6370

64-
## Sponsors
65-
66-
<p align="center">
67-
<img src="https://sponsors.amanv.dev/sponsors.png" alt="Sponsors" width="300">
68-
</p>
69-
7071
## Star History
7172

7273
<a href="https://www.star-history.com/#AmanVarshney01/create-better-t-stack&Date">

apps/cli/README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ pnpm create better-t-stack@latest
2121

2222
Follow the prompts to configure your project or use the `--yes` flag for defaults.
2323

24+
## Sponsors
25+
26+
<p align="center">
27+
<img src="https://sponsors.amanv.dev/sponsors.png" alt="Sponsors" width="300">
28+
</p>
29+
2430
## Features
2531

2632
| Category | Options |
@@ -211,9 +217,3 @@ my-better-t-app/
211217
```
212218

213219
After project creation, you'll receive detailed instructions for next steps and additional setup requirements.
214-
215-
## Sponsors
216-
217-
<p align="center">
218-
<img src="https://sponsors.amanv.dev/sponsors.png" alt="Sponsors" width="300">
219-
</p>

apps/web/public/icon/x.svg

Lines changed: 1 addition & 0 deletions
Loading

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

Lines changed: 166 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import {
44
Github,
55
Globe,
66
Heart,
7+
Star,
78
Terminal,
89
} from "lucide-react";
910
import Image from "next/image";
11+
// import Link from "next/link";
1012
import { useEffect, useState } from "react";
1113
import type { Sponsor } from "@/lib/types";
1214

@@ -48,13 +50,22 @@ export default function SponsorsSection() {
4850
);
4951
}
5052

53+
const aAmount = getAmount(a);
54+
const bAmount = getAmount(b);
55+
56+
if (aAmount !== bAmount) {
57+
return bAmount - aAmount;
58+
}
59+
5160
const aIsMonthly = !a.isOneTime;
5261
const bIsMonthly = !b.isOneTime;
5362

5463
if (aIsMonthly && !bIsMonthly) return -1;
5564
if (!aIsMonthly && bIsMonthly) return 1;
5665

57-
return getAmount(b) - getAmount(a);
66+
return (
67+
new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
68+
);
5869
});
5970

6071
setSponsors(sortedSponsors);
@@ -66,13 +77,46 @@ export default function SponsorsSection() {
6677
});
6778
}, []);
6879

80+
const SPECIAL_SPONSOR_THRESHOLD = 100;
81+
82+
const getSponsorAmount = (sponsor: Sponsor) => {
83+
if (sponsor.monthlyDollars === -1) {
84+
if (sponsor.tierName) {
85+
const match = sponsor.tierName.match(/\$(\d+(?:\.\d+)?)/);
86+
return match ? Number.parseFloat(match[1]) : 0;
87+
}
88+
return 0;
89+
}
90+
91+
if (!sponsor.isOneTime && sponsor.monthlyDollars > 0) {
92+
return sponsor.monthlyDollars;
93+
}
94+
95+
if (sponsor.isOneTime && sponsor.tierName) {
96+
const match = sponsor.tierName.match(/\$(\d+(?:\.\d+)?)/);
97+
return match ? Number.parseFloat(match[1]) : 0;
98+
}
99+
100+
return 0;
101+
};
102+
103+
const isSpecialSponsor = (sponsor: Sponsor) => {
104+
const amount = getSponsorAmount(sponsor);
105+
return amount >= SPECIAL_SPONSOR_THRESHOLD;
106+
};
107+
69108
const currentSponsors = sponsors.filter(
70109
(sponsor) => sponsor.monthlyDollars !== -1,
71110
);
72111
const pastSponsors = sponsors.filter(
73112
(sponsor) => sponsor.monthlyDollars === -1,
74113
);
75114

115+
const specialSponsors = currentSponsors.filter(isSpecialSponsor);
116+
const regularSponsors = currentSponsors.filter(
117+
(sponsor) => !isSpecialSponsor(sponsor),
118+
);
119+
76120
return (
77121
<div className="mb-12">
78122
<div className="mb-6 flex flex-wrap items-center justify-between gap-2 sm:flex-nowrap">
@@ -133,23 +177,19 @@ export default function SponsorsSection() {
133177
</div>
134178
) : (
135179
<div className="space-y-8">
136-
{currentSponsors.length > 0 && (
180+
{specialSponsors.length > 0 && (
137181
<div className="space-y-4">
138-
{/* <div className="flex items-center gap-2">
139-
<span className="text-primary text-sm">▶</span>
140-
<span className="font-semibold text-foreground text-sm">
141-
ACTIVE_SPONSORS.SH
142-
</span>
143-
<span className="text-muted-foreground text-xs">
144-
({currentSponsors.length})
145-
</span>
146-
</div> */}
147182
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
148-
{currentSponsors.map((entry, index) => {
183+
{specialSponsors.map((entry, index) => {
149184
const since = new Date(entry.createdAt).toLocaleDateString(
150185
undefined,
151186
{ year: "numeric", month: "short" },
152187
);
188+
const sponsorUrl =
189+
entry.sponsor.websiteUrl ||
190+
entry.sponsor.linkUrl ||
191+
`https://github.com/${entry.sponsor.login}`;
192+
153193
return (
154194
<div
155195
key={entry.sponsor.login}
@@ -158,18 +198,102 @@ export default function SponsorsSection() {
158198
>
159199
<div className="border-border border-b px-3 py-2">
160200
<div className="flex items-center gap-2">
161-
<span className="text-primary text-xs"></span>
201+
<Star className="h-4 w-4 text-yellow-500/90" />
162202
<div className="ml-auto flex items-center gap-2 text-muted-foreground text-xs">
163-
<span>
164-
{entry.isOneTime ? "ONE-TIME" : "MONTHLY"}
165-
</span>
203+
<span>SPECIAL</span>
166204
<span></span>
167205
<span>SINCE {since.toUpperCase()}</span>
168206
</div>
169207
</div>
170208
</div>
171209
<div className="p-4">
172-
<div className="flex items-center gap-4">
210+
<div className="flex gap-4">
211+
<div className="flex-shrink-0">
212+
<Image
213+
src={
214+
entry.sponsor.customLogoUrl ||
215+
entry.sponsor.avatarUrl
216+
}
217+
alt={entry.sponsor.name || entry.sponsor.login}
218+
width={100}
219+
height={100}
220+
className="rounded border border-border transition-colors duration-300"
221+
unoptimized
222+
/>
223+
</div>
224+
<div className="grid grid-cols-1 grid-rows-[1fr_auto] justify-between py-2">
225+
<div>
226+
<h3 className="truncate font-semibold text-foreground text-sm">
227+
{entry.sponsor.name || entry.sponsor.login}
228+
</h3>
229+
{entry.tierName && (
230+
<p className=" text-primary text-xs">
231+
{entry.tierName}
232+
</p>
233+
)}
234+
</div>
235+
<div className="flex flex-col gap-1">
236+
<a
237+
href={`https://github.com/${entry.sponsor.login}`}
238+
target="_blank"
239+
rel="noopener noreferrer"
240+
className="group flex items-center gap-2 text-muted-foreground text-xs transition-colors hover:text-primary"
241+
>
242+
<Github className="h-4 w-4" />
243+
<span className="truncate">
244+
{entry.sponsor.login}
245+
</span>
246+
</a>
247+
{(entry.sponsor.websiteUrl ||
248+
entry.sponsor.linkUrl) && (
249+
<a
250+
href={sponsorUrl}
251+
target="_blank"
252+
rel="noopener noreferrer"
253+
className="group flex items-center gap-2 text-muted-foreground text-xs transition-colors hover:text-primary"
254+
>
255+
<Globe className="h-4 w-4" />
256+
<span className="truncate">
257+
{sponsorUrl
258+
?.replace(/^https?:\/\//, "")
259+
?.replace(/\/$/, "")}
260+
</span>
261+
</a>
262+
)}
263+
</div>
264+
</div>
265+
</div>
266+
</div>
267+
</div>
268+
);
269+
})}
270+
</div>
271+
</div>
272+
)}
273+
{regularSponsors.length > 0 && (
274+
<div className="space-y-4">
275+
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
276+
{regularSponsors.map((entry, index) => {
277+
const since = new Date(entry.createdAt).toLocaleDateString(
278+
undefined,
279+
{ year: "numeric", month: "short" },
280+
);
281+
return (
282+
<div
283+
key={entry.sponsor.login}
284+
className="rounded border border-border"
285+
style={{ animationDelay: `${index * 50}ms` }}
286+
>
287+
<div className="border-border border-b px-3 py-2">
288+
<div className="flex items-center gap-2">
289+
<span className="text-primary text-xs"></span>
290+
<div className="ml-auto flex items-center gap-2 text-muted-foreground text-xs">
291+
<span>SINCE {since.toUpperCase()}</span>
292+
</div>
293+
</div>
294+
</div>
295+
<div className="p-4">
296+
<div className="flex gap-4">
173297
<div className="flex-shrink-0">
174298
<Image
175299
src={entry.sponsor.avatarUrl}
@@ -180,7 +304,7 @@ export default function SponsorsSection() {
180304
unoptimized
181305
/>
182306
</div>
183-
<div className="min-w-0 flex-1 space-y-2">
307+
<div className="grid grid-cols-1 grid-rows-[1fr_auto] justify-between py-2">
184308
<div>
185309
<h3 className="truncate font-semibold text-foreground text-sm">
186310
{entry.sponsor.name || entry.sponsor.login}
@@ -267,6 +391,12 @@ export default function SponsorsSection() {
267391
undefined,
268392
{ year: "numeric", month: "short" },
269393
);
394+
const wasSpecial = isSpecialSponsor(entry);
395+
const sponsorUrl =
396+
entry.sponsor.websiteUrl ||
397+
entry.sponsor.linkUrl ||
398+
`https://github.com/${entry.sponsor.login}`;
399+
270400
return (
271401
<div
272402
key={entry.sponsor.login}
@@ -275,29 +405,36 @@ export default function SponsorsSection() {
275405
>
276406
<div className="border-border/70 border-b px-3 py-2">
277407
<div className="flex items-center gap-2">
278-
<span className="text-muted-foreground text-xs">
279-
280-
</span>
408+
{wasSpecial ? (
409+
<Star className="h-4 w-4 text-yellow-500/60" />
410+
) : (
411+
<span className="text-muted-foreground text-xs">
412+
413+
</span>
414+
)}
281415
<div className="ml-auto flex items-center gap-2 text-muted-foreground text-xs">
282-
<span>PAST</span>
283-
<span></span>
416+
{wasSpecial && <span>SPECIAL</span>}
417+
{wasSpecial && <span></span>}
284418
<span>SINCE {since.toUpperCase()}</span>
285419
</div>
286420
</div>
287421
</div>
288422
<div className="p-4">
289-
<div className="flex items-center gap-4">
423+
<div className="flex gap-4">
290424
<div className="flex-shrink-0">
291425
<Image
292-
src={entry.sponsor.avatarUrl}
426+
src={
427+
entry.sponsor.customLogoUrl ||
428+
entry.sponsor.avatarUrl
429+
}
293430
alt={entry.sponsor.name || entry.sponsor.login}
294431
width={80}
295432
height={80}
296-
className="rounded border border-border/70 transition-colors duration-300"
433+
className="rounded border border-border/70"
297434
unoptimized
298435
/>
299436
</div>
300-
<div className="min-w-0 flex-1 space-y-2">
437+
<div className="grid grid-cols-1 grid-rows-[1fr_auto] justify-between">
301438
<div>
302439
<h3 className="truncate font-semibold text-muted-foreground text-sm">
303440
{entry.sponsor.name || entry.sponsor.login}
@@ -323,20 +460,14 @@ export default function SponsorsSection() {
323460
{(entry.sponsor.websiteUrl ||
324461
entry.sponsor.linkUrl) && (
325462
<a
326-
href={
327-
entry.sponsor.websiteUrl ||
328-
entry.sponsor.linkUrl
329-
}
463+
href={sponsorUrl}
330464
target="_blank"
331465
rel="noopener noreferrer"
332466
className="group flex items-center gap-2 text-muted-foreground/70 text-xs transition-colors hover:text-muted-foreground"
333467
>
334468
<Globe className="h-4 w-4" />
335469
<span className="truncate">
336-
{(
337-
entry.sponsor.websiteUrl ||
338-
entry.sponsor.linkUrl
339-
)
470+
{sponsorUrl
340471
?.replace(/^https?:\/\//, "")
341472
?.replace(/\/$/, "")}
342473
</span>

0 commit comments

Comments
 (0)