From ab6bf192021285e51cd1a18fbba6c2d9bf2ec1c8 Mon Sep 17 00:00:00 2001
From: Zoltan Runyo <zoltan.runyo@ess.eu>
Date: Mon, 22 Nov 2021 14:58:52 +0000
Subject: [PATCH] ICSHWI-8272: Refine error handling

---
 src/api/SwaggerApi.js                         | 52 ++++++++++++-------
 src/components/IOC/CreateIOC.js               | 13 +++--
 src/components/IOC/DeployIOC.js               | 12 +++--
 src/components/IOC/IOCCreateDialog.js         | 12 +++--
 src/components/IOC/IOCDelete.js               | 32 +++++++++---
 src/components/IOC/IOCDeployDialog.js         | 17 ++++--
 src/components/IOC/IOCDetailAdmin.js          | 19 +++++--
 src/components/IOC/IOCUndeployDialog.js       |  6 ++-
 src/components/IOC/UndeployIOC.js             | 10 ++--
 src/components/auth/Login.js                  | 11 ++--
 .../navigation/GlobalAppBar/GlobalAppBar.js   | 12 +++--
 11 files changed, 140 insertions(+), 56 deletions(-)

diff --git a/src/api/SwaggerApi.js b/src/api/SwaggerApi.js
index 56ee34bb..9045bd21 100644
--- a/src/api/SwaggerApi.js
+++ b/src/api/SwaggerApi.js
@@ -37,13 +37,20 @@ export const apiContext = createContext(null);
 export const userContext = createContext({ user: null, userRoles:[], getUserRoles: ()=>{}, login: ()=>{}, logout: ()=>{} });
 
 export function UserProvider({ children }) {
+  function onLoginError(message) {
+    setLoginError(message);
+  }  
+
   const [user, setUser] = useState();
+  const [loginError, setLoginError] = useState();
   const [userRoles, getUserRoles, resetUserRoles] = useUserRoles();
   const [userInfo, getUserInfo] = useUserInfo();
-  const [loginResponse, loginFcn] = useLogin();
+  const [loginResponse, loginFcn] = useLogin(onLoginError);
   const logoutFcn = useLogout();
   const history = useHistory();
 
+  const resetLoginError = () => setLoginError(null);
+
   console.log("rendering UserProvider")
   console.log(user);
 
@@ -69,7 +76,7 @@ export function UserProvider({ children }) {
   }
 
   return (
-    <userContext.Provider value={{ user, userRoles, getUserRoles, login, logout }}>
+    <userContext.Provider value={{ user, userRoles, getUserRoles, login, loginError, resetLoginError, logout }}>
       {children}
     </userContext.Provider>
   );
@@ -90,7 +97,7 @@ export function APIProvider({ children }) {
     <CircularProgress />;
 }
 
-export function useAPIErrorHandler() {
+export function useAPIErrorHandler(onError) {
   const {logout} = useContext(userContext);
   const showError = useCustomSnackbar();
 
@@ -100,6 +107,9 @@ export function useAPIErrorHandler() {
     if (status === 401) {
       logout();
     }
+    else if (onError){
+      onError(obj?.description ?? message);
+    }
     else {
       showError(obj?.description ?? message);
     }
@@ -110,7 +120,7 @@ export function useAPIErrorHandler() {
 
 export function useAsync({ fcn, call = true, init = null, onError = null }) {
   const [response, setResponse] = useState(init);
-  const errorHandler = useAPIErrorHandler();
+  const errorHandler = useAPIErrorHandler(onError);
 
   const reset = () => setResponse(init);
 
@@ -136,7 +146,7 @@ function callAndUnpack(fcn, unpacker = x => x) {
   return async (...args) => {
     console.log(...args);
     const response = await fcn(...args);
-    const unpacked = unpacker(response.obj);
+    const unpacked = unpacker(response.obj, response.status);
     return unpacked;
   };
 }
@@ -205,13 +215,13 @@ export function useIOC(id) {
   return useAsync({ fcn: method.bind(null, { iocId: id }) });
 }
 
-export function useCreateIOC() {
+export function useCreateIOC(onError) {
   const api = useContext(apiContext);
   const method = callAndUnpack(
     body => api.apis.IOCs.createIoc({}, { requestBody: body }),
     unpackIOC
   );
-  return useAsync({ fcn: method, call: false });
+  return useAsync({ fcn: method, call: false, onError: onError });
 }
 
 export function unpackDeployment(deployment) {
@@ -303,22 +313,22 @@ export function useCommandJob(awxCommandId) {
   return useAsync({ fcn: method.bind(null, { awxCommandId: awxCommandId }), call: true });
 }
 
-export function useUpdateAndDeployIoc(id) {
+export function useUpdateAndDeployIoc(id, onError) {
   const api = useContext(apiContext);
   const method = callAndUnpack(
     body => api.apis.Deployments.updateAndDeployIoc({ iocId: id }, { requestBody: body }),
     unpackDeployment
   );
-  return useAsync({ fcn: method, call: false });
+  return useAsync({ fcn: method, call: false, onError: onError });
 }
 
-export function useCreateUndeployment(id) {
+export function useCreateUndeployment(id, onError) {
   const api = useContext(apiContext);
   const method = callAndUnpack(
     body => api.apis.Deployments.createUndeployment({iocId: id}, { requestBody: body }),
     unpackDeployment
   );
-  return useAsync({ fcn: method, call: false });
+  return useAsync({ fcn: method, call: false, onError: onError });
 }
 
 export function unpackHost(host) {
@@ -381,7 +391,7 @@ export function unpackLogin(loginResponse) {
   return { ...loginResponse };
 }
 
-export function useLogin() {
+export function useLogin(onError) {
   const api = useContext(apiContext);
   const method = callAndUnpack(
     (username, password) => api.apis.Authentication.login({},{
@@ -392,7 +402,7 @@ export function useLogin() {
     }),
     unpackLogin
   );
-  return useAsync({ fcn: method, call: false });
+  return useAsync({ fcn: method, call: false, onError: onError });
 }
 
 export function unpackUser(user) {
@@ -560,19 +570,25 @@ export function useAllowedGitProjects() {
   return useAsync({ fcn: method, call: false, init: [] });
 }
 
-export function useDeleteIOC(id) {
+export function useDeleteIOC(id, onError) {
   const api = useContext(apiContext);
-  const method = callAndUnpack(api.apis.IOCs.deleteIoc);
-  return useAsync({ fcn: method.bind(null, {iocId: id}), call: false });
+  const method = callAndUnpack(api.apis.IOCs.deleteIoc, unpackDeleteIocResponse);
+  return useAsync({ fcn: method.bind(null, {iocId: id}), call: false, onError: onError });
+}
+
+export function unpackDeleteIocResponse(_, status) {
+  if (status === 204) {
+    return "Successful delete";
+  }
 }
 
-export function useUpdateIoc(id) {
+export function useUpdateIoc(id, onError) {
   const api = useContext(apiContext);
   const method = callAndUnpack(
     body => api.apis.IOCs.updateIoc({ iocId: id }, { requestBody: body }),
     unpackUpdateIoc
   );
-  return useAsync({ fcn: method, call: false });
+  return useAsync({ fcn: method, call: false, onError: onError  });
 }
 
 export function unpackUpdateIoc(updateIoc) {
diff --git a/src/components/IOC/CreateIOC.js b/src/components/IOC/CreateIOC.js
index f24f32f1..fa768b03 100644
--- a/src/components/IOC/CreateIOC.js
+++ b/src/components/IOC/CreateIOC.js
@@ -1,13 +1,18 @@
-import React from "react";
+import React, {useState} from "react";
 import { Redirect } from "react-router-dom";
 import { IOCCreateDialog } from "./IOCCreateDialog";
 
 // Process component
-export function CreateIOC({ open, setOpen, submitCallback, hook, title, buttonText, isUpdateIoc, init={}}) {
-  const [ioc, action] = hook();
+export function CreateIOC({ open, setOpen, submitCallback, hook}) {
+  function onError(message) {
+    setError(message);
+  }  
+  
+  const [ioc, action] = hook(onError);
+  const [error, setError] = useState();
 
   if (!ioc) {
-    return (<IOCCreateDialog open={open} setOpen={setOpen} submitCallback={action} />);
+    return (<IOCCreateDialog open={open} setOpen={setOpen} submitCallback={action} error={error} resetError={() => setError(null)} />);
   }
   else {
     submitCallback(); // This works but throws a warning because I am changing state in the parent while the child is rerendering. Not sure yet how to fix.
diff --git a/src/components/IOC/DeployIOC.js b/src/components/IOC/DeployIOC.js
index 6c9f8196..ba394b72 100644
--- a/src/components/IOC/DeployIOC.js
+++ b/src/components/IOC/DeployIOC.js
@@ -1,15 +1,21 @@
-import React, { useContext} from "react";
+import React, { useContext, useState} from "react";
 import { Redirect } from "react-router-dom";
 import { IOCDeployDialog } from "./IOCDeployDialog";
 import { notificationContext } from "../../components/common/notification/Notifications";
 
 // Process component
 export function DeployIOC({ open, setOpen, submitCallback, hook, hasActiveDeployment, init={}}) {
-  const [deployment, action] = hook();
+  function onError(message) {
+    setError(message);
+  }
+
+  const [deployment, action] = hook(onError);
+  const [error, setError] = useState();
   const {watchDeployment} = useContext(notificationContext);
 
   if (!deployment) {
-    return (<IOCDeployDialog open={open} setOpen={setOpen} submitCallback={action} init={init} hasActiveDeployment={hasActiveDeployment}/>);
+    return (<IOCDeployDialog open={open} setOpen={setOpen} submitCallback={action} init={init} 
+              hasActiveDeployment={hasActiveDeployment} error={error} resetError={() => setError(null)}/>);
   }
   else {
     submitCallback(); // This works but throws a warning because I am changing state in the parent while the child is rerendering. Not sure yet how to fix.
diff --git a/src/components/IOC/IOCCreateDialog.js b/src/components/IOC/IOCCreateDialog.js
index 85ae5c80..e6cfabc5 100644
--- a/src/components/IOC/IOCCreateDialog.js
+++ b/src/components/IOC/IOCCreateDialog.js
@@ -1,6 +1,6 @@
 import React, { useEffect, useState } from "react";
 import { Button, TextField, Dialog, DialogActions, DialogContent, DialogTitle, makeStyles } from "@material-ui/core";
-import { Autocomplete } from "@material-ui/lab";
+import { Autocomplete, Alert } from "@material-ui/lab";
 import { useNamingNames, useAllowedGitProjects } from "../../api/SwaggerApi";
 import { useTypingTimer } from "../common/SearchBoxFilter/TypingTimer";
 
@@ -10,7 +10,7 @@ const useStyles = makeStyles((theme) => ({
   },
 }));
 
-export function IOCCreateDialog({ open, setOpen, submitCallback }) {
+export function IOCCreateDialog({ open, setOpen, submitCallback, error, resetError }) {
   const classes = useStyles();
   const [names, getNames] = useNamingNames();
   const [name, setName] = useState();
@@ -50,7 +50,7 @@ export function IOCCreateDialog({ open, setOpen, submitCallback }) {
             options={names}
             getOptionLabel={(option) => {console.log(option?.name); return option?.name}}
             renderInput={(params) => <TextField {...params} label="IOC name" variant="outlined" required/>}
-            onChange={(event, value, reason) => setName(value)}
+            onChange={(event, value, reason) => {setName(value); resetError();}}
             onInputChange={(event, value, reason) => {event && onNameKeyUp(event.nativeEvent)}}
           />
 
@@ -64,10 +64,14 @@ export function IOCCreateDialog({ open, setOpen, submitCallback }) {
             options={allowedGitProjects}
             defaultValue = { "" }
             getOptionLabel={(option) => {return option}}
-            onChange={(event, value, reason) => setGitUrl(value)}
+            onChange={(event, value, reason) => {setGitUrl(value); resetError();}}
             renderInput={(params) => <TextField {...params} label="Git repository" variant="outlined" fullWidth required />}
           />
 
+          {error ?
+            <Alert severity="error">{error}</Alert>
+          : <></>}
+
         </DialogContent>
         <DialogActions>
           <Button onClick={handleClose} color="primary">Cancel</Button>
diff --git a/src/components/IOC/IOCDelete.js b/src/components/IOC/IOCDelete.js
index 614319ed..7080a317 100644
--- a/src/components/IOC/IOCDelete.js
+++ b/src/components/IOC/IOCDelete.js
@@ -1,12 +1,13 @@
-import React, { useState, useRef } from 'react';
+import React, { useState, useRef, useEffect } from 'react';
 import { useHistory } from "react-router-dom";
 import { makeStyles } from '@material-ui/core/styles';
-import { Button, Typography } from "@material-ui/core";
+import { Button, Typography, Grid } from "@material-ui/core";
 import { theme } from "../../Theme";
 import { useDeleteIOC } from '../../api/SwaggerApi';
 import { SimpleAccordion } from "../common/Accordion/SimpleAccordion";
 import { ConfirmationDialog } from "../dialog/ConfirmationDialog";
 import { SimpleModal } from "../../components/common/SimpleModal/SimpleModal";
+import { Alert } from "@material-ui/lab";
 
 const useStyles = makeStyles({
     deleteButton: {
@@ -28,13 +29,18 @@ export default function IOCDelete({ ioc }) {
 
     const history = useHistory();
 
+    function onError(message) {
+        setError(message);
+    }  
+
     //for the dialog
+    const [error, setError] = useState();
     const [adHocDialogOpen, setAdHocDialogOpen] = useState(false);
     const [adHocDialogTitle, setAdHocDiatlogTitle] = useState();
     const [adHocDialogDescription, setAdHocDialogDescription] = useState();
     const callbackRef = useRef();
 
-    const [, deleteIOC] = useDeleteIOC(ioc.id);
+    const [response, deleteIOC] = useDeleteIOC(ioc.id, onError);
 
     //Setting up dialog properties
     const openDeleteModal = () => {
@@ -50,10 +56,15 @@ export default function IOCDelete({ ioc }) {
 
     //what to do when deleting IOC
     const deleteIoc = async () => {
+        deleteIOC();
+    }
 
-        await deleteIOC();
+    useEffect(() => 
+    {
+      if (response) {
         history.goBack();
-    }
+      }
+    }, [response, history]);
 
 
     return (
@@ -63,7 +74,16 @@ export default function IOCDelete({ ioc }) {
             </SimpleModal>
 
             <SimpleAccordion summary="Delete IOC" defaultExpanded>
-                <Button className={classes.deleteButton} onClick={openDeleteModal}>Delete IOC</Button>
+                <Grid container spacing={1}>
+                    {error ?
+                    <Grid item xs={12}>
+                        <Alert severity="error">{error}</Alert>
+                    </Grid>
+                    : <></>}
+                    <Grid item xs={12}>
+                        <Button className={classes.deleteButton} onClick={openDeleteModal}>Delete IOC</Button>
+                    </Grid>
+                </Grid>
             </SimpleAccordion>
         </>
     );
diff --git a/src/components/IOC/IOCDeployDialog.js b/src/components/IOC/IOCDeployDialog.js
index 9480e205..2c5a7554 100644
--- a/src/components/IOC/IOCDeployDialog.js
+++ b/src/components/IOC/IOCDeployDialog.js
@@ -1,6 +1,6 @@
 import React, { useEffect, useState } from "react";
 import { Button, TextField, Dialog, DialogActions, DialogContent, DialogTitle, Typography, makeStyles } from "@material-ui/core";
-import { Autocomplete } from "@material-ui/lab";
+import { Autocomplete, Alert } from "@material-ui/lab";
 import { useCSEntrySearch, useTagsAndCommitIds } from "../../api/SwaggerApi";
 import { useTypingTimer } from "../common/SearchBoxFilter/TypingTimer";
 
@@ -8,9 +8,12 @@ const useStyles = makeStyles((theme) => ({
   textField: {
     marginBottom: theme.spacing(1),
   },
+  alert: {
+    marginTop: theme.spacing(1),
+  },
 }));
 
-export function IOCDeployDialog({ open, setOpen, submitCallback, hasActiveDeployment, init = {}}) {
+export function IOCDeployDialog({ open, setOpen, submitCallback, hasActiveDeployment, init = {}, error, resetError}) {
   const classes = useStyles();
   const [hosts, getHosts] = useCSEntrySearch();
   const [host, setHost] = useState(null);
@@ -50,7 +53,7 @@ export function IOCDeployDialog({ open, setOpen, submitCallback, hasActiveDeploy
         <DialogContent>
           <TextField autoComplete="off" className={classes.textField} id="comment" label="Deployment comment" variant="outlined" fullWidth/>
           <TextField autoComplete="off" className={classes.textField} id="git" label="Git repository" variant="standard" defaultValue={init.git || ""} fullWidth
-            onChange={(event) => setGitRepo(event.target.value)} disabled required/>
+            onChange={(event) => {setGitRepo(event.target.value); resetError();}} disabled required/>
           
           <Autocomplete
             className={classes.textField}
@@ -60,7 +63,7 @@ export function IOCDeployDialog({ open, setOpen, submitCallback, hasActiveDeploy
             defaultValue = { init.version || "" }
             onFocus={(event) => {getTagOrCommitId(gitRepo);}}
             getOptionLabel={(option) => {return option}}
-            onChange={(event, value, reason) => setGitVersion(value)}
+            onChange={(event, value, reason) => {setGitVersion(value); resetError();}}
             disabled={(!gitRepo) || (gitRepo.trim() === "")}
             renderInput={(params) => <TextField {...params} label="Git reference" variant="outlined" fullWidth required />}
           />
@@ -73,7 +76,7 @@ export function IOCDeployDialog({ open, setOpen, submitCallback, hasActiveDeploy
             defaultValue = { init }
             getOptionLabel={option => {console.log(option); return option?.csEntryHost?.fqdn}}
             renderInput={(params) => <TextField {...params} label="host" variant="outlined" required/>}
-            onChange={(event, value, reason) => setHost(value)}
+            onChange={(event, value, reason) => {setHost(value); resetError();}}
             onInputChange={(event, value, reason) => {event && onHostKeyUp(event.nativeEvent)}}
             disabled={hasActiveDeployment}
           />
@@ -84,6 +87,10 @@ export function IOCDeployDialog({ open, setOpen, submitCallback, hasActiveDeploy
           </Typography> 
           : <></>
           }
+
+          {error ?
+            <Alert severity="error"className={classes.alert}>{error}</Alert>
+          : <></>}
         </DialogContent>
         <DialogActions>
           <Button onClick={handleClose} color="primary">Cancel</Button>
diff --git a/src/components/IOC/IOCDetailAdmin.js b/src/components/IOC/IOCDetailAdmin.js
index 34ed4801..5cb1fc4f 100644
--- a/src/components/IOC/IOCDetailAdmin.js
+++ b/src/components/IOC/IOCDetailAdmin.js
@@ -1,7 +1,7 @@
 import React, { useState, useEffect, useRef } from 'react';
 import { SimpleAccordion } from "../common/Accordion/SimpleAccordion";
 import { Button, makeStyles, TextField, Typography } from "@material-ui/core";
-import { Autocomplete } from "@material-ui/lab";
+import { Autocomplete, Alert } from "@material-ui/lab";
 import { useAllowedGitProjects, useNamingNames, useUpdateIoc } from "../../api/SwaggerApi";
 import { useTypingTimer } from "../common/SearchBoxFilter/TypingTimer";
 import { ConfirmationDialog } from "../dialog/ConfirmationDialog";
@@ -27,9 +27,14 @@ export default function IOCDetailAdmin({ ioc, getIOC, resetTab }) {
     const [adHocDialogOpen, setAdHocDialogOpen] = useState(false);
     const [adHocDialogTitle, setAdHocDiatlogTitle] = useState();
     const [adHocDialogDescription, setAdHocDialogDescription] = useState();
+    const [error, setError] = useState();
     const callbackRef = useRef();
 
-    const [uioc, actionUpdateIoc] = useUpdateIoc(ioc.id);
+    function onError(message) {
+        setError(message);
+    }  
+
+    const [uioc, actionUpdateIoc] = useUpdateIoc(ioc.id, onError);
 
     useEffect(() => {
         getAllowedGitProjects();
@@ -90,7 +95,7 @@ export default function IOCDetailAdmin({ ioc, getIOC, resetTab }) {
                         defaultValue={name}
                         getOptionLabel={(option) => { console.log(option?.name); return option?.name }}
                         renderInput={(params) => <TextField {...params} label="IOC name" variant="outlined" required />}
-                        onChange={(event, value, reason) => setName(value)}
+                        onChange={(event, value, reason) => {setName(value); setError(null);}}
                         onInputChange={(event, value, reason) => { event && onNameKeyUp(event.nativeEvent) }}
                     />
 
@@ -101,10 +106,14 @@ export default function IOCDetailAdmin({ ioc, getIOC, resetTab }) {
                         options={allowedGitProjects}
                         defaultValue={gitUrl}
                         getOptionLabel={(option) => { return option }}
-                        onChange={(event, value, reason) => setGitUrl(value)}
+                        onChange={(event, value, reason) => {setGitUrl(value); setError(null);}}
                         renderInput={(params) => <TextField {...params} label="Git repository" variant="outlined" fullWidth required />} />
 
-                    <TextField id="owner" label="Owner" variant="outlined" fullWidth required value={owner} onChange={(event) => setOwner(event.target.value)} />
+                    <TextField id="owner" className={classes.textField} label="Owner" variant="outlined" fullWidth required value={owner} onChange={(event) => {setOwner(event.target.value); setError(null);}} />
+
+                    {error ?
+                        <Alert severity="error">{error}</Alert>
+                    : <></>}
 
                     <br /><br />
                     <Button color="primary" variant="contained" type="submit">Modify IOC</Button>
diff --git a/src/components/IOC/IOCUndeployDialog.js b/src/components/IOC/IOCUndeployDialog.js
index be0781e2..426c7e7f 100644
--- a/src/components/IOC/IOCUndeployDialog.js
+++ b/src/components/IOC/IOCUndeployDialog.js
@@ -1,5 +1,6 @@
 import React from "react";
 import { Button, TextField, Dialog, DialogActions, DialogContent, DialogTitle, DialogContentText, makeStyles } from "@material-ui/core";
+import { Alert } from "@material-ui/lab";
 
 const useStyles = makeStyles((theme) => ({
   textField: {
@@ -7,7 +8,7 @@ const useStyles = makeStyles((theme) => ({
   },
 }));
 
-export function IOCUndeployDialog({ open, setOpen, submitCallback, ioc }) {
+export function IOCUndeployDialog({ open, setOpen, submitCallback, ioc , error }) {
   const classes = useStyles();
 
   const handleClose = () => {
@@ -32,6 +33,9 @@ export function IOCUndeployDialog({ open, setOpen, submitCallback, ioc }) {
             Do you really want to undeploy {ioc.latestVersion.namingName} from {ioc.activeDeployment.host.host}?
           </DialogContentText>
           <TextField autoComplete="off" className={classes.textField} id="comment" label="Undeployment comment" variant="outlined" fullWidth/>
+          {error ?
+            <Alert severity="error"className={classes.alert}>{error}</Alert>
+          : <></>}
         </DialogContent>
         <DialogActions>
           <Button onClick={handleClose} color="primary">Cancel</Button>
diff --git a/src/components/IOC/UndeployIOC.js b/src/components/IOC/UndeployIOC.js
index e5e9e208..cc05b5a6 100644
--- a/src/components/IOC/UndeployIOC.js
+++ b/src/components/IOC/UndeployIOC.js
@@ -1,15 +1,19 @@
-import React, { useContext} from "react";
+import React, { useContext, useState} from "react";
 import { Redirect } from "react-router-dom";
 import { IOCUndeployDialog } from "./IOCUndeployDialog";
 import { notificationContext } from "../../components/common/notification/Notifications";
 
 // Process component
 export function UndeployIOC({ open, setOpen, submitCallback, hook, ioc }) {
-  const [deployment, action] = hook();
+  function onError(message) {
+    setError(message);
+  }
+  const [deployment, action] = hook(onError);
+  const [error, setError] = useState();
   const {watchDeployment} = useContext(notificationContext);
 
   if (!deployment) {
-    return (<IOCUndeployDialog open={open} setOpen={setOpen} submitCallback={action} ioc={ioc}/>);
+    return (<IOCUndeployDialog open={open} setOpen={setOpen} submitCallback={action} ioc={ioc} error={error}/>);
   }
   else {
     submitCallback(); // This works but throws a warning because I am changing state in the parent while the child is rerendering. Not sure yet how to fix.
diff --git a/src/components/auth/Login.js b/src/components/auth/Login.js
index 690dfe60..a185181b 100644
--- a/src/components/auth/Login.js
+++ b/src/components/auth/Login.js
@@ -8,6 +8,7 @@ import {
   DialogContent, 
   makeStyles
 } from '@material-ui/core';
+import { Alert } from "@material-ui/lab";
 
 const useStyles = makeStyles((theme) => ({
   textField: {
@@ -15,7 +16,7 @@ const useStyles = makeStyles((theme) => ({
   },
 }));
 
-export default function Login({ auth, open, setOpen }) {
+export default function Login({ auth, open, setOpen, error, resetError }) {
   const classes = useStyles();
 
   const handleClose = () => {
@@ -27,7 +28,6 @@ export default function Login({ auth, open, setOpen }) {
     const username = document.getElementById('username').value;
     const password = document.getElementById('password').value;
     await auth(username, password);
-    setOpen(false);
   }
 
   return (
@@ -35,8 +35,11 @@ export default function Login({ auth, open, setOpen }) {
       <form>
         <DialogTitle id="form-dialog-title">Login</DialogTitle>
         <DialogContent>
-          <TextField id="username" className={classes.textField} label="Username" variant="filled" fullWidth autoFocus />
-          <TextField id="password" className={classes.textField} label="Password" variant="filled" type="password" fullWidth />
+          <TextField id="username" className={classes.textField} label="Username" variant="filled" onChange={resetError} fullWidth autoFocus required />
+          <TextField id="password" className={classes.textField} label="Password" variant="filled" type="password" onChange={resetError} fullWidth required />
+          {error ?
+            <Alert severity="error">{error}</Alert>
+          : <></>}
         </DialogContent>
         <DialogActions>
           <Button onClick={handleClose} color="primary">Cancel</Button>
diff --git a/src/components/navigation/GlobalAppBar/GlobalAppBar.js b/src/components/navigation/GlobalAppBar/GlobalAppBar.js
index 5ba11efc..3a23e3e6 100644
--- a/src/components/navigation/GlobalAppBar/GlobalAppBar.js
+++ b/src/components/navigation/GlobalAppBar/GlobalAppBar.js
@@ -178,7 +178,7 @@ export function UserInfo({ user }) {
 
 export function ProfileMenu() {
   const theme = useTheme();
-  const { user, logout, login } = useContext(userContext);
+  const { user, logout, login, loginError, resetLoginError } = useContext(userContext);
   const [anchorEl, setAnchorEl] = useState(null);
   const [loginFormOpen, setLoginFormOpen] = useState(false);
   const history = useHistory();
@@ -195,6 +195,12 @@ export function ProfileMenu() {
     history.push("/home");
   }
 
+  useEffect(() => {
+    if (user) {
+      setLoginFormOpen(false);
+    }
+  }, [user, setLoginFormOpen]);
+
   return (
     <>
     { user ?
@@ -219,9 +225,9 @@ export function ProfileMenu() {
     </>
     :
     <>
-      <Button color="inherit" onClick={() => { setLoginFormOpen(true) }}>Login</Button>
+      <Button color="inherit" onClick={() => { resetLoginError(); setLoginFormOpen(true) }}>Login</Button>
       <SimpleModal open={loginFormOpen} setOpen={setLoginFormOpen}>
-        <Login open={loginFormOpen} setOpen={setLoginFormOpen} auth={login}/>
+        <Login open={loginFormOpen} setOpen={setLoginFormOpen} auth={login} error={loginError} resetError={resetLoginError}/>
       </SimpleModal>
     </>
   }
-- 
GitLab