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() {