commit 4f42fa52f0744bd66740709fb85140888cb138ee
Author: Václav Honzík <honzikv@students.zcu.cz>
Date:   Mon May 2 13:00:50 2022 +0200

    Login dialog + slice for notifications
    
    re #9628

diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 322c683..e4adcda 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -13,27 +13,31 @@ import Navigation from './features/Navigation/Navigation'
 import TrackingTool from './features/TrackingTool/TrackingTool'
 import Logout from './features/Auth/Logout'
 import ThemeWrapper from './features/Theme/ThemeWrapper'
+import Notification from './features/Notification/Notification'
+import { Fragment } from 'react'
 
 const App = () => {
-
     return (
         <ThemeWrapper>
-            <Navigation>
-                <Box sx={{mx: 10}}>
-                    <Routes>
-                        <Route path="/" element={<Home />} />
-                        <Route path="/catalog" element={<Catalog />} />
-                        <Route
-                            path="/catalog/:itemId"
-                            element={<CatalogItemDetail />}
-                        />
-                        <Route path="/login" element={<Login />} />
-                        <Route path="/logout" element={<Logout />} />
-                        <Route path="/map" element={<TrackingTool />} />
-                        <Route path="*" element={<NotFound />} />
-                    </Routes>
-                </Box>
-            </Navigation>
+            <Fragment>
+                <Notification />
+                <Navigation>
+                    <Box sx={{ mx: 10 }}>
+                        <Routes>
+                            <Route path="/" element={<Home />} />
+                            <Route path="/catalog" element={<Catalog />} />
+                            <Route
+                                path="/catalog/:itemId"
+                                element={<CatalogItemDetail />}
+                            />
+                            <Route path="/login" element={<Login />} />
+                            <Route path="/logout" element={<Logout />} />
+                            <Route path="/map" element={<TrackingTool />} />
+                            <Route path="*" element={<NotFound />} />
+                        </Routes>
+                    </Box>
+                </Navigation>
+            </Fragment>
         </ThemeWrapper>
     )
 }
diff --git a/frontend/src/features/Auth/LoginDialog.tsx b/frontend/src/features/Auth/LoginDialog.tsx
new file mode 100644
index 0000000..a6e759d
--- /dev/null
+++ b/frontend/src/features/Auth/LoginDialog.tsx
@@ -0,0 +1,184 @@
+import { Fragment, FunctionComponent, useState } from 'react'
+import Dialog, { DialogProps } from '@mui/material/Dialog'
+import {
+    Button,
+    DialogContent,
+    Link,
+    Stack,
+    TextField,
+    Typography,
+} from '@mui/material'
+import { useFormik } from 'formik'
+import * as yup from 'yup'
+import { useDispatch } from 'react-redux'
+import { showNotification } from '../Notification/notificationSlice'
+import axiosInstance from '../../api/api'
+import { Link as RouterLink } from 'react-router-dom'
+
+export interface CreateIndexDialogProps {
+    maxWidth?: DialogProps['maxWidth']
+}
+
+const RegisterDialog: FunctionComponent<CreateIndexDialogProps> = ({
+    maxWidth,
+}) => {
+    const [open, setOpen] = useState(false)
+    const [submitButtonEnabled, setSubmitButtonEnabled] = useState(true)
+
+    const dispatch = useDispatch()
+
+    const hideDialog = () => {
+        setOpen(false)
+    }
+
+    const showDialog = () => {
+        setOpen(true)
+    }
+
+    const validationSchema = yup.object().shape({
+        email: yup.string().email().required('Email is required'),
+        password: yup.string().required('Password is required'),
+    })
+
+    const formik = useFormik({
+        initialValues: {
+            name: '',
+            password: '',
+        },
+        validationSchema,
+        onSubmit: async (values) => {
+            setSubmitButtonEnabled(false)
+            let userRegistered = false
+            try {
+                const { status } = await axiosInstance.post(
+                    `/users/${values.name}`,
+                    values
+                )
+
+                switch (status) {
+                    case 200:
+                        dispatch({
+                            message: 'User was created successfully',
+                            severity: 'success',
+                        })
+                        userRegistered = true
+                        break
+                    case 204:
+                        dispatch(
+                            showNotification({
+                                message: 'User already exists',
+                                severity: 'error',
+                            })
+                        )
+                        break
+                    default:
+                        dispatch({
+                            message:
+                                'Unknown error ocurred, the user was not registered. Please try again later',
+                            severity: 'error',
+                        })
+                }
+            } catch (err: any) {
+                dispatch(
+                    showNotification({
+                        message: 'The user could not be registered 😥',
+                        severity: 'error',
+                    })
+                )
+            }
+
+            if (userRegistered) {
+                onClose()
+            }
+
+            // Always fetch new indices
+            // TODO actually fetch the users
+            // dispatch(fetchUsers())
+            setSubmitButtonEnabled(true)
+        },
+    })
+
+    // Method called on closing the dialog
+    const onClose = () => {
+        hideDialog()
+        formik.resetForm()
+    }
+
+    return (
+        <Fragment>
+            <Stack
+                direction="row"
+                justifyContent="flex-end"
+                alignItems="center"
+            >
+                <Button variant="outlined" color="primary" onClick={showDialog}>
+                    Create new Index
+                </Button>
+            </Stack>
+
+            <Dialog
+                open={open}
+                fullWidth={true}
+                onClose={onClose}
+                maxWidth={maxWidth || 'lg'}
+            >
+                <Typography sx={{ ml: 2, mt: 2 }} variant="h5" fontWeight="600">
+                    Login
+                </Typography>
+                <DialogContent>
+                    <form onSubmit={formik.handleSubmit}>
+                        <TextField
+                            fullWidth
+                            label="Name"
+                            name="name"
+                            sx={{ mb: 2 }}
+                            value={formik.values.name}
+                            onChange={formik.handleChange}
+                            error={
+                                Boolean(formik.errors.name) &&
+                                formik.touched.name
+                            }
+                            helperText={
+                                formik.errors.name &&
+                                formik.touched.name &&
+                                formik.errors.name
+                            }
+                        />
+                        <TextField
+                            fullWidth
+                            label="Password"
+                            name="password"
+                            type="password"
+                            sx={{ mb: 2 }}
+                            value={formik.values.password}
+                            onChange={formik.handleChange}
+                            error={
+                                Boolean(formik.errors.password) &&
+                                formik.touched.password
+                            }
+                            helperText={
+                                formik.errors.password &&
+                                formik.touched.password &&
+                                formik.errors.password
+                            }
+                        />
+                        <Fragment>
+                            <Button
+                                type="submit"
+                                variant="contained"
+                                disabled={!submitButtonEnabled}
+                                fullWidth
+                            >
+                                Log in
+                            </Button>
+                        </Fragment>
+                    </form> 
+
+                    <Link component={RouterLink} to="/resetPassword">Forgot password?</Link>
+                </DialogContent>
+            </Dialog>
+        </Fragment>
+    )
+}
+
+export default RegisterDialog
diff --git a/frontend/src/features/Notification/Notification.tsx b/frontend/src/features/Notification/Notification.tsx
new file mode 100644
index 0000000..b9be95b
--- /dev/null
+++ b/frontend/src/features/Notification/Notification.tsx
@@ -0,0 +1,55 @@
+import { Alert, AlertColor, Snackbar } from '@mui/material'
+import { Fragment, useEffect, useState } from 'react'
+import { useDispatch, useSelector } from 'react-redux'
+import { RootState } from '../redux/store'
+import { consumeNotification } from './notificationSlice'
+
+// Represents notification component that will be displayed on the screen
+const Notification = () => {
+    const dispatch = useDispatch()
+    const notification = useSelector((state: RootState) => state.notification)
+
+    const [displayMessage, setDisplayMessage] = useState('')
+    const [open, setOpen] = useState(false)
+    const [severity, setSeverity] = useState<AlertColor>('info')
+    const [autohideDuration, setAutohideDuration] = useState<number | null>(
+        null
+    )
+
+    const closeNotification = () => {
+        setOpen(false)
+        setAutohideDuration(null)
+    }
+
+    // Set the message to be displayed if something is set
+    useEffect(() => {
+        if (notification.message) {
+            setDisplayMessage(notification.message)
+            setSeverity(notification.severity as AlertColor)
+            if (notification.autohideSecs) {
+                setAutohideDuration(notification.autohideSecs * 1000)
+            }
+            // Consume the message from store
+            dispatch(consumeNotification())
+
+            // Show the message in the notification
+            setOpen(true)
+        }
+    }, [notification, dispatch])
+
+    return (
+        <Fragment>
+            <Snackbar
+                open={open}
+                autoHideDuration={autohideDuration}
+                anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
+            >
+                <Alert severity={severity} onClose={closeNotification}>
+                    {displayMessage}
+                </Alert>
+            </Snackbar>
+        </Fragment>
+    )
+}
+
+export default Notification
diff --git a/frontend/src/features/Notification/notificationSlice.ts b/frontend/src/features/Notification/notificationSlice.ts
new file mode 100644
index 0000000..976c8ec
--- /dev/null
+++ b/frontend/src/features/Notification/notificationSlice.ts
@@ -0,0 +1,36 @@
+import { AlertColor } from '@mui/material'
+import { createSlice } from '@reduxjs/toolkit'
+
+export interface NotificationState {
+    message?: string
+    severity: AlertColor
+    autohideSecs?: number
+}
+
+const initialState = {
+    message: undefined,
+    severity: 'info',
+    autohideSecs: undefined
+}
+
+const notificationSlice = createSlice({
+    name: 'notification',
+    initialState,
+    reducers: {
+        showNotification: (state, action) => ({
+            ...state,
+            message: action.payload.message,
+            severity: action.payload.severity,
+            autohideSecs: action.payload.autohideSecs,
+        }),
+        // consumes the message so it is not displayed after the page gets refreshed
+        consumeNotification: (state) => ({
+            ...initialState,
+        }),
+    },
+})
+
+const notificationReducer = notificationSlice.reducer
+export const { showNotification, consumeNotification } =
+    notificationSlice.actions
+export default notificationReducer
diff --git a/frontend/src/features/TrackingTool/TrackingTool.tsx b/frontend/src/features/TrackingTool/TrackingTool.tsx
index bf2c1b3..35461b8 100644
--- a/frontend/src/features/TrackingTool/TrackingTool.tsx
+++ b/frontend/src/features/TrackingTool/TrackingTool.tsx
@@ -6,6 +6,8 @@ import mapConfig from '../../config/mapConfig'
 import TextPath from 'react-leaflet-textpath'
 import PlaintextUpload from './PlaintextUpload'
 import FileUpload from './FileUpload'
+import L from 'leaflet'
+import DeleteIcon from '@mui/icons-material/Delete'
 
 // Page with tracking tool
 const TrackingTool = () => {
@@ -64,9 +66,7 @@ const TrackingTool = () => {
                     repeat
                     center
                     weight={10}
-                >
-                    <Popup>Caesar 🥗 War Path (Allegedly)</Popup>
-                </TextPath>
+                ></TextPath>
             )
         }
 
@@ -123,12 +123,13 @@ const TrackingTool = () => {
                             url={mapConfig.url}
                         />
                         {coords.map(({ latitude, longitude }, idx) => (
-                            <Marker position={[latitude, longitude]} />
+                            <Marker
+                                position={[latitude, longitude]}
+                            />
                         ))}
                         {polylines}
                     </MapContainer>
                 </Grid>
-                
             </Grid>
         </Fragment>
     )
diff --git a/frontend/src/features/redux/store.ts b/frontend/src/features/redux/store.ts
index e5bc471..e3297b6 100644
--- a/frontend/src/features/redux/store.ts
+++ b/frontend/src/features/redux/store.ts
@@ -5,6 +5,7 @@ import userReducer from '../Auth/userSlice'
 import themeReducer from '../Theme/themeSlice'
 import catalogReducer from '../Catalog/catalogSlice'
 import { composeWithDevTools } from 'redux-devtools-extension'
+import notificationReducer from '../Notification/notificationSlice'
 
 const composeEnhancers = composeWithDevTools({})
 
@@ -14,6 +15,7 @@ const store = createStore(
         user: userReducer,
         theme: themeReducer,
         catalog: catalogReducer,
+        notification: notificationReducer
     }),
     process.env.REACT_APP_DEV_ENV === 'true'
         ? composeEnhancers( // ComposeEnhancers will inject redux-devtools-extension
