feat(web): improve sponsors section, add special sponsor highlighting, update README, add X icon (#426)

This commit is contained in:
Aman Varshney
2025-07-25 20:05:14 +05:30
committed by GitHub
parent 1b37f34d47
commit 16518cc449
7 changed files with 201 additions and 50 deletions

View File

@@ -17,6 +17,13 @@ bun create better-t-stack@latest
pnpm create better-t-stack@latest
```
## Sponsors
<p align="center">
<img src="https://sponsors.amanv.dev/sponsors.png" alt="Sponsors" width="300">
</p>
## Features
- **Zero-config setup** with interactive CLI wizard
@@ -61,12 +68,6 @@ bun dev:web
Just fork the repository and submit a pull request!
## Sponsors
<p align="center">
<img src="https://sponsors.amanv.dev/sponsors.png" alt="Sponsors" width="300">
</p>
## Star History
<a href="https://www.star-history.com/#AmanVarshney01/create-better-t-stack&Date">

View File

@@ -21,6 +21,12 @@ pnpm create better-t-stack@latest
Follow the prompts to configure your project or use the `--yes` flag for defaults.
## Sponsors
<p align="center">
<img src="https://sponsors.amanv.dev/sponsors.png" alt="Sponsors" width="300">
</p>
## Features
| Category | Options |
@@ -211,9 +217,3 @@ my-better-t-app/
```
After project creation, you'll receive detailed instructions for next steps and additional setup requirements.
## Sponsors
<p align="center">
<img src="https://sponsors.amanv.dev/sponsors.png" alt="Sponsors" width="300">
</p>

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1227" fill="none" viewBox="0 0 1200 1227"><path fill="#fff" d="M714.163 519.284 1160.89 0h-105.86L667.137 450.887 357.328 0H0l468.492 681.821L0 1226.37h105.866l409.625-476.152 327.181 476.152H1200L714.137 519.284h.026ZM569.165 687.828l-47.468-67.894-377.686-540.24h162.604l304.797 435.991 47.468 67.894 396.2 566.721H892.476L569.165 687.854v-.026Z"/></svg>

After

Width:  |  Height:  |  Size: 418 B

View File

@@ -4,9 +4,11 @@ import {
Github,
Globe,
Heart,
Star,
Terminal,
} from "lucide-react";
import Image from "next/image";
// import Link from "next/link";
import { useEffect, useState } from "react";
import type { Sponsor } from "@/lib/types";
@@ -48,13 +50,22 @@ export default function SponsorsSection() {
);
}
const aAmount = getAmount(a);
const bAmount = getAmount(b);
if (aAmount !== bAmount) {
return bAmount - aAmount;
}
const aIsMonthly = !a.isOneTime;
const bIsMonthly = !b.isOneTime;
if (aIsMonthly && !bIsMonthly) return -1;
if (!aIsMonthly && bIsMonthly) return 1;
return getAmount(b) - getAmount(a);
return (
new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
);
});
setSponsors(sortedSponsors);
@@ -66,6 +77,34 @@ export default function SponsorsSection() {
});
}, []);
const SPECIAL_SPONSOR_THRESHOLD = 100;
const getSponsorAmount = (sponsor: Sponsor) => {
if (sponsor.monthlyDollars === -1) {
if (sponsor.tierName) {
const match = sponsor.tierName.match(/\$(\d+(?:\.\d+)?)/);
return match ? Number.parseFloat(match[1]) : 0;
}
return 0;
}
if (!sponsor.isOneTime && sponsor.monthlyDollars > 0) {
return sponsor.monthlyDollars;
}
if (sponsor.isOneTime && sponsor.tierName) {
const match = sponsor.tierName.match(/\$(\d+(?:\.\d+)?)/);
return match ? Number.parseFloat(match[1]) : 0;
}
return 0;
};
const isSpecialSponsor = (sponsor: Sponsor) => {
const amount = getSponsorAmount(sponsor);
return amount >= SPECIAL_SPONSOR_THRESHOLD;
};
const currentSponsors = sponsors.filter(
(sponsor) => sponsor.monthlyDollars !== -1,
);
@@ -73,6 +112,11 @@ export default function SponsorsSection() {
(sponsor) => sponsor.monthlyDollars === -1,
);
const specialSponsors = currentSponsors.filter(isSpecialSponsor);
const regularSponsors = currentSponsors.filter(
(sponsor) => !isSpecialSponsor(sponsor),
);
return (
<div className="mb-12">
<div className="mb-6 flex flex-wrap items-center justify-between gap-2 sm:flex-nowrap">
@@ -133,19 +177,103 @@ export default function SponsorsSection() {
</div>
) : (
<div className="space-y-8">
{currentSponsors.length > 0 && (
{specialSponsors.length > 0 && (
<div className="space-y-4">
{/* <div className="flex items-center gap-2">
<span className="text-primary text-sm">▶</span>
<span className="font-semibold text-foreground text-sm">
ACTIVE_SPONSORS.SH
</span>
<span className="text-muted-foreground text-xs">
({currentSponsors.length})
</span>
</div> */}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{currentSponsors.map((entry, index) => {
{specialSponsors.map((entry, index) => {
const since = new Date(entry.createdAt).toLocaleDateString(
undefined,
{ year: "numeric", month: "short" },
);
const sponsorUrl =
entry.sponsor.websiteUrl ||
entry.sponsor.linkUrl ||
`https://github.com/${entry.sponsor.login}`;
return (
<div
key={entry.sponsor.login}
className="rounded border border-border"
style={{ animationDelay: `${index * 50}ms` }}
>
<div className="border-border border-b px-3 py-2">
<div className="flex items-center gap-2">
<Star className="h-4 w-4 text-yellow-500/90" />
<div className="ml-auto flex items-center gap-2 text-muted-foreground text-xs">
<span>SPECIAL</span>
<span></span>
<span>SINCE {since.toUpperCase()}</span>
</div>
</div>
</div>
<div className="p-4">
<div className="flex gap-4">
<div className="flex-shrink-0">
<Image
src={
entry.sponsor.customLogoUrl ||
entry.sponsor.avatarUrl
}
alt={entry.sponsor.name || entry.sponsor.login}
width={100}
height={100}
className="rounded border border-border transition-colors duration-300"
unoptimized
/>
</div>
<div className="grid grid-cols-1 grid-rows-[1fr_auto] justify-between py-2">
<div>
<h3 className="truncate font-semibold text-foreground text-sm">
{entry.sponsor.name || entry.sponsor.login}
</h3>
{entry.tierName && (
<p className=" text-primary text-xs">
{entry.tierName}
</p>
)}
</div>
<div className="flex flex-col gap-1">
<a
href={`https://github.com/${entry.sponsor.login}`}
target="_blank"
rel="noopener noreferrer"
className="group flex items-center gap-2 text-muted-foreground text-xs transition-colors hover:text-primary"
>
<Github className="h-4 w-4" />
<span className="truncate">
{entry.sponsor.login}
</span>
</a>
{(entry.sponsor.websiteUrl ||
entry.sponsor.linkUrl) && (
<a
href={sponsorUrl}
target="_blank"
rel="noopener noreferrer"
className="group flex items-center gap-2 text-muted-foreground text-xs transition-colors hover:text-primary"
>
<Globe className="h-4 w-4" />
<span className="truncate">
{sponsorUrl
?.replace(/^https?:\/\//, "")
?.replace(/\/$/, "")}
</span>
</a>
)}
</div>
</div>
</div>
</div>
</div>
);
})}
</div>
</div>
)}
{regularSponsors.length > 0 && (
<div className="space-y-4">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{regularSponsors.map((entry, index) => {
const since = new Date(entry.createdAt).toLocaleDateString(
undefined,
{ year: "numeric", month: "short" },
@@ -160,16 +288,12 @@ export default function SponsorsSection() {
<div className="flex items-center gap-2">
<span className="text-primary text-xs"></span>
<div className="ml-auto flex items-center gap-2 text-muted-foreground text-xs">
<span>
{entry.isOneTime ? "ONE-TIME" : "MONTHLY"}
</span>
<span></span>
<span>SINCE {since.toUpperCase()}</span>
</div>
</div>
</div>
<div className="p-4">
<div className="flex items-center gap-4">
<div className="flex gap-4">
<div className="flex-shrink-0">
<Image
src={entry.sponsor.avatarUrl}
@@ -180,7 +304,7 @@ export default function SponsorsSection() {
unoptimized
/>
</div>
<div className="min-w-0 flex-1 space-y-2">
<div className="grid grid-cols-1 grid-rows-[1fr_auto] justify-between py-2">
<div>
<h3 className="truncate font-semibold text-foreground text-sm">
{entry.sponsor.name || entry.sponsor.login}
@@ -267,6 +391,12 @@ export default function SponsorsSection() {
undefined,
{ year: "numeric", month: "short" },
);
const wasSpecial = isSpecialSponsor(entry);
const sponsorUrl =
entry.sponsor.websiteUrl ||
entry.sponsor.linkUrl ||
`https://github.com/${entry.sponsor.login}`;
return (
<div
key={entry.sponsor.login}
@@ -275,29 +405,36 @@ export default function SponsorsSection() {
>
<div className="border-border/70 border-b px-3 py-2">
<div className="flex items-center gap-2">
<span className="text-muted-foreground text-xs">
</span>
{wasSpecial ? (
<Star className="h-4 w-4 text-yellow-500/60" />
) : (
<span className="text-muted-foreground text-xs">
</span>
)}
<div className="ml-auto flex items-center gap-2 text-muted-foreground text-xs">
<span>PAST</span>
<span></span>
{wasSpecial && <span>SPECIAL</span>}
{wasSpecial && <span></span>}
<span>SINCE {since.toUpperCase()}</span>
</div>
</div>
</div>
<div className="p-4">
<div className="flex items-center gap-4">
<div className="flex gap-4">
<div className="flex-shrink-0">
<Image
src={entry.sponsor.avatarUrl}
src={
entry.sponsor.customLogoUrl ||
entry.sponsor.avatarUrl
}
alt={entry.sponsor.name || entry.sponsor.login}
width={80}
height={80}
className="rounded border border-border/70 transition-colors duration-300"
className="rounded border border-border/70"
unoptimized
/>
</div>
<div className="min-w-0 flex-1 space-y-2">
<div className="grid grid-cols-1 grid-rows-[1fr_auto] justify-between">
<div>
<h3 className="truncate font-semibold text-muted-foreground text-sm">
{entry.sponsor.name || entry.sponsor.login}
@@ -323,20 +460,14 @@ export default function SponsorsSection() {
{(entry.sponsor.websiteUrl ||
entry.sponsor.linkUrl) && (
<a
href={
entry.sponsor.websiteUrl ||
entry.sponsor.linkUrl
}
href={sponsorUrl}
target="_blank"
rel="noopener noreferrer"
className="group flex items-center gap-2 text-muted-foreground/70 text-xs transition-colors hover:text-muted-foreground"
>
<Globe className="h-4 w-4" />
<span className="truncate">
{(
entry.sponsor.websiteUrl ||
entry.sponsor.linkUrl
)
{sponsorUrl
?.replace(/^https?:\/\//, "")
?.replace(/\/$/, "")}
</span>

View File

@@ -13,19 +13,25 @@ const TWEET_IDS = [
"1931029815047455149",
"1933149770639614324",
"1937599252173128103",
"1947357370302304559",
"1930511724702285885",
"1945591420657532994",
"1945204056063913989",
"1912836377365905496",
"1947973299805561005",
"1907817662215757853",
"1933216760896934060",
"1942558041704182158",
"1947636576118304881",
"1937383786637094958",
"1931709370003583004",
"1929147326955704662",
"1948050877454938549",
"1904228496144269699",
"1930257410259616057",
"1937258706279817570",
"1917815700980391964",
"1947812547551498466",
"1928317790588403953",
"1917640304758514093",
"1907831059275735353",
@@ -74,8 +80,8 @@ const TWEET_IDS = [
];
export const components: TwitterComponents = {
AvatarImg: (props) => <Image {...props} unoptimized />,
MediaImg: (props) => <Image {...props} fill unoptimized />,
AvatarImg: (props) => <Image {...props} alt={props.alt} unoptimized />,
MediaImg: (props) => <Image {...props} alt={props.alt} fill unoptimized />,
};
export default function Testimonials() {

View File

@@ -2,6 +2,7 @@ import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared";
import Image from "next/image";
import discordLogo from "@/public/icon/discord.svg";
import npmLogo from "@/public/icon/npm.svg";
import xLogo from "@/public/icon/x.svg";
import mainLogo from "@/public/logo.svg";
export const logo = (
@@ -20,7 +21,6 @@ export const baseOptions: BaseLayoutProps = {
title: (
<>
{logo}
<span className="font-medium [.uwu_&]:hidden [header_&]:text-[15px]">
Better T Stack
</span>
@@ -60,6 +60,17 @@ export const baseOptions: BaseLayoutProps = {
external: true,
secondary: true,
},
{
text: "X",
icon: (
<Image src={xLogo} alt="x" className="size-4 invert dark:invert-0" />
),
label: "X",
type: "icon",
url: "https://x.com/amanvarshney01",
external: true,
secondary: true,
},
{
text: "Discord",
icon: (

View File

@@ -38,6 +38,7 @@ export interface Sponsor {
avatarUrl: string;
websiteUrl?: string;
linkUrl: string;
customLogoUrl: string;
type: string;
};
isOneTime: boolean;