From 4f42fa52f0744bd66740709fb85140888cb138ee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?V=C3=A1clav=20Honz=C3=ADk?= <honzikv@students.zcu.cz>
Date: Mon, 2 May 2022 13:00:50 +0200
Subject: [PATCH 1/2] Login dialog + slice for notifications

re #9628
---
 frontend/src/App.tsx                          |  38 ++--
 frontend/src/features/Auth/LoginDialog.tsx    | 184 ++++++++++++++++++
 .../features/Notification/Notification.tsx    |  55 ++++++
 .../Notification/notificationSlice.ts         |  36 ++++
 .../features/TrackingTool/TrackingTool.tsx    |  11 +-
 frontend/src/features/redux/store.ts          |   2 +
 6 files changed, 304 insertions(+), 22 deletions(-)
 create mode 100644 frontend/src/features/Auth/LoginDialog.tsx
 create mode 100644 frontend/src/features/Notification/Notification.tsx
 create mode 100644 frontend/src/features/Notification/notificationSlice.ts

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
-- 
GitLab


From 6129910f243ca34f79bd580afca37bb7e36678f9 Mon Sep 17 00:00:00 2001
From: Vaclav Honzik <vaclavhonzik98@gmail.com>
Date: Thu, 5 May 2022 12:00:10 +0200
Subject: [PATCH 2/2] Show dialog in login page

re #9628
---
 frontend/src/features/Auth/Login.tsx          |  86 +-----------
 frontend/src/features/Auth/LoginDialog.tsx    | 131 +++++-------------
 frontend/src/features/Auth/RegisterDialog.tsx |   6 +
 frontend/src/features/Auth/userSlice.ts       |  17 ++-
 frontend/src/features/Auth/userThunks.ts      |   1 +
 .../Navigation/navigationMenuItems.ts         |   4 +-
 frontend/src/features/Theme/ThemeChanger.tsx  |   1 -
 7 files changed, 63 insertions(+), 183 deletions(-)
 create mode 100644 frontend/src/features/Auth/RegisterDialog.tsx
 delete mode 100644 frontend/src/features/Theme/ThemeChanger.tsx

diff --git a/frontend/src/features/Auth/Login.tsx b/frontend/src/features/Auth/Login.tsx
index b4ece4b..9b90ef7 100644
--- a/frontend/src/features/Auth/Login.tsx
+++ b/frontend/src/features/Auth/Login.tsx
@@ -1,45 +1,16 @@
-import { Button, TextField, Typography } from '@mui/material'
-import { useFormik } from 'formik'
 import { Fragment, useEffect } from 'react'
-import { useDispatch, useSelector } from 'react-redux'
+import { useSelector } from 'react-redux'
 import { useNavigate } from 'react-router-dom'
-import * as yup from 'yup'
-import { SchemaOf } from 'yup'
 import { RootState } from '../redux/store'
-import { logIn } from './userThunks'
+import LoginDialog from './LoginDialog'
 
-interface LoginFields {
-    username: string
-    password: string
-}
 
 const Login = () => {
-    const validationSchema: SchemaOf<LoginFields> = yup.object().shape({
-        username: yup.string().required('Username is required'),
-        password: yup.string().required('Password is required'),
-    })
-
-    const dispatch = useDispatch()
-    const formik = useFormik({
-        initialValues: {
-            username: '',
-            password: '',
-        },
-        validationSchema,
-        onSubmit: () => {
-            dispatch(
-                logIn({
-                    username: formik.values.username,
-                    password: formik.values.password,
-                })
-            )
-        },
-    })
-
-    // Redirect to home if the user is logged in
     const userLoggedIn = useSelector(
         (state: RootState) => state.user.isLoggedIn
     )
+
+    // Redirect to home if the user is logged in
     const navigate = useNavigate()
     useEffect(() => {
         if (userLoggedIn) {
@@ -49,54 +20,7 @@ const Login = () => {
 
     return (
         <Fragment>
-            <Typography variant="h3">Login</Typography>
-
-            <form onSubmit={formik.handleSubmit}>
-                <TextField
-                    label="Username"
-                    name="username"
-                    fullWidth
-                    sx={{ mb: 2 }}
-                    value={formik.values.username}
-                    onChange={formik.handleChange}
-                    error={
-                        Boolean(formik.errors.username) &&
-                        formik.touched.username
-                    }
-                    helperText={
-                        formik.errors.username &&
-                        formik.touched.username &&
-                        formik.errors.username
-                    }
-                />
-                <TextField
-                    type="password"
-                    label="Password"
-                    name="password"
-                    fullWidth
-                    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
-                    }
-                    sx={{ mb: 2 }}
-                />
-                <Button
-                    size="large"
-                    variant="contained"
-                    color="primary"
-                    type="submit"
-                    fullWidth
-                >
-                    Login
-                </Button>
-            </form>
+            <LoginDialog />
         </Fragment>
     )
 }
diff --git a/frontend/src/features/Auth/LoginDialog.tsx b/frontend/src/features/Auth/LoginDialog.tsx
index a6e759d..fb4dd11 100644
--- a/frontend/src/features/Auth/LoginDialog.tsx
+++ b/frontend/src/features/Auth/LoginDialog.tsx
@@ -1,19 +1,19 @@
-import { Fragment, FunctionComponent, useState } from 'react'
+import { Fragment, FunctionComponent, useEffect, 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'
+import { useDispatch, useSelector } from 'react-redux'
+import { Link as RouterLink, useNavigate } from 'react-router-dom'
+import { logIn } from './userThunks'
+import { RootState } from '../redux/store'
+import { resetLoggingIn } from './userSlice'
 
 export interface CreateIndexDialogProps {
     maxWidth?: DialogProps['maxWidth']
@@ -22,105 +22,48 @@ export interface CreateIndexDialogProps {
 const RegisterDialog: FunctionComponent<CreateIndexDialogProps> = ({
     maxWidth,
 }) => {
-    const [open, setOpen] = useState(false)
-    const [submitButtonEnabled, setSubmitButtonEnabled] = useState(true)
+    const [open, setOpen] = useState(true)
 
     const dispatch = useDispatch()
-
-    const hideDialog = () => {
-        setOpen(false)
-    }
-
-    const showDialog = () => {
-        setOpen(true)
-    }
-
+    const navigate = useNavigate()
+    dispatch(resetLoggingIn())
     const validationSchema = yup.object().shape({
-        email: yup.string().email().required('Email is required'),
+        username: yup.string().required('Username is required'),
         password: yup.string().required('Password is required'),
     })
 
+    const isLoggingIn = useSelector(
+        (state: RootState) => state.user.isLoggingIn
+    )
+
     const formik = useFormik({
         initialValues: {
-            name: '',
+            username: '',
             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)
+        onSubmit: () => {
+            dispatch(
+                logIn({
+                    username: formik.values.username,
+                    password: formik.values.password,
+                })
+            )
         },
     })
 
-    // Method called on closing the dialog
-    const onClose = () => {
-        hideDialog()
+    const onCancel = () => {
         formik.resetForm()
+        navigate('/')
     }
 
     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'}
+                onClose={onCancel}
+                maxWidth="md"
             >
                 <Typography sx={{ ml: 2, mt: 2 }} variant="h5" fontWeight="600">
                     Login
@@ -130,18 +73,18 @@ const RegisterDialog: FunctionComponent<CreateIndexDialogProps> = ({
                         <TextField
                             fullWidth
                             label="Name"
-                            name="name"
+                            name="username"
                             sx={{ mb: 2 }}
-                            value={formik.values.name}
+                            value={formik.values.username}
                             onChange={formik.handleChange}
                             error={
-                                Boolean(formik.errors.name) &&
-                                formik.touched.name
+                                Boolean(formik.errors.username) &&
+                                formik.touched.username
                             }
                             helperText={
-                                formik.errors.name &&
-                                formik.touched.name &&
-                                formik.errors.name
+                                formik.errors.username &&
+                                formik.touched.username &&
+                                formik.errors.username
                             }
                         />
                         <TextField
@@ -166,15 +109,17 @@ const RegisterDialog: FunctionComponent<CreateIndexDialogProps> = ({
                             <Button
                                 type="submit"
                                 variant="contained"
-                                disabled={!submitButtonEnabled}
                                 fullWidth
+                                disabled={isLoggingIn}
                             >
                                 Log in
                             </Button>
                         </Fragment>
-                    </form> 
+                    </form>
 
-                    <Link component={RouterLink} to="/resetPassword">Forgot password?</Link>
+                    <Link component={RouterLink} to="/resetPassword">
+                        Forgot password?
+                    </Link>
                 </DialogContent>
             </Dialog>
         </Fragment>
diff --git a/frontend/src/features/Auth/RegisterDialog.tsx b/frontend/src/features/Auth/RegisterDialog.tsx
new file mode 100644
index 0000000..8833c33
--- /dev/null
+++ b/frontend/src/features/Auth/RegisterDialog.tsx
@@ -0,0 +1,6 @@
+import { useFormik } from "formik"
+
+const register = () => {
+
+    return <></>
+}
diff --git a/frontend/src/features/Auth/userSlice.ts b/frontend/src/features/Auth/userSlice.ts
index bd85f6c..8b6b395 100644
--- a/frontend/src/features/Auth/userSlice.ts
+++ b/frontend/src/features/Auth/userSlice.ts
@@ -8,6 +8,7 @@ export interface UserState {
     refreshToken?: string
     username: string
     roles: string[]
+    isLoggingIn: boolean
     isLoggedIn: boolean
     lastErr?: string // consumable for errors during thunks
 }
@@ -21,6 +22,7 @@ const persistConfig = {
 const initialState: UserState = {
     roles: [],
     isLoggedIn: false,
+    isLoggingIn: false,
     username: '',
 }
 
@@ -41,27 +43,28 @@ export const userSlice = createSlice({
             ...state,
             lastErr: action.payload,
         }),
-        setUserState: (state, action) => {
-            return ({ ...state, ...action.payload })
-        },
+        setUserState: (state, action) => ({ ...state, ...action.payload }),
+        resetLoggingIn: (state) => ({ ...state, isLoggingIn: false }),
     },
 
     // Thunks
     extraReducers: (builder) => {
         builder.addCase(logIn.fulfilled, (state, action) => {
-            return ({ ...state, ...action.payload })
+            return { ...state, ...action.payload }
         })
         builder.addCase(logIn.rejected, (state, action) => {
             if (action.payload && typeof action.error.message === 'string') {
-                return ({ ...state, lastErr: action.error.message })
+                return { ...state, lastErr: action.error.message }
             }
         })
+        builder.addCase(logIn.pending, (state, action) => {
+            return { ...state, isLoggingIn: true }
+        })
     },
 })
 
-
 const userReducer = persistReducer(persistConfig, userSlice.reducer)
 
-export const { logout, refreshTokens, setErr, setUserState } = userSlice.actions
+export const { logout, refreshTokens, setErr, setUserState, resetLoggingIn } = userSlice.actions
 
 export default userReducer
diff --git a/frontend/src/features/Auth/userThunks.ts b/frontend/src/features/Auth/userThunks.ts
index a7a16d7..6bbf7d3 100644
--- a/frontend/src/features/Auth/userThunks.ts
+++ b/frontend/src/features/Auth/userThunks.ts
@@ -40,6 +40,7 @@ export const logIn = createAsyncThunk(
                 refreshToken,
                 username: sub,
                 roles: authorities,
+                isLoggingIn: false,
                 isLoggedIn: true
             }
             
diff --git a/frontend/src/features/Navigation/navigationMenuItems.ts b/frontend/src/features/Navigation/navigationMenuItems.ts
index 8a0bf34..6b1aef2 100644
--- a/frontend/src/features/Navigation/navigationMenuItems.ts
+++ b/frontend/src/features/Navigation/navigationMenuItems.ts
@@ -15,7 +15,8 @@ export interface NavigationMenuItem {
     // All privileges that can access this menu item
     accessibleTo: Set<string>
     icon: OverridableComponent<SvgIconTypeMap<{}, 'svg'>>
-    position: number
+    position: number,
+    isDialog?: boolean
 }
 
 const visitorRole = 'VISITOR'
@@ -69,6 +70,7 @@ const items: NavigationMenuItem[] = [
         accessibleTo: new Set([visitorRoleOnly]),
         icon: LoginIcon,
         position: 5,
+        isDialog: true
     },
     {
         name: 'Statistics',
diff --git a/frontend/src/features/Theme/ThemeChanger.tsx b/frontend/src/features/Theme/ThemeChanger.tsx
deleted file mode 100644
index 56004c9..0000000
--- a/frontend/src/features/Theme/ThemeChanger.tsx
+++ /dev/null
@@ -1 +0,0 @@
-export default {}
\ No newline at end of file
-- 
GitLab