mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
fix sponsor data handling
This commit is contained in:
@@ -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
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 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>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 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>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
Run without installing globally:
|
Run without installing globally:
|
||||||
|
|||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
88
apps/web/src/lib/sponsor-utils.ts
Normal file
88
apps/web/src/lib/sponsor-utils.ts
Normal 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(/\/$/, "");
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user