From 4f522088fb37548203302205baa999da16f8ac27 Mon Sep 17 00:00:00 2001 From: Damian Stasik <920747+damianstasik@users.noreply.github.com> Date: Wed, 28 Aug 2024 20:32:39 +0200 Subject: [PATCH] Improve error handling in the UI Signed-off-by: Damian Stasik <920747+damianstasik@users.noreply.github.com> --- frontend/package-lock.json | 13 +++++ frontend/package.json | 1 + frontend/src/q.ts | 9 +-- frontend/src/query.ts | 3 + frontend/src/routes/Error/index.tsx | 24 ++++---- frontend/src/routes/Module/query.ts | 29 +++++----- frontend/src/routes/ModuleExample/query.ts | 10 ++-- frontend/src/routes/ModuleSubmodule/query.ts | 10 ++-- frontend/src/routes/Modules/query.ts | 10 ++-- frontend/src/routes/Provider/query.ts | 58 ++++++++------------ frontend/src/routes/Providers/query.ts | 10 ++-- frontend/src/utils/errors.tsx | 9 +++ 12 files changed, 93 insertions(+), 93 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f55fe4cb..d5c466e0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -22,6 +22,7 @@ "eslint-plugin-react-hooks": "^5.1.0-rc.0", "eslint-plugin-react-refresh": "^0.4.11", "globals": "^15.9.0", + "ky": "^1.7.1", "lunr": "^2.3.9", "openapi-typescript": "^5.4.2", "postcss": "^8.4.41", @@ -3559,6 +3560,18 @@ "json-buffer": "3.0.1" } }, + "node_modules/ky": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/ky/-/ky-1.7.1.tgz", + "integrity": "sha512-KJ/IXXkFhTDqxcN8wKqMXk1/UoOpc0UnOB6H7QcqlPInh/M2B5Mlj+i9exez1w4RSwJhNFmHiUDPriAYFwb5VA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/ky?sponsor=1" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 9b6c1552..979af420 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -27,6 +27,7 @@ "eslint-plugin-react-hooks": "^5.1.0-rc.0", "eslint-plugin-react-refresh": "^0.4.11", "globals": "^15.9.0", + "ky": "^1.7.1", "lunr": "^2.3.9", "openapi-typescript": "^5.4.2", "postcss": "^8.4.41", diff --git a/frontend/src/q.ts b/frontend/src/q.ts index ee96ad85..b957548e 100644 --- a/frontend/src/q.ts +++ b/frontend/src/q.ts @@ -1,16 +1,13 @@ import { queryOptions } from "@tanstack/react-query"; import lunr from "lunr"; +import { api } from "./query"; export const getSearchIndexQuery = () => queryOptions({ queryKey: ["search-index"], queryFn: async () => { - const response = await fetch( - `${import.meta.env.VITE_DATA_API_URL}/search.json`, - ); + const data = await api(`search.json`).json(); - const res = await response.json(); - - return lunr.Index.load(res); + return lunr.Index.load(data); }, }); diff --git a/frontend/src/query.ts b/frontend/src/query.ts index 9513ade6..d2cf541a 100644 --- a/frontend/src/query.ts +++ b/frontend/src/query.ts @@ -1,4 +1,5 @@ import { QueryClient } from "@tanstack/react-query"; +import ky from "ky"; export const queryClient = new QueryClient({ defaultOptions: { @@ -7,3 +8,5 @@ export const queryClient = new QueryClient({ }, }, }); + +export const api = ky.create({ prefixUrl: import.meta.env.VITE_DATA_API_URL }); diff --git a/frontend/src/routes/Error/index.tsx b/frontend/src/routes/Error/index.tsx index 42f22b0e..8bd13019 100644 --- a/frontend/src/routes/Error/index.tsx +++ b/frontend/src/routes/Error/index.tsx @@ -1,21 +1,19 @@ import { useRouteError } from "react-router-dom"; -import { Header } from "../../components/Header"; -import { Paragraph } from "../../components/Paragraph"; -import PatternBg from "../../components/PatternBg"; -import { NotFoundPageError } from "@/utils/errors"; +import { Header } from "@/components/Header"; +import { Paragraph } from "@/components/Paragraph"; +import PatternBg from "@/components/PatternBg"; +import { is404Error } from "@/utils/errors"; export function Error() { const routeError = useRouteError() as Error; - const title = - routeError instanceof NotFoundPageError - ? "Page Not Found" - : "An Error Occurred"; + const is404 = is404Error(routeError); - const message = - routeError instanceof NotFoundPageError - ? "The page you are looking for does not exist." - : "We're sorry, but an unexpected error occurred. Please try again later."; + const title = is404 ? "Page Not Found" : "An Error Occurred"; + + const message = is404 + ? "The page you are looking for does not exist." + : "We're sorry, but an unexpected error occurred. Please try again later."; return ( <> @@ -24,7 +22,7 @@ export function Error() { <main className="container m-auto flex flex-col items-center gap-8 text-center"> <h2 className="text-6xl font-bold">{title}</h2> <Paragraph className="text-balance">{message}</Paragraph> - {!!routeError.message && ( + {import.meta.env.DEV && !!routeError.message && ( <pre className="text-balance">{routeError.message}</pre> )} </main> diff --git a/frontend/src/routes/Module/query.ts b/frontend/src/routes/Module/query.ts index f806d264..0bd7726e 100644 --- a/frontend/src/routes/Module/query.ts +++ b/frontend/src/routes/Module/query.ts @@ -1,4 +1,5 @@ import { definitions } from "@/api"; +import { api } from "@/query"; import { queryOptions } from "@tanstack/react-query"; export const getModuleVersionDataQuery = ( @@ -10,13 +11,11 @@ export const getModuleVersionDataQuery = ( return queryOptions({ queryKey: ["module-version", namespace, name, target, version], queryFn: async () => { - const response = await fetch( - `${import.meta.env.VITE_DATA_API_URL}/modules/${namespace}/${name}/${target}/${version}/index.json`, - ); + const data = await api( + `modules/${namespace}/${name}/${target}/${version}/index.json`, + ).json<definitions["ModuleVersion"]>(); - const data = await response.json(); - - return data as definitions["ModuleVersion"]; + return data; }, }); }; @@ -29,13 +28,11 @@ export const getModuleDataQuery = ( return queryOptions({ queryKey: ["module", namespace, name, target], queryFn: async () => { - const response = await fetch( - `${import.meta.env.VITE_DATA_API_URL}/modules/${namespace}/${name}/${target}/index.json`, - ); - - const data = await response.json(); + const data = await api( + `modules/${namespace}/${name}/${target}/index.json`, + ).json<definitions["Module"]>(); - return data as definitions["Module"]; + return data; }, }); }; @@ -49,11 +46,11 @@ export const getModuleReadmeQuery = ( return queryOptions({ queryKey: ["module-readme", namespace, name, target, version], queryFn: async () => { - const response = await fetch( - `${import.meta.env.VITE_DATA_API_URL}/modules/${namespace}/${name}/${target}/${version}/README.md`, - ); + const data = await api( + `modules/${namespace}/${name}/${target}/${version}/README.md`, + ).text(); - return response.text(); + return data; }, }); }; diff --git a/frontend/src/routes/ModuleExample/query.ts b/frontend/src/routes/ModuleExample/query.ts index c6597ce2..d0ef72cc 100644 --- a/frontend/src/routes/ModuleExample/query.ts +++ b/frontend/src/routes/ModuleExample/query.ts @@ -1,4 +1,4 @@ -import { queryClient } from "@/query"; +import { api, queryClient } from "@/query"; import { queryOptions } from "@tanstack/react-query"; import { getModuleVersionDataQuery } from "../Module/query"; import { NotFoundPageError } from "@/utils/errors"; @@ -20,11 +20,11 @@ export const getModuleExampleReadmeQuery = ( example, ], queryFn: async () => { - const response = await fetch( - `${import.meta.env.VITE_DATA_API_URL}/modules/${namespace}/${name}/${target}/${version}/examples/${example}/README.md`, - ); + const data = await api( + `modules/${namespace}/${name}/${target}/${version}/examples/${example}/README.md`, + ).text(); - return response.text(); + return data; }, }); }; diff --git a/frontend/src/routes/ModuleSubmodule/query.ts b/frontend/src/routes/ModuleSubmodule/query.ts index 3418effe..e51cd116 100644 --- a/frontend/src/routes/ModuleSubmodule/query.ts +++ b/frontend/src/routes/ModuleSubmodule/query.ts @@ -1,4 +1,4 @@ -import { queryClient } from "@/query"; +import { api, queryClient } from "@/query"; import { queryOptions } from "@tanstack/react-query"; import { getModuleVersionDataQuery } from "../Module/query"; import { NotFoundPageError } from "@/utils/errors"; @@ -20,11 +20,11 @@ export const getModuleSubmoduleReadmeQuery = ( submodule, ], queryFn: async () => { - const response = await fetch( - `${import.meta.env.VITE_DATA_API_URL}/modules/${namespace}/${name}/${target}/${version}/modules/${submodule}/README.md`, - ); + const data = await api( + `modules/${namespace}/${name}/${target}/${version}/modules/${submodule}/README.md`, + ).text(); - return response.text(); + return data; }, }); }; diff --git a/frontend/src/routes/Modules/query.ts b/frontend/src/routes/Modules/query.ts index 6fce1acf..43de0374 100644 --- a/frontend/src/routes/Modules/query.ts +++ b/frontend/src/routes/Modules/query.ts @@ -1,16 +1,14 @@ import { queryOptions } from "@tanstack/react-query"; import { definitions } from "@/api"; +import { api } from "@/query"; export const getModulesQuery = () => queryOptions({ queryKey: ["modules"], queryFn: async () => { - const response = await fetch( - `${import.meta.env.VITE_DATA_API_URL}/modules/index.json`, - ); + const data = + await api(`modules/index.json`).json<definitions["ModuleList"]>(); - const res = await response.json(); - - return res.modules as definitions["ModuleList"]["modules"]; + return data.modules; }, }); diff --git a/frontend/src/routes/Provider/query.ts b/frontend/src/routes/Provider/query.ts index e87e8098..d12e4396 100644 --- a/frontend/src/routes/Provider/query.ts +++ b/frontend/src/routes/Provider/query.ts @@ -1,26 +1,21 @@ import { definitions } from "@/api"; -import { queryOptions, skipToken } from "@tanstack/react-query"; +import { api } from "@/query"; +import { queryOptions } from "@tanstack/react-query"; export const getProviderVersionDataQuery = ( namespace: string | undefined, provider: string | undefined, version: string | undefined, ) => { - const hasParams = namespace && provider && version; - return queryOptions({ queryKey: ["provider-version", namespace, provider, version], - queryFn: hasParams - ? async () => { - const response = await fetch( - `${import.meta.env.VITE_DATA_API_URL}/providers/${namespace}/${provider}/${version}/index.json`, - ); - - const data = await response.json(); + queryFn: async () => { + const data = await api( + `providers/${namespace}/${provider}/${version}/index.json`, + ).json<definitions["ProviderVersion"]>(); - return data as definitions["ProviderVersion"]; - } - : skipToken, + return data; + }, }); }; @@ -35,18 +30,14 @@ export const getProviderDocsQuery = ( return queryOptions({ queryKey: ["provider-doc", namespace, provider, type, name, lang, version], queryFn: async () => { - try { - const urlBase = `${import.meta.env.VITE_DATA_API_URL}/providers/${namespace}/${provider}/${version}`; - const requestURL = - type === undefined && name === undefined - ? `${urlBase}/index.md` - : `${urlBase}/${lang ? `cdktf/${lang}/` : ""}${type}/${name}.md`; - - const response = await fetch(requestURL); - return response.text(); - } catch { - return ""; - } + const urlBase = `providers/${namespace}/${provider}/${version}`; + const requestURL = + type === undefined && name === undefined + ? `${urlBase}/index.md` + : `${urlBase}/${lang ? `cdktf/${lang}/` : ""}${type}/${name}.md`; + + const response = await api(requestURL).text(); + return response; }, }); }; @@ -57,17 +48,12 @@ export const getProviderDataQuery = ( ) => { return queryOptions({ queryKey: ["provider", namespace, provider], - queryFn: - namespace && provider - ? async () => { - const response = await fetch( - `${import.meta.env.VITE_DATA_API_URL}/providers/${namespace}/${provider}/index.json`, - ); - - const data = await response.json(); + queryFn: async () => { + const data = await api( + `providers/${namespace}/${provider}/index.json`, + ).json<definitions["Provider"]>(); - return data as definitions["Provider"]; - } - : skipToken, + return data; + }, }); }; diff --git a/frontend/src/routes/Providers/query.ts b/frontend/src/routes/Providers/query.ts index e4c3fa48..cb38bdb3 100644 --- a/frontend/src/routes/Providers/query.ts +++ b/frontend/src/routes/Providers/query.ts @@ -1,16 +1,14 @@ import { queryOptions } from "@tanstack/react-query"; import { definitions } from "@/api"; +import { api } from "@/query"; export const getProvidersQuery = () => queryOptions({ queryKey: ["providers"], queryFn: async () => { - const response = await fetch( - `${import.meta.env.VITE_DATA_API_URL}/providers/index.json`, - ); + const data = + await api(`providers/index.json`).json<definitions["ProviderList"]>(); - const res = await response.json(); - - return res.providers as definitions["ProviderList"]["providers"]; + return data.providers; }, }); diff --git a/frontend/src/utils/errors.tsx b/frontend/src/utils/errors.tsx index f8eca856..60d9fd2b 100644 --- a/frontend/src/utils/errors.tsx +++ b/frontend/src/utils/errors.tsx @@ -1 +1,10 @@ +import { HTTPError } from "ky"; + export class NotFoundPageError extends Error {} + +export function is404Error(error: unknown) { + return ( + error instanceof NotFoundPageError || + (error instanceof HTTPError && error.response.status === 404) + ); +}