From 909263a509bbb0d8b6c9bbba6877a92b080a7a42 Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Wed, 30 Jul 2025 20:52:03 +0530 Subject: [PATCH] fix sponsor data handling --- README.md | 4 +- apps/cli/README.md | 4 +- .../(home)/_components/sponsors-section.tsx | 122 ++++-------------- .../src/components/special-sponsor-banner.tsx | 58 ++++----- apps/web/src/lib/sponsor-utils.ts | 88 +++++++++++++ 5 files changed, 145 insertions(+), 131 deletions(-) create mode 100644 apps/web/src/lib/sponsor-utils.ts diff --git a/README.md b/README.md index 1d5ac35..68c6c25 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,14 @@ 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

+![demo](https://cdn.jsdelivr.net/gh/amanvarshney01/create-better-t-stack/demo.gif) + ## Quick Start ```bash diff --git a/apps/cli/README.md b/apps/cli/README.md index ce36df2..94e541e 100644 --- a/apps/cli/README.md +++ b/apps/cli/README.md @@ -2,14 +2,14 @@ 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

+![demo](https://cdn.jsdelivr.net/gh/amanvarshney01/create-better-t-stack@master/demo.gif) + ## Quick Start Run without installing globally: diff --git a/apps/web/src/app/(home)/_components/sponsors-section.tsx b/apps/web/src/app/(home)/_components/sponsors-section.tsx index 180abdd..da4a4be 100644 --- a/apps/web/src/app/(home)/_components/sponsors-section.tsx +++ b/apps/web/src/app/(home)/_components/sponsors-section.tsx @@ -10,6 +10,17 @@ import { import Image from "next/image"; // import Link from "next/link"; 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"; export default function SponsorsSection() { @@ -26,48 +37,7 @@ export default function SponsorsSection() { }) .then((data) => { const sponsorsData = Array.isArray(data) ? data : []; - - 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() - ); - }); - + const sortedSponsors = sortSponsors(sponsorsData); setSponsors(sortedSponsors); 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) => { - 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, - ); - const pastSponsors = sponsors.filter( - (sponsor) => sponsor.monthlyDollars === -1, - ); - - const specialSponsors = currentSponsors.filter(isSpecialSponsor); - const regularSponsors = currentSponsors.filter( - (sponsor) => !isSpecialSponsor(sponsor), + const specialSponsors = sortSpecialSponsors( + filterSpecialSponsors(currentSponsors), ); + const regularSponsors = filterRegularSponsors(currentSponsors); return (
@@ -185,10 +123,7 @@ export default function SponsorsSection() { undefined, { year: "numeric", month: "short" }, ); - const sponsorUrl = - entry.sponsor.websiteUrl || - entry.sponsor.linkUrl || - `https://github.com/${entry.sponsor.login}`; + const sponsorUrl = getSponsorUrl(entry); return (
- {sponsorUrl - ?.replace(/^https?:\/\//, "") - ?.replace(/\/$/, "")} + {formatSponsorUrl(sponsorUrl)} )} @@ -340,12 +273,10 @@ export default function SponsorsSection() { > - {( + {formatSponsorUrl( entry.sponsor.websiteUrl || - entry.sponsor.linkUrl - ) - ?.replace(/^https?:\/\//, "") - ?.replace(/\/$/, "")} + entry.sponsor.linkUrl, + )} )} @@ -392,10 +323,7 @@ export default function SponsorsSection() { { year: "numeric", month: "short" }, ); const wasSpecial = isSpecialSponsor(entry); - const sponsorUrl = - entry.sponsor.websiteUrl || - entry.sponsor.linkUrl || - `https://github.com/${entry.sponsor.login}`; + const sponsorUrl = getSponsorUrl(entry); return (
- {sponsorUrl - ?.replace(/^https?:\/\//, "") - ?.replace(/\/$/, "")} + {formatSponsorUrl(sponsorUrl)} )} diff --git a/apps/web/src/components/special-sponsor-banner.tsx b/apps/web/src/components/special-sponsor-banner.tsx index c56bf27..191925e 100644 --- a/apps/web/src/components/special-sponsor-banner.tsx +++ b/apps/web/src/components/special-sponsor-banner.tsx @@ -1,14 +1,15 @@ "use client"; import Image from "next/image"; import { useEffect, useState } from "react"; +import { + filterCurrentSponsors, + filterSpecialSponsors, + sortSpecialSponsors, +} from "@/lib/sponsor-utils"; import type { Sponsor } from "@/lib/types"; -const isSpecialSponsor = (sponsor: Sponsor) => { - return sponsor.monthlyDollars >= 100; -}; - export function SpecialSponsorBanner() { - const [specialSponsor, setSpecialSponsor] = useState(null); + const [specialSponsors, setSpecialSponsors] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { @@ -19,13 +20,11 @@ export function SpecialSponsorBanner() { }) .then((data) => { const sponsorsData = Array.isArray(data) ? data : []; - const specialSponsor = sponsorsData.find( - (sponsor) => sponsor.monthlyDollars > 0 && isSpecialSponsor(sponsor), + const currentSponsors = filterCurrentSponsors(sponsorsData); + const specials = sortSpecialSponsors( + filterSpecialSponsors(currentSponsors), ); - - if (specialSponsor) { - setSpecialSponsor(specialSponsor); - } + setSpecialSponsors(specials); setLoading(false); }) .catch(() => { @@ -46,29 +45,30 @@ export function SpecialSponsorBanner() { ); } - if (!specialSponsor) { + if (!specialSponsors.length) { return null; } return (
-
- {specialSponsor.sponsor.name -
-

- {specialSponsor.sponsor.name || specialSponsor.sponsor.login} -

-
+
+ {specialSponsors.map((sponsor) => ( +
+ {sponsor.sponsor.name +
+

+ {sponsor.sponsor.name || sponsor.sponsor.login} +

+
+
+ ))}
); diff --git a/apps/web/src/lib/sponsor-utils.ts b/apps/web/src/lib/sponsor-utils.ts new file mode 100644 index 0000000..2770a11 --- /dev/null +++ b/apps/web/src/lib/sponsor-utils.ts @@ -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(/\/$/, ""); +};