Game profile: upstream failure logs and retry affordance
- 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:
5 files changed
+43
-2
No files matched your search
@@ -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 ? (
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in new issue
Block a user