diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e45b55a9..90dd08fc 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.44", @@ -3576,6 +3577,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 69f79fdb..ced0cdcc 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.44", 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/router.tsx b/frontend/src/router.tsx index 702b2764..7b0fd58f 100644 --- a/frontend/src/router.tsx +++ b/frontend/src/router.tsx @@ -48,163 +48,212 @@ import { ProviderDocsError } from "./routes/Provider/Docs/error"; export const router = createBrowserRouter( [ { - id: "home", - index: true, - element: , - errorElement: , - }, - { - id: "providers", - path: "/providers", - element: , - errorElement: , - loader: providersLoader, - handle: { - crumb: () => createCrumb("/providers", "Providers"), - }, - }, - { - id: "modules", - path: "/modules", - element: , - errorElement: , - loader: modulesLoader, - handle: { - crumb: () => createCrumb("/modules", "Modules"), - }, - }, - { - id: "module", - path: "module", - handle: { - crumb: () => createCrumb("/modules", "Modules"), - }, errorElement: , children: [ { - path: ":namespace", + id: "home", + index: true, + element: , + }, + { + id: "providers", + path: "/providers", + element: , + loader: providersLoader, + handle: { + crumb: () => createCrumb("/providers", "Providers"), + }, + }, + { + id: "modules", + path: "/modules", + element: , + loader: modulesLoader, + handle: { + crumb: () => createCrumb("/modules", "Modules"), + }, + }, + { + id: "module", + path: "module", + handle: { + crumb: () => createCrumb("/modules", "Modules"), + }, children: [ { - index: true, - element: , - }, - { - id: "module-version", - path: ":name/:target/:version?", - loader: moduleLoader, - handle: { - middleware: moduleMiddleware, - crumb: ({ - namespace, - name, - target, - rawVersion, - }: ModuleRouteContext) => - createCrumb( - `/module/${namespace}/${name}/${target}/${rawVersion}`, - `${namespace}/${name}`, - ), - }, + path: ":namespace", children: [ { - element: , - children: [ - { - index: true, - element: , - loader: moduleReadmeLoader, - }, - { - path: "inputs", - element: , - }, - { - path: "outputs", - element: , - }, - { - path: "dependencies", - element: , - }, - { - path: "resources", - element: , - }, - ], + index: true, + element: , }, { - path: "example/:example", - element: , - loader: moduleExampleLoader, + id: "module-version", + path: ":name/:target/:version?", + loader: moduleLoader, handle: { - middleware: moduleExampleMiddleware, + middleware: moduleMiddleware, crumb: ({ namespace, name, target, - example, rawVersion, - }: ModuleRouteContext & ModuleExampleRouteContext) => + }: ModuleRouteContext) => createCrumb( - `/module/${namespace}/${name}/${target}/${rawVersion}/example/${example}`, - example, + `/module/${namespace}/${name}/${target}/${rawVersion}`, + `${namespace}/${name}`, ), }, children: [ { - index: true, - element: , - loader: moduleExampleReadmeLoader, + element: , + children: [ + { + index: true, + element: , + loader: moduleReadmeLoader, + }, + { + path: "inputs", + element: , + }, + { + path: "outputs", + element: , + }, + { + path: "dependencies", + element: , + }, + { + path: "resources", + element: , + }, + ], }, { - path: "inputs", - element: , + path: "example/:example", + element: , + loader: moduleExampleLoader, + handle: { + middleware: moduleExampleMiddleware, + crumb: ({ + namespace, + name, + target, + example, + rawVersion, + }: ModuleRouteContext & ModuleExampleRouteContext) => + createCrumb( + `/module/${namespace}/${name}/${target}/${rawVersion}/example/${example}`, + example, + ), + }, + children: [ + { + index: true, + element: , + loader: moduleExampleReadmeLoader, + }, + { + path: "inputs", + element: , + }, + { + path: "outputs", + element: , + }, + ], }, { - path: "outputs", - element: , + path: "submodule/:submodule", + element: , + loader: moduleSubmoduleLoader, + handle: { + middleware: moduleSubmoduleMiddleware, + crumb: ({ + namespace, + name, + target, + submodule, + rawVersion, + }: ModuleRouteContext & ModuleSubmoduleRouteContext) => + createCrumb( + `/module/${namespace}/${name}/${target}/${rawVersion}/submodule/${submodule}`, + submodule, + ), + }, + children: [ + { + index: true, + element: , + loader: moduleSubmoduleReadmeLoader, + }, + { + path: "inputs", + element: , + }, + { + path: "outputs", + element: , + }, + { + path: "dependencies", + element: , + }, + { + path: "resources", + element: , + }, + ], }, ], }, + ], + }, + ], + }, + { + id: "provider", + path: "/provider", + handle: { + crumb: () => createCrumb("/providers", "Providers"), + }, + children: [ + { + path: ":namespace", + children: [ + { + index: true, + element: , + }, { - path: "submodule/:submodule", - element: , - loader: moduleSubmoduleLoader, + path: ":provider/:version?", + element: , + loader: providerLoader, handle: { - middleware: moduleSubmoduleMiddleware, + middleware: providerMiddleware, crumb: ({ namespace, - name, - target, - submodule, - rawVersion, - }: ModuleRouteContext & ModuleSubmoduleRouteContext) => + provider, + version, + }: ProviderRouteContext) => createCrumb( - `/module/${namespace}/${name}/${target}/${rawVersion}/submodule/${submodule}`, - submodule, + `/provider/${namespace}/${provider}/${version}`, + `${namespace}/${provider}`, ), }, children: [ { index: true, - element: , - loader: moduleSubmoduleReadmeLoader, - }, - { - path: "inputs", - element: , - }, - { - path: "outputs", - element: , + element: , + loader: providerOverviewLoader, }, { - path: "dependencies", - element: , - }, - { - path: "resources", - element: , + path: "docs/:type/:doc", + element: , + loader: providerDocsLoader, + errorElement: , }, ], }, @@ -214,55 +263,6 @@ export const router = createBrowserRouter( }, ], }, - { - id: "provider", - path: "/provider", - handle: { - crumb: () => createCrumb("/providers", "Providers"), - }, - errorElement: , - children: [ - { - path: ":namespace", - children: [ - { - index: true, - element: , - }, - { - path: ":provider/:version?", - element: , - loader: providerLoader, - handle: { - middleware: providerMiddleware, - crumb: ({ - namespace, - provider, - version, - }: ProviderRouteContext) => - createCrumb( - `/provider/${namespace}/${provider}/${version}`, - `${namespace}/${provider}`, - ), - }, - children: [ - { - index: true, - element: , - loader: providerOverviewLoader, - }, - { - path: "docs/:type/:doc", - element: , - loader: providerDocsLoader, - errorElement: , - }, - ], - }, - ], - }, - ], - }, ], { async unstable_dataStrategy({ request, params, matches }) { 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() {

{title}

{message} - {!!routeError.message && ( + {import.meta.env.DEV && !!routeError.message && (
{routeError.message}
)}
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(); - 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(); - 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(); - 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 0191a6bd..5c37feba 100644 --- a/frontend/src/routes/Provider/query.ts +++ b/frontend/src/routes/Provider/query.ts @@ -1,5 +1,5 @@ import { definitions } from "@/api"; -import { NotFoundPageError } from "@/utils/errors"; +import { api } from "@/query"; import { queryOptions } from "@tanstack/react-query"; export const getProviderVersionDataQuery = ( @@ -10,13 +10,11 @@ export const getProviderVersionDataQuery = ( return queryOptions({ queryKey: ["provider-version", namespace, provider, version], queryFn: async () => { - const response = await fetch( - `${import.meta.env.VITE_DATA_API_URL}/providers/${namespace}/${provider}/${version}/index.json`, - ); + const data = await api( + `providers/${namespace}/${provider}/${version}/index.json`, + ).json(); - const data = await response.json(); - - return data as definitions["ProviderVersion"]; + return data; }, }); }; @@ -32,19 +30,14 @@ export const getProviderDocsQuery = ( return queryOptions({ queryKey: ["provider-doc", namespace, provider, type, name, lang, version], queryFn: async () => { - const urlBase = `${import.meta.env.VITE_DATA_API_URL}/providers/${namespace}/${provider}/${version}`; + 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 fetch(requestURL); - - if (!response.ok) { - throw new NotFoundPageError(); - } - - return response.text(); + const data = await api(requestURL).text(); + return data; }, }); }; @@ -56,13 +49,11 @@ export const getProviderDataQuery = ( return queryOptions({ queryKey: ["provider", namespace, provider], queryFn: async () => { - const response = await fetch( - `${import.meta.env.VITE_DATA_API_URL}/providers/${namespace}/${provider}/index.json`, - ); - - const data = await response.json(); + const data = await api( + `providers/${namespace}/${provider}/index.json`, + ).json(); - return data as definitions["Provider"]; + 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(); - 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) + ); +}