From dfd4b5682d9d4193a52c7971b1febed09efe62fe Mon Sep 17 00:00:00 2001 From: richardscull <106016833+richardscull@users.noreply.github.com> Date: Sun, 25 Jan 2026 03:17:36 +0200 Subject: [PATCH] feat: Show problem details error toast on request with status > 404 --- lib/hooks/api/beatmap/useBeatmap.ts | 15 ++++++-- lib/hooks/api/beatmap/useBeatmapSet.ts | 8 ++++- lib/hooks/useToastApiRequestFailed.ts | 49 ++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 lib/hooks/useToastApiRequestFailed.ts diff --git a/lib/hooks/api/beatmap/useBeatmap.ts b/lib/hooks/api/beatmap/useBeatmap.ts index f976232..9001229 100644 --- a/lib/hooks/api/beatmap/useBeatmap.ts +++ b/lib/hooks/api/beatmap/useBeatmap.ts @@ -4,8 +4,17 @@ import useSWR from "swr"; import type { BeatmapResponse } from "@/lib/types/api"; +import { useToastApiRequestFailed } from "../../useToastApiRequestFailed"; + export function useBeatmap(beatmapId: number | null) { - return useSWR(beatmapId ? `beatmap/${beatmapId}` : null, { - dedupingInterval: 1000 * 60 * 10, - }); + const swrResult = useSWR( + beatmapId ? `beatmap/${beatmapId}` : null, + { + dedupingInterval: 1000 * 60 * 10, + }, + ); + + useToastApiRequestFailed(swrResult); + + return swrResult; } diff --git a/lib/hooks/api/beatmap/useBeatmapSet.ts b/lib/hooks/api/beatmap/useBeatmapSet.ts index d4fa74f..b08046b 100644 --- a/lib/hooks/api/beatmap/useBeatmapSet.ts +++ b/lib/hooks/api/beatmap/useBeatmapSet.ts @@ -4,11 +4,17 @@ import useSWR from "swr"; import type { BeatmapSetResponse } from "@/lib/types/api"; +import { useToastApiRequestFailed } from "../../useToastApiRequestFailed"; + export function useBeatmapSet(beatmapSetId: number | null) { - return useSWR( + const swrResult = useSWR( beatmapSetId ? `beatmapset/${beatmapSetId}` : null, { dedupingInterval: 1000 * 60 * 10, }, ); + + useToastApiRequestFailed(swrResult); + + return swrResult; } diff --git a/lib/hooks/useToastApiRequestFailed.ts b/lib/hooks/useToastApiRequestFailed.ts new file mode 100644 index 0000000..9d30748 --- /dev/null +++ b/lib/hooks/useToastApiRequestFailed.ts @@ -0,0 +1,49 @@ +"use client"; + +import type { HTTPError } from "ky"; +import { useEffect, useRef } from "react"; +import type { SWRResponse } from "swr"; + +import { useToast } from "@/hooks/use-toast"; + +import type { ProblemDetailsResponseType } from "../types/api"; + +export function useToastApiRequestFailed(swrResult: SWRResponse) { + const { toast } = useToast(); + const lastErrorRef = useRef(null); + + useEffect(() => { + const { error } = swrResult; + + if (error && error !== lastErrorRef.current) { + lastErrorRef.current = error; + + if (error instanceof Error && "response" in error) { + const httpError = error as HTTPError; + + if (httpError.response?.status && httpError.response.status >= 404) { + try { + const problemDetails = JSON.parse(httpError.message) as ProblemDetailsResponseType; + + if (!problemDetails.detail) + throw new Error("No detail in problem details response"); + + toast({ + title: "Error", + description: problemDetails.detail, + variant: "destructive", + }); + } + catch { + // Ignore the error if it's not a ProblemDetailsResponseType + } + } + } + } + + if (!error && lastErrorRef.current) { + lastErrorRef.current = null; + } + // eslint-disable-next-line react-hooks/exhaustive-deps -- Only need to react to error changes + }, [swrResult.error, toast]); +}