From cd5c35f9288098110b3eb1289ebf74941a227e2d Mon Sep 17 00:00:00 2001
From: Janne Mareike Koschinski <janne@kuschku.de>
Date: Thu, 5 Aug 2021 16:40:25 +0200
Subject: [PATCH] Implement error handling

---
 ui/src/components/ErrorAlert.tsx   | 18 +++++++++++++
 ui/src/components/ErrorContext.tsx | 42 ++++++++++++++++++++++++++++++
 ui/src/components/UploadView.tsx   | 14 +++++++---
 3 files changed, 71 insertions(+), 3 deletions(-)
 create mode 100644 ui/src/components/ErrorAlert.tsx
 create mode 100644 ui/src/components/ErrorContext.tsx

diff --git a/ui/src/components/ErrorAlert.tsx b/ui/src/components/ErrorAlert.tsx
new file mode 100644
index 0000000..8dbcd41
--- /dev/null
+++ b/ui/src/components/ErrorAlert.tsx
@@ -0,0 +1,18 @@
+import {Paper} from "@material-ui/core";
+
+export interface ErrorAlertProps {
+    severity: string,
+    error: unknown,
+}
+
+export function ErrorAlert({severity, error}: ErrorAlertProps) {
+    if (!error) {
+        return null;
+    }
+
+    return (
+        <Paper variant="outlined" color={severity}>
+            <strong>Error</strong>: {"" + error}
+        </Paper>
+    )
+}
diff --git a/ui/src/components/ErrorContext.tsx b/ui/src/components/ErrorContext.tsx
new file mode 100644
index 0000000..988ae5f
--- /dev/null
+++ b/ui/src/components/ErrorContext.tsx
@@ -0,0 +1,42 @@
+import {createContext, FC, FunctionComponent, PropsWithChildren, useCallback, useContext, useState} from "react";
+import {createPortal} from "react-dom";
+
+type ChildrenComponent = FunctionComponent<PropsWithChildren<{}>>;
+
+const ErrorContext = createContext<ChildrenComponent | null>(null);
+
+export function useErrorDisplay(): [FC, ChildrenComponent] {
+    const [ref, setRef] = useState<HTMLDivElement | null>(null);
+    const errorDisplay = useCallback(
+        () => (
+            <div ref={setRef}/>
+        ),
+        []
+    );
+    const errorRenderer: ChildrenComponent = useCallback(
+        ({children}: PropsWithChildren<{}>) => ref && createPortal(
+            children,
+            ref
+        ),
+        [ref]
+    );
+    const errorWrapper = useCallback(
+        ({children}: PropsWithChildren<{}>) => (
+            <ErrorContext.Provider value={errorRenderer}>
+                {children}
+            </ErrorContext.Provider>
+        ),
+        [errorRenderer]
+    );
+
+    return [errorDisplay, errorWrapper];
+}
+
+export const ErrorPortal: ChildrenComponent = (props: PropsWithChildren<{}>) => {
+    const errorRenderer = useContext(ErrorContext);
+    if (!errorRenderer) {
+        return null;
+    }
+
+    return errorRenderer(props);
+}
diff --git a/ui/src/components/UploadView.tsx b/ui/src/components/UploadView.tsx
index eae8360..8c99800 100644
--- a/ui/src/components/UploadView.tsx
+++ b/ui/src/components/UploadView.tsx
@@ -1,14 +1,22 @@
 import {useUploadImage} from "../api/useUploadImage";
+import {ErrorPortal} from "./ErrorContext";
+import {ErrorAlert} from "./ErrorAlert";
+import {LinearProgress} from "@material-ui/core";
 
 export default function UploadView() {
-    const {mutate: upload, error: uploadError, isLoading: uploadLoading} = useUploadImage();
+    const {mutate: upload, error, isLoading} = useUploadImage();
 
     return (
         <div>
-            <pre>Error: {JSON.stringify(uploadError, null, 2)}</pre>
-            <pre>Loading: {JSON.stringify(uploadLoading, null, 2)}</pre>
+            {isLoading && (
+                <LinearProgress/>
+            )}
+            <ErrorPortal>
+                <ErrorAlert severity="error" error={error}/>
+            </ErrorPortal>
             <input
                 type="file"
+                disabled={isLoading}
                 onChange={async ({target}) => {
                     if (target.files) {
                         await upload(target.files)
-- 
GitLab