From c5ead1ddb7ce8e116df14369adb05a6808cbf4b3 Mon Sep 17 00:00:00 2001 From: Jordan Doyle Date: Fri, 17 Sep 2021 01:13:09 +0100 Subject: [PATCH] Unified error handling on the frontend --- chartered-frontend/tsconfig.json | 2 +- chartered-frontend/src/util.tsx | 23 +++++++++++++++++++++++ chartered-frontend/src/pages/ErrorPage.tsx | 11 +++++++++++ chartered-frontend/src/pages/Loading.tsx | 9 +++++++++ chartered-frontend/src/pages/SingleCrate.tsx | 43 +++++++++++++++++++++++++++++++++++++++---- chartered-frontend/src/pages/ssh-keys/ListSshKeys.tsx | 31 ++++++++++++++++++++++++++++--- 6 files changed, 98 insertions(+), 21 deletions(-) diff --git a/chartered-frontend/tsconfig.json b/chartered-frontend/tsconfig.json index 230f5db..0674cc5 100644 --- a/chartered-frontend/tsconfig.json +++ a/chartered-frontend/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { "jsx": "react" }, - "lib": ["es2015"] + "lib": ["ES2015"] } diff --git a/chartered-frontend/src/util.tsx b/chartered-frontend/src/util.tsx index d369f7a..e0fea43 100644 --- a/chartered-frontend/src/util.tsx +++ a/chartered-frontend/src/util.tsx @@ -1,3 +1,4 @@ +import React = require("react"); import { AuthContext } from "./useAuth"; export const BASE_URL = 'http://localhost:8888'; @@ -8,4 +9,26 @@ export function authenticatedEndpoint(auth: AuthContext, endpoint: string): string { return `${BASE_URL}/a/${auth.authKey}/web/v1/${endpoint}`; +} + +export function useAuthenticatedRequest({ auth, endpoint }: { auth: AuthContext, endpoint: string }, reloadOn = []): { response: S | null, error: string | null } { + const [error, setError] = React.useState(null); + const [response, setResponse] = React.useState(null); + + React.useEffect(async () => { + try { + let req = await fetch(authenticatedEndpoint(auth, endpoint)); + let res = await req.json(); + + if (res.error) { + setError(res.error); + } else { + setResponse(res); + } + } catch (e) { + setError(e.message); + } + }, reloadOn); + + return { response, error }; }diff --git a/chartered-frontend/src/pages/ErrorPage.tsx b/chartered-frontend/src/pages/ErrorPage.tsx new file mode 100644 index 0000000..726646c 100644 --- /dev/null +++ a/chartered-frontend/src/pages/ErrorPage.tsx @@ -1,0 +1,11 @@ +import React = require("react"); + +export default function ErrorPage({ message }: { message: string }) { + return ( +
+
+ {message} +
+
+ ); +}diff --git a/chartered-frontend/src/pages/Loading.tsx b/chartered-frontend/src/pages/Loading.tsx new file mode 100644 index 0000000..87793bf 100644 --- /dev/null +++ a/chartered-frontend/src/pages/Loading.tsx @@ -1,0 +1,9 @@ +import React = require("react"); + +export default function Loading() { + return
+
+ Loading... +
+
; +}diff --git a/chartered-frontend/src/pages/SingleCrate.tsx b/chartered-frontend/src/pages/SingleCrate.tsx index b68829a..81b3078 100644 --- a/chartered-frontend/src/pages/SingleCrate.tsx +++ a/chartered-frontend/src/pages/SingleCrate.tsx @@ -1,13 +1,14 @@ import React = require('react'); import { useState, useEffect } from 'react'; -import { Link } from "react-router-dom"; import { useAuth } from '../useAuth'; import Nav from "../sections/Nav"; +import Loading from './Loading'; +import ErrorPage from './ErrorPage'; import { Box, HouseDoor, Book, Building, PersonPlus } from 'react-bootstrap-icons'; import { useParams } from "react-router-dom"; -import { authenticatedEndpoint } from '../util'; +import { authenticatedEndpoint, useAuthenticatedRequest } from '../util'; import Prism from 'react-syntax-highlighter/dist/cjs/prism'; import ReactMarkdown from 'react-markdown'; @@ -15,21 +16,39 @@ type Tab = 'readme' | 'versions' | 'members'; +interface CrateInfo { + versions: CrateInfoVersion[], +} + +interface CrateInfoVersion { + vers: string, + homepage: string | null, + description: string | null, + documentation: string | null, + repository: string | null, + deps: CrateInfoVersionDependency[], +} + +interface CrateInfoVersionDependency { + name: string, + version_req: string, +} + export default function SingleCrate() { const auth = useAuth(); const { crate } = useParams(); - const [crateInfo, setCrateInfo] = useState(null); const [currentTab, setCurrentTab] = useState('readme'); - useEffect(async () => { - let res = await fetch(authenticatedEndpoint(auth, `crates/${crate}`)); - let json = await res.json(); - setCrateInfo(json); - }, []); - - if (!crateInfo) { - return (
Loading...
); + const { response: crateInfo, error } = useAuthenticatedRequest({ + auth, + endpoint: `crates/${crate}`, + }); + + if (error) { + return ; + } else if (!crateInfo) { + return ; } const crateVersion = crateInfo.versions[crateInfo.versions.length - 1]; @@ -156,7 +175,7 @@ ); } -function Members(props: { crateInfo: any }) { +function Members(props: { crateInfo: CrateInfoVersion }) { const x = ["John Paul", "David Davidson", "Andrew Smith"]; return
diff --git a/chartered-frontend/src/pages/ssh-keys/ListSshKeys.tsx b/chartered-frontend/src/pages/ssh-keys/ListSshKeys.tsx index e8a5a15..7a410a4 100644 --- a/chartered-frontend/src/pages/ssh-keys/ListSshKeys.tsx +++ a/chartered-frontend/src/pages/ssh-keys/ListSshKeys.tsx @@ -1,30 +1,45 @@ import React = require("react"); import { useState, useEffect } from 'react'; import { Link } from 'react-router-dom'; import Nav from "../../sections/Nav"; import { useAuth } from "../../useAuth"; -import { authenticatedEndpoint } from "../../util"; +import { useAuthenticatedRequest, authenticatedEndpoint } from "../../util"; import { Plus, Trash } from "react-bootstrap-icons"; import { Button, Modal } from "react-bootstrap"; import HumanTime from "react-human-time"; +import ErrorPage from "../ErrorPage"; +import Loading from "../Loading"; +interface SshKeysResponse { + keys: SshKeysResponseKey[], +} + +interface SshKeysResponseKey { + id: number, + name: string, + fingerprint: string, + created_at: string, + last_used_at: string, +} + export default function ListSshKeys() { const auth = useAuth(); const [error, setError] = useState(""); const [deleting, setDeleting] = useState(null); - const [sshKeys, setSshKeys] = useState(null); const [reloadSshKeys, setReloadSshKeys] = useState(0); - useEffect(async () => { - let res = await fetch(authenticatedEndpoint(auth, 'ssh-key')); - let json = await res.json(); - setSshKeys(json); + + const { response: sshKeys, error: loadError } = useAuthenticatedRequest({ + auth, + endpoint: 'ssh-key', }, [reloadSshKeys]); - if (!sshKeys) { - return (
loading...
); + if (loadError) { + return ; + } else if (!sshKeys) { + return ; } const deleteKey = async () => { -- rgit 0.1.3