fix sponsor data handling

This commit is contained in:
Aman Varshney
2025-07-30 20:52:03 +05:30
parent 12cbbe560a
commit 909263a509
5 changed files with 145 additions and 131 deletions

View File

@@ -2,14 +2,14 @@
A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations
![demo](https://cdn.jsdelivr.net/gh/amanvarshney01/create-better-t-stack/demo.gif)
## Sponsors ## Sponsors
<p align="center"> <p align="center">
<img src="https://sponsors.amanv.dev/sponsors.png" alt="Sponsors"> <img src="https://sponsors.amanv.dev/sponsors.png" alt="Sponsors">
</p> </p>
![demo](https://cdn.jsdelivr.net/gh/amanvarshney01/create-better-t-stack/demo.gif)
## Quick Start ## Quick Start
```bash ```bash

View File

@@ -2,14 +2,14 @@
A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations
![demo](https://cdn.jsdelivr.net/gh/amanvarshney01/create-better-t-stack@master/demo.gif)
## Sponsors ## Sponsors
<p align="center"> <p align="center">
<img src="https://sponsors.amanv.dev/sponsors.png" alt="Sponsors"> <img src="https://sponsors.amanv.dev/sponsors.png" alt="Sponsors">
</p> </p>
![demo](https://cdn.jsdelivr.net/gh/amanvarshney01/create-better-t-stack@master/demo.gif)
## Quick Start ## Quick Start
Run without installing globally: Run without installing globally:

View File

@@ -10,6 +10,17 @@ import {
import Image from "next/image"; import Image from "next/image";
// import Link from "next/link"; // import Link from "next/link";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import {
filterCurrentSponsors,
filterPastSponsors,
filterRegularSponsors,
filterSpecialSponsors,
formatSponsorUrl,
getSponsorUrl,
isSpecialSponsor,
sortSpecialSponsors,
sortSponsors,
} from "@/lib/sponsor-utils";
import type { Sponsor } from "@/lib/types"; import type { Sponsor } from "@/lib/types";
export default function SponsorsSection() { export default function SponsorsSection() {
@@ -26,48 +37,7 @@ export default function SponsorsSection() {
}) })
.then((data) => { .then((data) => {
const sponsorsData = Array.isArray(data) ? data : []; const sponsorsData = Array.isArray(data) ? data : [];
const sortedSponsors = sortSponsors(sponsorsData);
const sortedSponsors = sponsorsData.sort((a, b) => {
const getAmount = (sponsor: Sponsor) => {
if (!sponsor.isOneTime && sponsor.monthlyDollars > 0) {
return sponsor.monthlyDollars;
}
if (sponsor.tierName) {
const match = sponsor.tierName.match(/\$(\d+(?:\.\d+)?)/);
return match ? Number.parseFloat(match[1]) : 0;
}
return 0;
};
if (a.monthlyDollars === -1 && b.monthlyDollars !== -1) return 1;
if (a.monthlyDollars !== -1 && b.monthlyDollars === -1) return -1;
if (a.monthlyDollars === -1 && b.monthlyDollars === -1) {
return (
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
);
}
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 (
new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
);
});
setSponsors(sortedSponsors); setSponsors(sortedSponsors);
setLoadingSponsors(false); setLoadingSponsors(false);
}) })
@@ -77,45 +47,13 @@ export default function SponsorsSection() {
}); });
}, []); }, []);
const SPECIAL_SPONSOR_THRESHOLD = 100; const currentSponsors = filterCurrentSponsors(sponsors);
const pastSponsors = filterPastSponsors(sponsors);
const getSponsorAmount = (sponsor: Sponsor) => { const specialSponsors = sortSpecialSponsors(
if (sponsor.monthlyDollars === -1) { filterSpecialSponsors(currentSponsors),
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,
);
const pastSponsors = sponsors.filter(
(sponsor) => sponsor.monthlyDollars === -1,
);
const specialSponsors = currentSponsors.filter(isSpecialSponsor);
const regularSponsors = currentSponsors.filter(
(sponsor) => !isSpecialSponsor(sponsor),
); );
const regularSponsors = filterRegularSponsors(currentSponsors);
return ( return (
<div className="mb-12"> <div className="mb-12">
@@ -185,10 +123,7 @@ export default function SponsorsSection() {
undefined, undefined,
{ year: "numeric", month: "short" }, { year: "numeric", month: "short" },
); );
const sponsorUrl = const sponsorUrl = getSponsorUrl(entry);
entry.sponsor.websiteUrl ||
entry.sponsor.linkUrl ||
`https://github.com/${entry.sponsor.login}`;
return ( return (
<div <div
@@ -254,9 +189,7 @@ export default function SponsorsSection() {
> >
<Globe className="h-4 w-4" /> <Globe className="h-4 w-4" />
<span className="truncate"> <span className="truncate">
{sponsorUrl {formatSponsorUrl(sponsorUrl)}
?.replace(/^https?:\/\//, "")
?.replace(/\/$/, "")}
</span> </span>
</a> </a>
)} )}
@@ -340,12 +273,10 @@ export default function SponsorsSection() {
> >
<Globe className="h-4 w-4" /> <Globe className="h-4 w-4" />
<span className="truncate"> <span className="truncate">
{( {formatSponsorUrl(
entry.sponsor.websiteUrl || entry.sponsor.websiteUrl ||
entry.sponsor.linkUrl entry.sponsor.linkUrl,
) )}
?.replace(/^https?:\/\//, "")
?.replace(/\/$/, "")}
</span> </span>
</a> </a>
)} )}
@@ -392,10 +323,7 @@ export default function SponsorsSection() {
{ year: "numeric", month: "short" }, { year: "numeric", month: "short" },
); );
const wasSpecial = isSpecialSponsor(entry); const wasSpecial = isSpecialSponsor(entry);
const sponsorUrl = const sponsorUrl = getSponsorUrl(entry);
entry.sponsor.websiteUrl ||
entry.sponsor.linkUrl ||
`https://github.com/${entry.sponsor.login}`;
return ( return (
<div <div
@@ -467,9 +395,7 @@ export default function SponsorsSection() {
> >
<Globe className="h-4 w-4" /> <Globe className="h-4 w-4" />
<span className="truncate"> <span className="truncate">
{sponsorUrl {formatSponsorUrl(sponsorUrl)}
?.replace(/^https?:\/\//, "")
?.replace(/\/$/, "")}
</span> </span>
</a> </a>
)} )}

View File

@@ -1,14 +1,15 @@
"use client"; "use client";
import Image from "next/image"; import Image from "next/image";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import {
filterCurrentSponsors,
filterSpecialSponsors,
sortSpecialSponsors,
} from "@/lib/sponsor-utils";
import type { Sponsor } from "@/lib/types"; import type { Sponsor } from "@/lib/types";
const isSpecialSponsor = (sponsor: Sponsor) => {
return sponsor.monthlyDollars >= 100;
};
export function SpecialSponsorBanner() { export function SpecialSponsorBanner() {
const [specialSponsor, setSpecialSponsor] = useState<Sponsor | null>(null); const [specialSponsors, setSpecialSponsors] = useState<Sponsor[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
useEffect(() => { useEffect(() => {
@@ -19,13 +20,11 @@ export function SpecialSponsorBanner() {
}) })
.then((data) => { .then((data) => {
const sponsorsData = Array.isArray(data) ? data : []; const sponsorsData = Array.isArray(data) ? data : [];
const specialSponsor = sponsorsData.find( const currentSponsors = filterCurrentSponsors(sponsorsData);
(sponsor) => sponsor.monthlyDollars > 0 && isSpecialSponsor(sponsor), const specials = sortSpecialSponsors(
filterSpecialSponsors(currentSponsors),
); );
setSpecialSponsors(specials);
if (specialSponsor) {
setSpecialSponsor(specialSponsor);
}
setLoading(false); setLoading(false);
}) })
.catch(() => { .catch(() => {
@@ -46,29 +45,30 @@ export function SpecialSponsorBanner() {
); );
} }
if (!specialSponsor) { if (!specialSponsors.length) {
return null; return null;
} }
return ( return (
<div className=""> <div className="">
<div className="flex items-center gap-3"> <div className="flex flex-col gap-2">
<Image {specialSponsors.map((sponsor) => (
src={ <div key={sponsor.sponsor.login} className="flex items-center gap-3">
specialSponsor.sponsor.customLogoUrl || <Image
specialSponsor.sponsor.avatarUrl src={sponsor.sponsor.customLogoUrl || sponsor.sponsor.avatarUrl}
} alt={sponsor.sponsor.name || sponsor.sponsor.login}
alt={specialSponsor.sponsor.name || specialSponsor.sponsor.login} width={30}
width={30} height={30}
height={30} className="rounded border border-border"
className="rounded border border-border" unoptimized
unoptimized />
/> <div className="min-w-0 flex-1">
<div className="min-w-0 flex-1"> <h4 className="truncate font-medium text-sm">
<h4 className="truncate font-medium text-sm"> {sponsor.sponsor.name || sponsor.sponsor.login}
{specialSponsor.sponsor.name || specialSponsor.sponsor.login} </h4>
</h4> </div>
</div> </div>
))}
</div> </div>
</div> </div>
); );

View File

@@ -0,0 +1,88 @@
import type { Sponsor } from "@/lib/types";
export const SPECIAL_SPONSOR_THRESHOLD = 100;
export const getSponsorAmount = (sponsor: Sponsor): number => {
// For past sponsors, return 0
if (sponsor.monthlyDollars === -1) {
return 0;
}
// For one-time sponsors, parse the actual amount from tierName
if (sponsor.isOneTime && sponsor.tierName) {
const match = sponsor.tierName.match(/\$(\d+(?:\.\d+)?)/);
return match ? Number.parseFloat(match[1]) : sponsor.monthlyDollars;
}
// For monthly sponsors, use monthlyDollars
return sponsor.monthlyDollars;
};
export const isSpecialSponsor = (sponsor: Sponsor): boolean => {
const amount = getSponsorAmount(sponsor);
return amount >= SPECIAL_SPONSOR_THRESHOLD;
};
export const sortSponsors = (sponsors: Sponsor[]): Sponsor[] => {
return sponsors.sort((a, b) => {
// Past sponsors (monthlyDollars === -1) go to the end
if (a.monthlyDollars === -1 && b.monthlyDollars !== -1) return 1;
if (a.monthlyDollars !== -1 && b.monthlyDollars === -1) return -1;
// If both are past sponsors, sort by creation date (newest first)
if (a.monthlyDollars === -1 && b.monthlyDollars === -1) {
return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
}
// For current sponsors, sort by actual amount (highest first)
const aAmount = getSponsorAmount(a);
const bAmount = getSponsorAmount(b);
if (aAmount !== bAmount) {
return bAmount - aAmount;
}
// If amounts are equal, sort by creation date (oldest first)
return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
});
};
export const sortSpecialSponsors = (sponsors: Sponsor[]): Sponsor[] => {
return sponsors.sort((a, b) => {
// Sort by actual amount (highest first)
const aAmount = getSponsorAmount(a);
const bAmount = getSponsorAmount(b);
if (aAmount !== bAmount) {
return bAmount - aAmount;
}
// If amounts are equal, sort by creation date (oldest first)
return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
});
};
export const filterCurrentSponsors = (sponsors: Sponsor[]): Sponsor[] => {
return sponsors.filter((sponsor) => sponsor.monthlyDollars !== -1);
};
export const filterPastSponsors = (sponsors: Sponsor[]): Sponsor[] => {
return sponsors.filter((sponsor) => sponsor.monthlyDollars === -1);
};
export const filterSpecialSponsors = (sponsors: Sponsor[]): Sponsor[] => {
return sponsors.filter(isSpecialSponsor);
};
export const filterRegularSponsors = (sponsors: Sponsor[]): Sponsor[] => {
return sponsors.filter((sponsor) => !isSpecialSponsor(sponsor));
};
export const getSponsorUrl = (sponsor: Sponsor): string => {
return (
sponsor.sponsor.websiteUrl ||
sponsor.sponsor.linkUrl ||
`https://github.com/${sponsor.sponsor.login}`
);
};
export const formatSponsorUrl = (url: string): string => {
return url?.replace(/^https?:\/\//, "")?.replace(/\/$/, "");
};