🏡 index : ~doyle/chartered.git

author Jordan Doyle <jordan@doyle.la> 2021-09-17 1:13:09.0 +01:00:00
committer Jordan Doyle <jordan@doyle.la> 2021-09-17 1:13:09.0 +01:00:00
commit
c5ead1ddb7ce8e116df14369adb05a6808cbf4b3 [patch]
tree
17decba9def8583f04cc3ba2a77074c6eafa62db
parent
25249d00e5e0d3790782ce4d7e2e9dd38d3f1939
download
c5ead1ddb7ce8e116df14369adb05a6808cbf4b3.tar.gz

Unified error handling on the frontend



Diff

 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<S>({ 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 (
        <div className="bg-primary min-vh-100 d-flex justify-content-center align-items-center">
            <div className="alert alert-danger" role="alert">
                {message}
            </div>
        </div>
    );
}
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 <div className="min-vh-100 bg-primary d-flex justify-content-center align-items-center">
        <div className="spinner-border text-light" role="status">
            <span className="visually-hidden">Loading...</span>
        </div>
    </div>;
}
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<Tab>('readme');

    useEffect(async () => {
        let res = await fetch(authenticatedEndpoint(auth, `crates/${crate}`));
        let json = await res.json();
        setCrateInfo(json);
    }, []);

    if (!crateInfo) {
        return (<div>Loading...</div>);
    const { response: crateInfo, error } = useAuthenticatedRequest<CrateInfo>({
        auth,
        endpoint: `crates/${crate}`,
    });

    if (error) {
        return <ErrorPage message={error} />;
    } else if (!crateInfo) {
        return <Loading />;
    }

    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 <div className="container-fluid g-0">
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<SshKeysResponse>({
        auth,
        endpoint: 'ssh-key',
    }, [reloadSshKeys]);

    if (!sshKeys) {
        return (<div>loading...</div>);
    if (loadError) {
        return <ErrorPage message={loadError} />;
    } else if (!sshKeys) {
        return <Loading />;
    }

    const deleteKey = async () => {