Game profile: upstream failure logs and retry affordance
Build & Push / Lint & Build (push) Successful in 1m17s
Build & Push / Build & Push Image (push) Successful in 28s
Build & Push / Trigger Deploy (push) Successful in 1s

- Log Steam, ITAD and detectables upstream failures with
  status codes for diagnosis
- Show retry affordance when all profile sources come back
  empty or the load fails
This commit is contained in:
fristy committed 2026-09-11 06:29:54 +03:00
1 parent cbbbc3effa
commit d778a18a49
5 files changed
+43 -2

No files matched your search

+33 -2
View File
@@ -108,6 +108,7 @@ export function GameProfileModal({
}) { }) {
const [profile, setProfile] = useState<GameProfile | null>(null); const [profile, setProfile] = useState<GameProfile | null>(null);
const [failed, setFailed] = useState(false); const [failed, setFailed] = useState(false);
const [attempt, setAttempt] = useState(0);
const [bannerPick, setBannerPick] = useState<string | null>(null); const [bannerPick, setBannerPick] = useState<string | null>(null);
const tag = LOCALE_TAGS[lang] ?? "en-US"; const tag = LOCALE_TAGS[lang] ?? "en-US";
const country = COUNTRIES[lang] ?? "US"; const country = COUNTRIES[lang] ?? "US";
@@ -125,6 +126,7 @@ export function GameProfileModal({
const json = (await response.json()) as GameProfile; const json = (await response.json()) as GameProfile;
if (!cancelled) { if (!cancelled) {
setProfile(json); setProfile(json);
setFailed(false);
} }
} catch { } catch {
if (!cancelled) { if (!cancelled) {
@@ -136,7 +138,9 @@ export function GameProfileModal({
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, [applicationId, lang, country]); }, [applicationId, lang, country, attempt]);
const partialFail =
!!profile && !profile.sources.igdb && !profile.sources.steam && !profile.sources.itad;
useEffect(() => { useEffect(() => {
function onKey(event: KeyboardEvent): void { function onKey(event: KeyboardEvent): void {
@@ -292,7 +296,34 @@ export function GameProfileModal({
<p className="mt-4 animate-pulse text-sm text-zinc-500">{dict.loading}</p> <p className="mt-4 animate-pulse text-sm text-zinc-500">{dict.loading}</p>
) : null} ) : null}
{failed && !profile ? ( {failed && !profile ? (
<p className="mt-4 text-sm text-zinc-500">{dict.loadError}</p> <div className="mt-4 flex items-center gap-3 text-sm text-zinc-500">
<p>{dict.loadError}</p>
<button
type="button"
onClick={() => {
setFailed(false);
setAttempt((n) => n + 1);
}}
className="shrink-0 cursor-pointer rounded-full border border-white/10 px-3 py-1 text-xs text-zinc-300 transition-colors hover:border-white/25 hover:text-white"
>
{dict.retry}
</button>
</div>
) : null}
{partialFail ? (
<div className="mt-4 flex items-center gap-3 rounded-xl border border-white/5 bg-white/[0.03] p-3 text-sm text-zinc-400">
<p className="flex-1">{dict.partialFail}</p>
<button
type="button"
onClick={() => {
setProfile(null);
setAttempt((n) => n + 1);
}}
className="shrink-0 cursor-pointer rounded-full border border-white/10 px-3 py-1 text-xs text-zinc-300 transition-colors hover:border-white/25 hover:text-white"
>
{dict.retry}
</button>
</div>
) : null} ) : null}
{profile?.description ? ( {profile?.description ? (
+4
View File
@@ -92,6 +92,7 @@ async function getDetectables(): Promise<DetectableEntry[]> {
next: { revalidate: 86_400 }, next: { revalidate: 86_400 },
}); });
if (!response.ok) { if (!response.ok) {
console.error(`detectables failed: ${response.status}`);
return detectablesCache?.data ?? []; return detectablesCache?.data ?? [];
} }
const data = (await response.json()) as DetectableEntry[]; const data = (await response.json()) as DetectableEntry[];
@@ -153,6 +154,7 @@ async function getSteamDetails(
{ next: { revalidate: 86_400 } }, { next: { revalidate: 86_400 } },
); );
if (!response.ok) { if (!response.ok) {
console.error(`steam details failed: ${steamId} ${response.status}`);
return null; return null;
} }
const json = (await response.json()) as Record<string, SteamAppDetails>; const json = (await response.json()) as Record<string, SteamAppDetails>;
@@ -177,6 +179,7 @@ async function getSteamReviewSummary(steamId: string): Promise<GameReviewSummary
"?json=1&language=all&purchase_type=all&num_per_page=0"; "?json=1&language=all&purchase_type=all&num_per_page=0";
const response = await fetch(url, { next: { revalidate: 3600 } }); const response = await fetch(url, { next: { revalidate: 3600 } });
if (!response.ok) { if (!response.ok) {
console.error(`steam reviews failed: ${steamId} ${response.status}`);
return null; return null;
} }
const json = (await response.json()) as { query_summary?: SteamReviewQuery }; const json = (await response.json()) as { query_summary?: SteamReviewQuery };
@@ -398,6 +401,7 @@ async function itadFetch<T>(path: string, init?: RequestInit): Promise<T | null>
}, },
); );
if (!response.ok) { if (!response.ok) {
console.error(`itad failed: ${path.split("?")[0]} ${response.status}`);
return null; return null;
} }
return (await response.json()) as T; return (await response.json()) as T;
+2
View File
@@ -46,6 +46,8 @@
"openOnIgdb": "IGDB", "openOnIgdb": "IGDB",
"loading": "Spieldaten werden geladen...", "loading": "Spieldaten werden geladen...",
"loadError": "Spieldaten konnten nicht geladen werden.", "loadError": "Spieldaten konnten nicht geladen werden.",
"retry": "Erneut versuchen",
"partialFail": "Einige Daten konnten nicht geladen werden.",
"screenshots": "Screenshots", "screenshots": "Screenshots",
"reviewsUnit": "Bewertungen", "reviewsUnit": "Bewertungen",
"scrollBack": "Zurück", "scrollBack": "Zurück",
+2
View File
@@ -46,6 +46,8 @@
"openOnIgdb": "IGDB", "openOnIgdb": "IGDB",
"loading": "Loading game info...", "loading": "Loading game info...",
"loadError": "Couldn't load game info.", "loadError": "Couldn't load game info.",
"retry": "Retry",
"partialFail": "Some data failed to load.",
"screenshots": "Screenshots", "screenshots": "Screenshots",
"reviewsUnit": "reviews", "reviewsUnit": "reviews",
"scrollBack": "Back", "scrollBack": "Back",
+2
View File
@@ -46,6 +46,8 @@
"openOnIgdb": "IGDB", "openOnIgdb": "IGDB",
"loading": "Oyun bilgileri yükleniyor...", "loading": "Oyun bilgileri yükleniyor...",
"loadError": "Oyun bilgileri yüklenemedi.", "loadError": "Oyun bilgileri yüklenemedi.",
"retry": "Tekrar Dene",
"partialFail": "Bazı veriler yüklenemedi.",
"screenshots": "Görseller", "screenshots": "Görseller",
"reviewsUnit": "değerlendirme", "reviewsUnit": "değerlendirme",
"scrollBack": "Geri", "scrollBack": "Geri",