From 6ef08f4b911ab792c5b8fe0aab94a7029a1cb2c5 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 11 Jun 2026 11:12:31 +0200 Subject: [PATCH] generic error view with try again --- src/components/Error/GenericErrorView.tsx | 68 +++++++++++++++++++++++ src/hooks/useLichess.ts | 45 ++++++++++----- src/pages/Callback.tsx | 11 +++- src/routes/__root.tsx | 2 + src/routes/callback.tsx | 5 ++ 5 files changed, 114 insertions(+), 17 deletions(-) create mode 100644 src/components/Error/GenericErrorView.tsx diff --git a/src/components/Error/GenericErrorView.tsx b/src/components/Error/GenericErrorView.tsx new file mode 100644 index 0000000..886816d --- /dev/null +++ b/src/components/Error/GenericErrorView.tsx @@ -0,0 +1,68 @@ +import { useNavigate, useRouter } from "@tanstack/react-router"; + +interface GenericErrorViewProps { + error?: Error | unknown; + resetErrorBoundary?: () => void; +} + +const GenericErrorView = ({ + error, + resetErrorBoundary, +}: GenericErrorViewProps) => { + const router = useRouter(); + const navigate = useNavigate(); + + const errorMessage = + error instanceof Error + ? error.message + : "An unexpected system error occurred."; + + const handleTryAgain = async () => { + if (resetErrorBoundary) { + resetErrorBoundary(); + } + + await router.invalidate(); + const currentSearch = router.state.location.search; + await navigate({ to: ".", search: currentSearch }); + }; + + return ( +
+
+

+ Something went wrong +

+

+ The application encountered an issue it couldn't recover from. +

+ +
+ Error Logs: + {errorMessage} +
+ +
+ {resetErrorBoundary && ( + + )} + +
+
+
+ ); +}; + +export default GenericErrorView; diff --git a/src/hooks/useLichess.ts b/src/hooks/useLichess.ts index 5641dbc..da0a30c 100644 --- a/src/hooks/useLichess.ts +++ b/src/hooks/useLichess.ts @@ -1,4 +1,3 @@ -import { useNavigate } from "@tanstack/react-router"; import { useServerFn } from "@tanstack/react-start"; import { useEffect, useState } from "react"; import { useLichessUser } from "#/context/LichessUserContext.tsx"; @@ -14,6 +13,8 @@ import { } from "#/server/lichess.ts"; import { getHeaders } from "#/utils/pgnUtils.ts"; +type Status = "loading" | "success" | "error"; + interface ITokenData { access_token: string; expires_in: number; @@ -85,24 +86,40 @@ export const useLichess = () => { return getSessionFn(); }; - const useLichessCallback = async ({ code }: { code: string }) => { - const navigate = useNavigate(); + const useLichessCallback = ({ code }: { code: string }) => { + const [status, setStatus] = useState("loading"); + const [error, setError] = useState(null); useEffect(() => { if (!code) return; - (async () => { - const tokenData = await lichessAccessToken({ code }); - const userData = await getLichessUser({ tokenData }); + const isMounted = true; - await setLichessUser({ - username: userData.username, - id: userData.id, - token: tokenData.access_token, - }); - await navigate({ to: "/chessboard" }); - })(); - }, [navigate, code]); + try { + (async () => { + const tokenData = await lichessAccessToken({ code }); + const userData = await getLichessUser({ tokenData }); + + if (!isMounted) return; + + await setLichessUser({ + username: userData.username, + id: userData.id, + token: tokenData.access_token, + }); + + setStatus("success"); + })(); + } catch (err: unknown) { + if (isMounted && err instanceof Error) { + console.error("Lichess Authentication Error:", err); + setError(err.message || "Failed to authenticate with Lichess"); + setStatus("error"); + } + } + }, [code]); + + return { status, error }; }; const getLichessUserStudies = async () => { diff --git a/src/pages/Callback.tsx b/src/pages/Callback.tsx index 9f06a1b..3ba34be 100644 --- a/src/pages/Callback.tsx +++ b/src/pages/Callback.tsx @@ -1,14 +1,19 @@ -import { getRouteApi } from "@tanstack/react-router"; +import { getRouteApi, useNavigate } from "@tanstack/react-router"; import { useLichess } from "#/hooks/useLichess.ts"; const routeApi = getRouteApi("/callback"); const Callback = () => { + const navigate = useNavigate(); const { useLichessCallback } = useLichess(); const { code } = routeApi.useSearch(); + const { status, error } = useLichessCallback({ code }); - // TODO deal with error if code is invalid - useLichessCallback({ code }).then(); + if (status === "success") navigate({ to: "/chessboard" }); + + if (status === "error") { + console.error(error); + } return
Loading...
; }; diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index 2e3ce86..f0baf99 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -3,6 +3,7 @@ import { createRootRoute, HeadContent, Scripts } from "@tanstack/react-router"; import { TanStackRouterDevtoolsPanel } from "@tanstack/react-router-devtools"; import { type ReactNode, Suspense } from "react"; import { ToastContainer } from "react-toastify"; +import GenericErrorView from "#/components/Error/GenericErrorView.tsx"; import { MatomoAnalytics } from "#/components/MatomoAnalytics.tsx"; import { LichessUserProvider } from "#/context/LichessUserContext.tsx"; import Footer from "../components/Footer"; @@ -43,6 +44,7 @@ export const Route = createRootRoute({ ], }), shellComponent: RootDocument, + errorComponent: ({ error }) => , }); function RootDocument({ children }: { children: ReactNode }) { diff --git a/src/routes/callback.tsx b/src/routes/callback.tsx index 5c9bf96..8a329d1 100644 --- a/src/routes/callback.tsx +++ b/src/routes/callback.tsx @@ -1,5 +1,6 @@ import { createFileRoute, notFound } from "@tanstack/react-router"; import { z } from "zod"; +import GenericErrorView from "#/components/Error/GenericErrorView.tsx"; import Callback from "#/pages/Callback.tsx"; const callbackSchema = z.object({ @@ -14,7 +15,11 @@ export const Route = createFileRoute("/callback")({ throw notFound(); } }, + component: RouteComponent, + errorComponent: ({ error, reset }) => ( + + ), }); function RouteComponent() {