Game profile: granular IGDB platforms and free-game badge
- List IGDB platform names verbatim (Windows, MacOS) instead of coarse buckets; fall back to Steam OS flags and hardware distributors only when IGDB has none - Show a Free badge instead of prices when best price is zero
This commit is contained in:
5 files changed
+42
-22
No files matched your search
@@ -161,6 +161,7 @@ export function GameProfileModal({
|
||||
const name = profile?.name ?? fallbackName;
|
||||
const best = profile?.itad?.prices ?? null;
|
||||
const low = profile?.itad?.historyLow ?? null;
|
||||
const isFree = !!best && best.price === 0;
|
||||
const hero = useMemo(() => {
|
||||
if (bannerPick) {
|
||||
return bannerPick;
|
||||
@@ -539,6 +540,11 @@ export function GameProfileModal({
|
||||
<p className="font-mono text-xs uppercase tracking-wider text-zinc-500">
|
||||
{dict.prices}
|
||||
</p>
|
||||
{isFree ? (
|
||||
<p className="mt-1.5 inline-block rounded-full border border-mint/20 bg-mint/5 px-4 py-1.5 text-xs font-semibold text-mint">
|
||||
{dict.free}
|
||||
</p>
|
||||
) : (
|
||||
<div className="mt-1.5 grid gap-2 sm:grid-cols-2">
|
||||
{best ? (
|
||||
<a
|
||||
@@ -571,6 +577,7 @@ export function GameProfileModal({
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
|
||||
+32
-22
@@ -88,9 +88,7 @@ async function getDetectables(): Promise<DetectableEntry[]> {
|
||||
return detectablesCache.data;
|
||||
}
|
||||
try {
|
||||
const response = await fetch("https://cdn.discordapp.com/detectables/games-v1.json", {
|
||||
next: { revalidate: 86_400 },
|
||||
});
|
||||
const response = await fetch("https://cdn.discordapp.com/detectables/games-v1.json");
|
||||
if (!response.ok) {
|
||||
console.error(`detectables failed: ${response.status}`);
|
||||
return detectablesCache?.data ?? [];
|
||||
@@ -104,10 +102,6 @@ async function getDetectables(): Promise<DetectableEntry[]> {
|
||||
}
|
||||
|
||||
const DISTRIBUTOR_PLATFORM: Record<string, string> = {
|
||||
steam: "PC",
|
||||
battlenet: "PC",
|
||||
gog: "PC",
|
||||
epic: "PC",
|
||||
xbox: "Xbox",
|
||||
xbox_title: "Xbox",
|
||||
xbox_game_pass: "Xbox",
|
||||
@@ -220,10 +214,16 @@ interface IgdbGame {
|
||||
|
||||
let igdbTokenCache: { token: string; expires: number } | null = null;
|
||||
|
||||
let igdbKeyWarned = false;
|
||||
|
||||
async function getIgdbToken(): Promise<string | null> {
|
||||
const clientId = process.env.TWITCH_CLIENT_ID;
|
||||
const clientSecret = process.env.TWITCH_CLIENT_SECRET;
|
||||
if (!clientId || !clientSecret) {
|
||||
if (!igdbKeyWarned) {
|
||||
igdbKeyWarned = true;
|
||||
console.error("igdb keys missing: set TWITCH_CLIENT_ID and TWITCH_CLIENT_SECRET");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (igdbTokenCache && Date.now() < igdbTokenCache.expires) {
|
||||
@@ -386,9 +386,15 @@ interface ItadOverviewPrice {
|
||||
} | null;
|
||||
}
|
||||
|
||||
let itadKeyWarned = false;
|
||||
|
||||
async function itadFetch<T>(path: string, init?: RequestInit): Promise<T | null> {
|
||||
const key = process.env.ITAD_API_KEY;
|
||||
if (!key) {
|
||||
if (!itadKeyWarned) {
|
||||
itadKeyWarned = true;
|
||||
console.error("itad key missing: set ITAD_API_KEY");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
@@ -499,24 +505,28 @@ export async function getGameProfile(
|
||||
|
||||
const igdbPlatforms = new Set<string>();
|
||||
for (const p of igdb?.platforms ?? []) {
|
||||
const label = `${p.abbreviation ?? ""} ${p.name ?? ""}`.toUpperCase();
|
||||
if (/PS|PLAYSTATION/.test(label)) {
|
||||
igdbPlatforms.add("PlayStation");
|
||||
} else if (/XBOX|XONE|XSX/.test(label)) {
|
||||
igdbPlatforms.add("Xbox");
|
||||
} else if (/PC|WIN|MAC|LIN/.test(label)) {
|
||||
igdbPlatforms.add("PC");
|
||||
} else if (/SWITCH/.test(label)) {
|
||||
igdbPlatforms.add("Switch");
|
||||
const raw = (p.name ?? "").trim();
|
||||
if (!raw) {
|
||||
continue;
|
||||
}
|
||||
if (raw === "PC (Microsoft Windows)") {
|
||||
igdbPlatforms.add("Windows");
|
||||
} else if (raw === "Mac") {
|
||||
igdbPlatforms.add("MacOS");
|
||||
} else {
|
||||
igdbPlatforms.add(raw);
|
||||
}
|
||||
}
|
||||
const platforms = new Set<string>(igdbPlatforms);
|
||||
if (platforms.size === 0) {
|
||||
if (
|
||||
steam?.platforms &&
|
||||
(steam.platforms.windows || steam.platforms.mac || steam.platforms.linux)
|
||||
) {
|
||||
platforms.add("PC");
|
||||
if (platforms.size === 0 && steam?.platforms) {
|
||||
if (steam.platforms.windows) {
|
||||
platforms.add("Windows");
|
||||
}
|
||||
if (steam.platforms.mac) {
|
||||
platforms.add("MacOS");
|
||||
}
|
||||
if (steam.platforms.linux) {
|
||||
platforms.add("Linux");
|
||||
}
|
||||
for (const sku of entry?.third_party_skus ?? []) {
|
||||
const mapped = DISTRIBUTOR_PLATFORM[sku.distributor];
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"links": "Links",
|
||||
"reviews": "Bewertungen",
|
||||
"prices": "Preise",
|
||||
"free": "Kostenlos",
|
||||
"bestPrice": "Bester Preis",
|
||||
"historyLow": "Historischer Tiefstpreis",
|
||||
"allPrices": "ITAD",
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"links": "Links",
|
||||
"reviews": "Reviews",
|
||||
"prices": "Prices",
|
||||
"free": "Free",
|
||||
"bestPrice": "Best Price",
|
||||
"historyLow": "All-Time Low",
|
||||
"allPrices": "ITAD",
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"links": "Bağlantılar",
|
||||
"reviews": "İncelemeler",
|
||||
"prices": "Fiyatlar",
|
||||
"free": "Ücretsiz",
|
||||
"bestPrice": "En İyi Fiyat",
|
||||
"historyLow": "Tarihi En Düşük",
|
||||
"allPrices": "ITAD",
|
||||
|
||||
Reference in new issue
Block a user