From c29e4a116d56e239af5ef3237e886d0e01b4691d Mon Sep 17 00:00:00 2001
From: Albin Henriksson <albhe428@student.liu.se>
Date: Fri, 2 Apr 2021 15:46:52 +0000
Subject: [PATCH] Resolve "Implement create competition"

---
 client/src/App.test.tsx                       |  12 +-
 client/src/App.tsx                            |   7 +-
 client/src/__mocks__/axios.js                 |   3 +
 client/src/actions/cities.ts                  |  15 ++
 client/src/actions/competitions.ts            |  39 ++++
 client/src/actions/types.ts                   |   5 +
 client/src/actions/user.ts                    |  23 +-
 client/src/hooks.ts                           |   5 +
 client/src/interfaces/AdminLoginData.ts       |   4 +
 client/src/interfaces/City.ts                 |   4 +
 client/src/interfaces/Competition.ts          |   7 +
 .../src/interfaces/CompetitionFilterParams.ts |   8 +
 client/src/interfaces/models.ts               |   6 +
 client/src/pages/admin/AdminPage.tsx          |  31 +--
 .../pages/admin/components/AddCompetition.tsx | 182 +++++++++++++++
 .../components/CompetitionManager.test.tsx    |  46 +++-
 .../admin/components/CompetitionManager.tsx   | 213 ++++++++----------
 client/src/pages/admin/components/styled.tsx  |  23 +-
 client/src/pages/admin/styled.tsx             |  13 ++
 .../src/pages/login/components/AdminLogin.tsx |  31 +--
 .../login/components/CompetitionLogin.tsx     |   1 -
 client/src/reducers/allReducers.ts            |   4 +
 client/src/reducers/citiesReducer.ts          |  14 ++
 client/src/reducers/competitionsReducer.ts    |  45 ++++
 client/src/reducers/uiReducer.ts              |  17 +-
 client/src/reducers/userReducer.ts            |  24 +-
 client/src/store.ts                           |   7 +-
 client/src/utils/SecureRoute.tsx              |  22 +-
 client/src/utils/checkAuthentication.ts       |  17 +-
 29 files changed, 627 insertions(+), 201 deletions(-)
 create mode 100644 client/src/__mocks__/axios.js
 create mode 100644 client/src/actions/cities.ts
 create mode 100644 client/src/actions/competitions.ts
 create mode 100644 client/src/hooks.ts
 create mode 100644 client/src/interfaces/AdminLoginData.ts
 create mode 100644 client/src/interfaces/City.ts
 create mode 100644 client/src/interfaces/Competition.ts
 create mode 100644 client/src/interfaces/CompetitionFilterParams.ts
 create mode 100644 client/src/pages/admin/components/AddCompetition.tsx
 create mode 100644 client/src/pages/admin/styled.tsx
 create mode 100644 client/src/reducers/citiesReducer.ts
 create mode 100644 client/src/reducers/competitionsReducer.ts

diff --git a/client/src/App.test.tsx b/client/src/App.test.tsx
index f27dce75..2eca756c 100644
--- a/client/src/App.test.tsx
+++ b/client/src/App.test.tsx
@@ -1,13 +1,13 @@
+import { render } from '@testing-library/react'
 import React from 'react'
 import { Provider } from 'react-redux'
 import App from './App'
 import store from './store'
 
 test('renders app', () => {
-  ;<Provider store={store}>
-    render(
-    <App />)
-  </Provider>
-  // const linkElement = screen.getByText(/learn react/i)
-  // expect(linkElement).toBeInTheDocument()
+  render(
+    <Provider store={store}>
+      <App />
+    </Provider>
+  )
 })
diff --git a/client/src/App.tsx b/client/src/App.tsx
index 61b64bee..fab142f7 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -1,4 +1,9 @@
-import { createMuiTheme, MuiThemeProvider, StylesProvider } from '@material-ui/core'
+import {
+  MuiThemeProvider,
+  StylesProvider,
+  unstable_createMuiStrictModeTheme as createMuiTheme,
+} from '@material-ui/core'
+// unstable version of createMuiTheme is required for React.StrictMode currently
 import React from 'react'
 import { ThemeProvider } from 'styled-components'
 import Main from './Main'
diff --git a/client/src/__mocks__/axios.js b/client/src/__mocks__/axios.js
new file mode 100644
index 00000000..c3547a13
--- /dev/null
+++ b/client/src/__mocks__/axios.js
@@ -0,0 +1,3 @@
+export default {
+  get: jest.fn().mockImplementation(),
+}
diff --git a/client/src/actions/cities.ts b/client/src/actions/cities.ts
new file mode 100644
index 00000000..381d0a04
--- /dev/null
+++ b/client/src/actions/cities.ts
@@ -0,0 +1,15 @@
+import axios from 'axios'
+import { AppDispatch } from './../store'
+import Types from './types'
+
+export const getCities = () => async (dispatch: AppDispatch) => {
+  await axios
+    .get('/misc/cities')
+    .then((res) => {
+      dispatch({
+        type: Types.SET_CITIES,
+        payload: res.data,
+      })
+    })
+    .catch((err) => console.log(err))
+}
diff --git a/client/src/actions/competitions.ts b/client/src/actions/competitions.ts
new file mode 100644
index 00000000..35f12d31
--- /dev/null
+++ b/client/src/actions/competitions.ts
@@ -0,0 +1,39 @@
+import axios from 'axios'
+import { CompetitionFilterParams } from '../interfaces/CompetitionFilterParams'
+import { AppDispatch, RootState } from './../store'
+import Types from './types'
+
+export const getCompetitions = () => async (dispatch: AppDispatch, getState: () => RootState) => {
+  const currentParams: CompetitionFilterParams = getState().competitions.filterParams
+  // Send params in snake-case for api
+  const params = {
+    page_size: currentParams.pageSize,
+    style_id: currentParams.styleId,
+    city_id: currentParams.cityId,
+    name: currentParams.name,
+    page: currentParams.page,
+    year: currentParams.year,
+  }
+  await axios
+    .get('/competitions/search', { params })
+    .then((res) => {
+      dispatch({
+        type: Types.SET_COMPETITIONS,
+        payload: res.data.competitions,
+      })
+      dispatch({
+        type: Types.SET_COMPETITIONS_TOTAL,
+        payload: res.data.total,
+      })
+      dispatch({
+        type: Types.SET_COMPETITIONS_COUNT,
+        payload: res.data.count,
+      })
+    })
+    .catch((err) => {
+      console.log(err)
+    })
+}
+export const setFilterParams = (params: CompetitionFilterParams) => (dispatch: AppDispatch) => {
+  dispatch({ type: Types.SET_COMPETITIONS_FILTER_PARAMS, payload: params })
+}
diff --git a/client/src/actions/types.ts b/client/src/actions/types.ts
index d0fb0d7c..d265a692 100644
--- a/client/src/actions/types.ts
+++ b/client/src/actions/types.ts
@@ -6,6 +6,11 @@ export default {
   CLEAR_ERRORS: 'SET_ERRORS',
   SET_UNAUTHENTICATED: 'SET_UNAUTHENTICATED',
   SET_AUTHENTICATED: 'SET_AUTHENTICATED',
+  SET_COMPETITIONS: 'SET_COMPETITIONS',
+  SET_COMPETITIONS_FILTER_PARAMS: 'SET_COMPETITIONS_FILTER_PARAMS',
+  SET_COMPETITIONS_TOTAL: 'SET_COMPETITIONS_TOTAL',
+  SET_COMPETITIONS_COUNT: 'SET_COMPETITIONS_COUNT',
+  SET_CITIES: 'SET_CITIES',
   AXIOS_GET: 'AXIOS_GET',
   AXIOS_GET_SUCCESS: 'AXIOS_GET_SUCCESS',
   AXIOS_GET_ERROR: 'AXIOS_GET_ERROR',
diff --git a/client/src/actions/user.ts b/client/src/actions/user.ts
index 30a9973a..104722eb 100644
--- a/client/src/actions/user.ts
+++ b/client/src/actions/user.ts
@@ -1,15 +1,18 @@
 import axios from 'axios'
+import { History } from 'history'
+import { AppDispatch } from '../store'
+import { AdminLoginData } from './../interfaces/AdminLoginData'
 import Types from './types'
 
-export const loginUser = (userData: any, history: any) => (dispatch: any) => {
+export const loginUser = (userData: AdminLoginData, history: History) => async (dispatch: AppDispatch) => {
   dispatch({ type: Types.LOADING_UI })
-  axios
+  await axios
     .post('/auth/login', userData)
     .then((res) => {
       const token = `Bearer ${res.data.access_token}`
       localStorage.setItem('token', token) //setting token to local storage
       axios.defaults.headers.common['Authorization'] = token //setting authorize token to header in axios
-      dispatch(getUserData())
+      getUserData()(dispatch)
       dispatch({ type: Types.CLEAR_ERRORS }) // no error
       history.push('/admin') //redirecting to admin page after login success
     })
@@ -22,14 +25,20 @@ export const loginUser = (userData: any, history: any) => (dispatch: any) => {
     })
 }
 
-export const getUserData = () => (dispatch: any) => {
+export const getUserData = () => async (dispatch: AppDispatch) => {
   dispatch({ type: Types.LOADING_USER })
-  axios
+  await axios
     .get('/users')
     .then((res) => {
       dispatch({
         type: Types.SET_USER,
-        payload: res.data,
+        payload: {
+          id: res.data.id,
+          name: res.data.name,
+          email: res.data.email,
+          roleId: res.data.role_id,
+          cityId: res.data.city_id,
+        },
       })
     })
     .catch((err) => {
@@ -37,7 +46,7 @@ export const getUserData = () => (dispatch: any) => {
     })
 }
 
-export const logoutUser = () => (dispatch: any) => {
+export const logoutUser = () => (dispatch: AppDispatch) => {
   localStorage.removeItem('token')
   delete axios.defaults.headers.common['Authorization']
   dispatch({
diff --git a/client/src/hooks.ts b/client/src/hooks.ts
new file mode 100644
index 00000000..597f2813
--- /dev/null
+++ b/client/src/hooks.ts
@@ -0,0 +1,5 @@
+import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
+import type { AppDispatch, RootState } from './store'
+
+export const useAppDispatch = () => useDispatch<AppDispatch>()
+export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
diff --git a/client/src/interfaces/AdminLoginData.ts b/client/src/interfaces/AdminLoginData.ts
new file mode 100644
index 00000000..70f61036
--- /dev/null
+++ b/client/src/interfaces/AdminLoginData.ts
@@ -0,0 +1,4 @@
+export interface AdminLoginData {
+  email: string
+  password: string
+}
diff --git a/client/src/interfaces/City.ts b/client/src/interfaces/City.ts
new file mode 100644
index 00000000..e5190b6b
--- /dev/null
+++ b/client/src/interfaces/City.ts
@@ -0,0 +1,4 @@
+export interface City {
+  id: number
+  name: string
+}
diff --git a/client/src/interfaces/Competition.ts b/client/src/interfaces/Competition.ts
new file mode 100644
index 00000000..433ae5a3
--- /dev/null
+++ b/client/src/interfaces/Competition.ts
@@ -0,0 +1,7 @@
+export interface Competition {
+  name: string
+  city_id: number
+  style_id: number
+  year: number
+  id: number
+}
diff --git a/client/src/interfaces/CompetitionFilterParams.ts b/client/src/interfaces/CompetitionFilterParams.ts
new file mode 100644
index 00000000..83946052
--- /dev/null
+++ b/client/src/interfaces/CompetitionFilterParams.ts
@@ -0,0 +1,8 @@
+export interface CompetitionFilterParams {
+  name?: string
+  year?: number
+  cityId?: number
+  styleId?: number
+  page: number
+  pageSize: number
+}
diff --git a/client/src/interfaces/models.ts b/client/src/interfaces/models.ts
index f2bc0b12..91920bd6 100644
--- a/client/src/interfaces/models.ts
+++ b/client/src/interfaces/models.ts
@@ -3,6 +3,12 @@ export interface AccountLoginModel {
   password: string
 }
 
+export interface AddCompetitionModel {
+  name: string
+  city: string
+  year: number
+}
+
 export interface CompetitionLoginModel {
   code: string
 }
diff --git a/client/src/pages/admin/AdminPage.tsx b/client/src/pages/admin/AdminPage.tsx
index 5a524470..3d2b94b8 100644
--- a/client/src/pages/admin/AdminPage.tsx
+++ b/client/src/pages/admin/AdminPage.tsx
@@ -3,7 +3,6 @@ import {
   Button,
   CssBaseline,
   Divider,
-  Drawer,
   List,
   ListItem,
   ListItemIcon,
@@ -15,13 +14,14 @@ import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
 import DashboardIcon from '@material-ui/icons/Dashboard'
 import MailIcon from '@material-ui/icons/Mail'
 import React from 'react'
-import { connect } from 'react-redux'
 import { Link, Route, Switch, useRouteMatch } from 'react-router-dom'
 import { logoutUser } from '../../actions/user'
+import { useAppDispatch } from '../../hooks'
 import CompetitionManager from './components/CompetitionManager'
 import Regions from './components/Regions'
+import { LeftDrawer } from './styled'
 
-const drawerWidth = 240
+const drawerWidth = 250
 const menuItems = ['Startsida', 'Regioner', 'Användare', 'Tävlingshanterare']
 
 const useStyles = makeStyles((theme: Theme) =>
@@ -30,14 +30,9 @@ const useStyles = makeStyles((theme: Theme) =>
       display: 'flex',
     },
     appBar: {
-      width: `calc(100% - ${drawerWidth}px)`,
+      width: '100%',
       marginLeft: drawerWidth,
     },
-    drawer: {
-      width: drawerWidth,
-      flexShrink: 0,
-      marginRight: drawerWidth,
-    },
     drawerPaper: {
       width: drawerWidth,
     },
@@ -51,13 +46,14 @@ const useStyles = makeStyles((theme: Theme) =>
   })
 )
 
-const AdminView: React.FC = (props: any) => {
+const AdminView: React.FC = () => {
   const classes = useStyles()
   const [openIndex, setOpenIndex] = React.useState(0)
   const { path, url } = useRouteMatch()
   const handleLogout = () => {
-    props.logoutUser()
+    dispatch(logoutUser())
   }
+  const dispatch = useAppDispatch()
   return (
     <div className={classes.root}>
       <CssBaseline />
@@ -68,12 +64,12 @@ const AdminView: React.FC = (props: any) => {
           </Typography>
         </Toolbar>
       </AppBar>
-      <Drawer
-        className={(classes.drawer, 'background')}
-        variant="permanent"
+      <LeftDrawer
+        width={drawerWidth}
         classes={{
           paper: classes.drawerPaper,
         }}
+        variant="permanent"
         anchor="left"
       >
         <div>
@@ -103,7 +99,7 @@ const AdminView: React.FC = (props: any) => {
             </ListItem>
           </List>
         </div>
-      </Drawer>
+      </LeftDrawer>
       <main className={classes.content}>
         <div className={classes.toolbar} />
         <Switch>
@@ -128,7 +124,4 @@ const AdminView: React.FC = (props: any) => {
     </div>
   )
 }
-const mapDispatchToProps = {
-  logoutUser,
-}
-export default connect(null, mapDispatchToProps)(AdminView)
+export default AdminView
diff --git a/client/src/pages/admin/components/AddCompetition.tsx b/client/src/pages/admin/components/AddCompetition.tsx
new file mode 100644
index 00000000..3bb54db0
--- /dev/null
+++ b/client/src/pages/admin/components/AddCompetition.tsx
@@ -0,0 +1,182 @@
+import { Button, FormControl, InputLabel, MenuItem, Popover, TextField } from '@material-ui/core'
+import { Alert, AlertTitle } from '@material-ui/lab'
+import axios from 'axios'
+import { Formik, FormikHelpers } from 'formik'
+import React from 'react'
+import * as Yup from 'yup'
+import { getCompetitions } from '../../../actions/competitions'
+import { useAppDispatch, useAppSelector } from '../../../hooks'
+import { City } from '../../../interfaces/City'
+import { AddCompetitionModel } from '../../../interfaces/models'
+import { AddCompetitionButton, AddCompetitionContent, AddCompetitionForm } from './styled'
+
+interface ServerResponse {
+  code: number
+  message: string
+}
+
+interface AddCompetitionFormModel {
+  model: AddCompetitionModel
+  error?: string
+}
+const noCitySelected = 'Välj stad'
+
+const competitionSchema: Yup.SchemaOf<AddCompetitionFormModel> = Yup.object({
+  model: Yup.object()
+    .shape({
+      name: Yup.string().required('Namn krävs'),
+      city: Yup.string().required('Stad krävs').notOneOf([noCitySelected], 'Välj en stad'),
+      year: Yup.number()
+        .integer('År måste vara ett heltal')
+        .required('År krävs')
+        .moreThan(1999, 'År måste vara minst 2000'),
+    })
+    .required(),
+  error: Yup.string().optional(),
+})
+
+const AddCompetition: React.FC = (props: any) => {
+  const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null)
+  const [selectedCity, setSelectedCity] = React.useState<City | undefined>()
+  const cities = useAppSelector((state) => state.cities)
+  const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
+    setAnchorEl(event.currentTarget)
+  }
+  const handleClose = () => {
+    setAnchorEl(null)
+  }
+
+  const open = Boolean(anchorEl)
+  const dispatch = useAppDispatch()
+  const id = open ? 'simple-popover' : undefined
+  const currentYear = new Date().getFullYear()
+  const handleCompetitionSubmit = async (
+    values: AddCompetitionFormModel,
+    actions: FormikHelpers<AddCompetitionFormModel>
+  ) => {
+    const params = {
+      name: values.model.name,
+      year: values.model.year,
+      city_id: selectedCity?.id as number,
+      style_id: 1,
+    }
+    await axios
+      .post<ServerResponse>('/competitions', params)
+      .then(() => {
+        actions.resetForm()
+        setAnchorEl(null)
+        dispatch(getCompetitions())
+        setSelectedCity(undefined)
+      })
+      .catch(({ response }) => {
+        console.warn(response.data)
+        if (response.data && response.data.message)
+          actions.setFieldError('error', response.data && response.data.message)
+        else actions.setFieldError('error', 'Something went wrong, please try again')
+      })
+      .finally(() => {
+        actions.setSubmitting(false)
+      })
+  }
+
+  const competitionInitialValues: AddCompetitionFormModel = {
+    model: { name: '', city: noCitySelected, year: currentYear },
+  }
+  return (
+    <div>
+      <AddCompetitionButton color="secondary" variant="contained" onClick={handleClick}>
+        Ny Tävling
+      </AddCompetitionButton>
+      <Popover
+        id={id}
+        open={open}
+        anchorEl={anchorEl}
+        onClose={handleClose}
+        anchorOrigin={{
+          vertical: 'bottom',
+          horizontal: 'center',
+        }}
+        transformOrigin={{
+          vertical: 'top',
+          horizontal: 'center',
+        }}
+      >
+        <AddCompetitionContent>
+          <Formik
+            initialValues={competitionInitialValues}
+            validationSchema={competitionSchema}
+            onSubmit={handleCompetitionSubmit}
+          >
+            {(formik) => (
+              <AddCompetitionForm onSubmit={formik.handleSubmit}>
+                <TextField
+                  label="Namn"
+                  name="model.name"
+                  helperText={formik.touched.model?.name ? formik.errors.model?.name : ''}
+                  error={Boolean(formik.touched.model?.name && formik.errors.model?.name)}
+                  onChange={formik.handleChange}
+                  onBlur={formik.handleBlur}
+                  margin="normal"
+                />
+                <FormControl>
+                  <InputLabel shrink id="demo-customized-select-native">
+                    Region
+                  </InputLabel>
+                  <TextField
+                    select
+                    name="model.city"
+                    id="standard-select-currency"
+                    value={selectedCity ? selectedCity.name : noCitySelected}
+                    onChange={formik.handleChange}
+                    onBlur={formik.handleBlur}
+                    error={Boolean(formik.errors.model?.city && formik.touched.model?.city)}
+                    helperText={formik.touched.model?.city && formik.errors.model?.city}
+                    margin="normal"
+                  >
+                    <MenuItem value={noCitySelected} onClick={() => setSelectedCity(undefined)}>
+                      {noCitySelected}
+                    </MenuItem>
+                    {cities &&
+                      cities.map((city) => (
+                        <MenuItem key={city.name} value={city.name} onClick={() => setSelectedCity(city)}>
+                          {city.name}
+                        </MenuItem>
+                      ))}
+                  </TextField>
+                </FormControl>
+                <TextField
+                  label="Ã…r"
+                  name="model.year"
+                  type="number"
+                  defaultValue={formik.initialValues.model.year}
+                  helperText={formik.touched.model?.year ? formik.errors.model?.year : ''}
+                  error={Boolean(formik.touched.model?.year && formik.errors.model?.year)}
+                  onChange={formik.handleChange}
+                  onBlur={formik.handleBlur}
+                  margin="normal"
+                />
+                <Button
+                  type="submit"
+                  fullWidth
+                  variant="contained"
+                  color="secondary"
+                  disabled={!formik.isValid || !formik.values.model?.name || !formik.values.model?.city}
+                >
+                  Skapa
+                </Button>
+                {formik.errors.error && (
+                  <Alert severity="error">
+                    <AlertTitle>Error</AlertTitle>
+                    {formik.errors.error}
+                  </Alert>
+                )}
+              </AddCompetitionForm>
+            )}
+          </Formik>
+        </AddCompetitionContent>
+      </Popover>
+    </div>
+  )
+}
+
+export default AddCompetition
diff --git a/client/src/pages/admin/components/CompetitionManager.test.tsx b/client/src/pages/admin/components/CompetitionManager.test.tsx
index 2a92138f..3fb03b69 100644
--- a/client/src/pages/admin/components/CompetitionManager.test.tsx
+++ b/client/src/pages/admin/components/CompetitionManager.test.tsx
@@ -1,12 +1,56 @@
 import { render } from '@testing-library/react'
+import mockedAxios from 'axios'
 import React from 'react'
+import { Provider } from 'react-redux'
 import { BrowserRouter } from 'react-router-dom'
+import store from '../../../store'
 import CompetitionManager from './CompetitionManager'
 
 it('renders competition manager', () => {
+  const cityRes: any = {
+    data: [
+      {
+        id: 1,
+        name: 'Link\u00f6ping',
+      },
+      {
+        id: 2,
+        name: 'Stockholm',
+      },
+    ],
+  }
+  const compRes: any = {
+    data: {
+      competitions: [
+        {
+          id: 21,
+          name: 'ggff',
+          year: 2021,
+          style_id: 1,
+          city_id: 1,
+        },
+        {
+          id: 22,
+          name: 'sssss',
+          year: 2021,
+          style_id: 1,
+          city_id: 1,
+        },
+      ],
+      count: 2,
+      total: 3,
+    },
+  }
+
+  ;(mockedAxios.get as jest.Mock).mockImplementation((path: string, params?: any) => {
+    if (path === '/competitions/search') return Promise.resolve(compRes)
+    else return Promise.resolve(cityRes)
+  })
   render(
     <BrowserRouter>
-      <CompetitionManager />
+      <Provider store={store}>
+        <CompetitionManager />
+      </Provider>
     </BrowserRouter>
   )
 })
diff --git a/client/src/pages/admin/components/CompetitionManager.tsx b/client/src/pages/admin/components/CompetitionManager.tsx
index a91b6636..c6803b03 100644
--- a/client/src/pages/admin/components/CompetitionManager.tsx
+++ b/client/src/pages/admin/components/CompetitionManager.tsx
@@ -1,11 +1,10 @@
-import { Button, Menu } from '@material-ui/core'
+import { Button, Menu, TablePagination, TextField, Typography } from '@material-ui/core'
 import FormControl from '@material-ui/core/FormControl'
-import InputBase from '@material-ui/core/InputBase'
 import InputLabel from '@material-ui/core/InputLabel'
 import MenuItem from '@material-ui/core/MenuItem'
 import Paper from '@material-ui/core/Paper'
 import Select from '@material-ui/core/Select'
-import { createStyles, makeStyles, Theme, withStyles } from '@material-ui/core/styles'
+import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
 import Table from '@material-ui/core/Table'
 import TableBody from '@material-ui/core/TableBody'
 import TableCell from '@material-ui/core/TableCell'
@@ -13,66 +12,20 @@ import TableContainer from '@material-ui/core/TableContainer'
 import TableHead from '@material-ui/core/TableHead'
 import TableRow from '@material-ui/core/TableRow'
 import MoreHorizIcon from '@material-ui/icons/MoreHoriz'
-import React from 'react'
+import axios from 'axios'
+import React, { useEffect } from 'react'
 import { Link } from 'react-router-dom'
-import { NewCompetitionButton, RemoveCompetition, TopBar } from './styled'
-
-const BootstrapInput = withStyles((theme: Theme) =>
-  createStyles({
-    root: {
-      'label + &': {
-        marginTop: theme.spacing(3),
-      },
-    },
-    input: {
-      borderRadius: 4,
-      position: 'relative',
-      backgroundColor: theme.palette.background.paper,
-      border: '1px solid #ced4da',
-      fontSize: 16,
-      padding: '10px 26px 10px 12px',
-      transition: theme.transitions.create(['border-color', 'box-shadow']),
-      '&:focus': {
-        borderRadius: 4,
-        borderColor: '#80bdff',
-        boxShadow: '0 0 0 0.2rem rgba(0,123,255,.25)',
-      },
-    },
-  })
-)(InputBase)
-
-function createCompetition(name: string, region: string, year: number, id: number) {
-  return { name, region, year, id }
-}
-
-const competitions = [
-  createCompetition('Tävling 1', 'Stockholm', 2021, 1),
-  createCompetition('Tävling 2', 'Stockholm', 2020, 2),
-  createCompetition('Tävling 3', 'Sala', 2020, 3),
-  createCompetition('Tävling 4', 'Sundsvall', 2020, 4),
-  createCompetition('Tävling 5', 'Linköping', 2020, 5),
-  createCompetition('Tävling 6', 'Linköping', 2020, 6),
-  createCompetition('Tävling 7', 'Sala', 2019, 7),
-  createCompetition('Tävling 8', 'Stockholm', 2019, 8),
-  createCompetition('Tävling 9', 'Stockholm', 2019, 9),
-  createCompetition('Tävling 10', 'Lidköping', 2019, 10),
-  createCompetition('Tävling 11', 'Stockholm', 2019, 11),
-  createCompetition('Tävling 12', 'Sala', 2018, 12),
-  createCompetition('Tävling 13', 'Tornby', 2018, 13),
-]
-
-const regions = competitions
-  .map((competition) => competition.region)
-  .filter((competition, index, self) => self.indexOf(competition) === index)
-
-const years = competitions
-  .map((competition) => competition.year)
-  .filter((competition, index, self) => self.indexOf(competition) === index)
+import { getCities } from '../../../actions/cities'
+import { getCompetitions, setFilterParams } from '../../../actions/competitions'
+import { useAppDispatch, useAppSelector } from '../../../hooks'
+import { CompetitionFilterParams } from '../../../interfaces/CompetitionFilterParams'
+import AddCompetition from './AddCompetition'
+import { FilterContainer, RemoveCompetition, TopBar, YearFilterTextField } from './styled'
 
 const useStyles = makeStyles((theme: Theme) =>
   createStyles({
     table: {
-      width: 1500, // TODO: Shrink table when smaller screen
+      width: '100%',
     },
     margin: {
       margin: theme.spacing(1),
@@ -80,37 +33,71 @@ const useStyles = makeStyles((theme: Theme) =>
   })
 )
 
-const CompetitionManager: React.FC = () => {
+const CompetitionManager: React.FC = (props: any) => {
   const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null)
+  const [activeId, setActiveId] = React.useState<number | undefined>(undefined)
+  const [timerHandle, setTimerHandle] = React.useState<number | undefined>(undefined)
+  const competitions = useAppSelector((state) => state.competitions.competitions)
+  const filterParams = useAppSelector((state) => state.competitions.filterParams)
+  const competitionTotal = useAppSelector((state) => state.competitions.total)
+  const cities = useAppSelector((state) => state.cities)
   const classes = useStyles()
-  const yearInitialValue = 0
-  const regionInitialValue = ''
   const noFilterText = 'Alla'
-  const [searchInput, setSearchInput] = React.useState('')
-  const [year, setYear] = React.useState(yearInitialValue)
-  const [region, setRegion] = React.useState(regionInitialValue)
-  const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
+  const dispatch = useAppDispatch()
+  const handleClick = (event: React.MouseEvent<HTMLButtonElement>, id: number) => {
     setAnchorEl(event.currentTarget)
+    setActiveId(id)
   }
 
   const handleClose = () => {
     setAnchorEl(null)
+    setActiveId(undefined)
   }
 
+  useEffect(() => {
+    dispatch(getCities())
+    dispatch(getCompetitions())
+  }, [])
+
   const onSearchChange = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
-    setSearchInput(event.target.value)
+    if (timerHandle) {
+      clearTimeout(timerHandle)
+      setTimerHandle(undefined)
+    }
+    //Only updates filter and api 100ms after last input was made
+    setTimerHandle(window.setTimeout(() => dispatch(getCompetitions()), 100))
+    dispatch(setFilterParams({ ...filterParams, name: event.target.value }))
+  }
+
+  const handleDeleteCompetition = async () => {
+    if (activeId) {
+      await axios
+        .delete(`/competitions/${activeId}`)
+        .then(() => {
+          setAnchorEl(null)
+          dispatch(getCompetitions())
+        })
+        .catch(({ response }) => {
+          console.warn(response.data)
+        })
+    }
+  }
+
+  const handleFilterChange = (newParams: CompetitionFilterParams) => {
+    dispatch(setFilterParams(newParams))
+    dispatch(getCompetitions())
   }
 
   return (
     <div>
       <TopBar>
-        <div>
-          <FormControl className={classes.margin}>
-            <InputLabel shrink id="demo-customized-textbox">
-              Sök
-            </InputLabel>
-            <BootstrapInput id="demo-customized-textbox" onChange={onSearchChange} />
-          </FormControl>
+        <FilterContainer>
+          <TextField
+            className={classes.margin}
+            value={filterParams.name || ''}
+            onChange={onSearchChange}
+            label="Sök"
+          ></TextField>
           <FormControl className={classes.margin}>
             <InputLabel shrink id="demo-customized-select-native">
               Region
@@ -118,42 +105,33 @@ const CompetitionManager: React.FC = () => {
             <Select
               labelId="demo-customized-select-label"
               id="demo-customized-select"
-              value={region === regionInitialValue ? noFilterText : region}
-              input={<BootstrapInput />}
-            >
-              <MenuItem value={noFilterText} onClick={() => setRegion(regionInitialValue)}>
-                {noFilterText}
-              </MenuItem>
-              {regions.map((text) => (
-                <MenuItem key={text} value={text} onClick={() => setRegion(text)}>
-                  {text}
-                </MenuItem>
-              ))}
-            </Select>
-          </FormControl>
-          <FormControl className={classes.margin}>
-            <InputLabel shrink id="demo-customized-select-label">
-              Ã…r
-            </InputLabel>
-            <Select
-              id="demo-customized-select"
-              value={year === yearInitialValue ? noFilterText : year}
-              input={<BootstrapInput />}
+              value={filterParams.cityId ? cities.find((city) => filterParams.cityId === city.id)?.name : noFilterText}
             >
-              <MenuItem value={noFilterText} onClick={() => setYear(yearInitialValue)}>
+              <MenuItem value={noFilterText} onClick={() => handleFilterChange({ ...filterParams, cityId: undefined })}>
                 {noFilterText}
               </MenuItem>
-              {years.map((year) => (
-                <MenuItem key={year} value={year} onClick={() => setYear(year)}>
-                  {year}
-                </MenuItem>
-              ))}
+              {cities &&
+                cities.map((city) => (
+                  <MenuItem
+                    key={city.name}
+                    value={city.name}
+                    onClick={() => handleFilterChange({ ...filterParams, cityId: city.id })}
+                  >
+                    {city.name}
+                  </MenuItem>
+                ))}
             </Select>
           </FormControl>
-        </div>
-        <NewCompetitionButton color="secondary" variant="contained">
-          Ny Tävling
-        </NewCompetitionButton>
+          <YearFilterTextField
+            label="Ã…r"
+            name="model.year"
+            type="number"
+            value={filterParams.year || new Date().getFullYear()}
+            onChange={(event) => handleFilterChange({ ...filterParams, year: +event.target.value })}
+            margin="normal"
+          />
+        </FilterContainer>
+        <AddCompetition />
       </TopBar>
       <TableContainer component={Paper}>
         <Table className={classes.table} aria-label="simple table">
@@ -166,24 +144,18 @@ const CompetitionManager: React.FC = () => {
             </TableRow>
           </TableHead>
           <TableBody>
-            {competitions
-              .filter((row) => {
-                const nameOkay = row.name.match(RegExp(searchInput, 'i')) //Makes sure name matches search input case insensitively
-                const yearOkay = year == yearInitialValue || row.year == year
-                const regionOkay = region == regionInitialValue || row.region == region
-                return yearOkay && regionOkay && nameOkay
-              })
-              .map((row) => (
+            {competitions &&
+              competitions.map((row) => (
                 <TableRow key={row.name}>
                   <TableCell scope="row">
                     <Button color="primary" component={Link} to={`/editor/competition-id=${row.id}`}>
                       {row.name}
                     </Button>
                   </TableCell>
-                  <TableCell align="right">{row.region}</TableCell>
+                  <TableCell align="right">{cities.find((city) => city.id === row.city_id)?.name || ''}</TableCell>
                   <TableCell align="right">{row.year}</TableCell>
                   <TableCell align="right">
-                    <Button onClick={handleClick}>
+                    <Button onClick={(event) => handleClick(event, row.id)}>
                       <MoreHorizIcon />
                     </Button>
                   </TableCell>
@@ -191,11 +163,22 @@ const CompetitionManager: React.FC = () => {
               ))}
           </TableBody>
         </Table>
+        {(!competitions || competitions.length === 0) && (
+          <Typography>Inga tävlingar hittades med nuvarande filter</Typography>
+        )}
       </TableContainer>
+      <TablePagination
+        component="div"
+        rowsPerPageOptions={[]}
+        rowsPerPage={filterParams.pageSize}
+        count={competitionTotal}
+        page={filterParams.page}
+        onChangePage={(event, newPage) => handleFilterChange({ ...filterParams, page: newPage })}
+      />
       <Menu id="simple-menu" anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose}>
         <MenuItem onClick={handleClose}>Starta</MenuItem>
         <MenuItem onClick={handleClose}>Duplicera</MenuItem>
-        <RemoveCompetition onClick={handleClose}>Ta bort</RemoveCompetition>
+        <RemoveCompetition onClick={handleDeleteCompetition}>Ta bort</RemoveCompetition>
       </Menu>
     </div>
   )
diff --git a/client/src/pages/admin/components/styled.tsx b/client/src/pages/admin/components/styled.tsx
index 7a7f75ed..69040178 100644
--- a/client/src/pages/admin/components/styled.tsx
+++ b/client/src/pages/admin/components/styled.tsx
@@ -1,4 +1,4 @@
-import { Button, MenuItem } from '@material-ui/core'
+import { Button, MenuItem, TextField } from '@material-ui/core'
 import styled from 'styled-components'
 
 export const TopBar = styled.div`
@@ -7,12 +7,29 @@ export const TopBar = styled.div`
   align-items: flex-end;
 `
 
-export const NewCompetitionButton = styled(Button)`
+export const AddCompetitionButton = styled(Button)`
   margin-bottom: 8px;
 `
 
+export const AddCompetitionForm = styled.form`
+  display: flex;
+  flex-direction: column;
+`
+
+export const AddCompetitionContent = styled.div`
+  padding: 15px;
+`
+
 export const RemoveCompetition = styled(MenuItem)`
-  color:red;
+  color: red;
 `
 
+export const YearFilterTextField = styled(TextField)`
+  width: 70px;
+`
 
+export const FilterContainer = styled.div`
+  display: flex;
+  align-items: flex-end;
+  margin: left 10px;
+`
diff --git a/client/src/pages/admin/styled.tsx b/client/src/pages/admin/styled.tsx
new file mode 100644
index 00000000..e4687523
--- /dev/null
+++ b/client/src/pages/admin/styled.tsx
@@ -0,0 +1,13 @@
+import { Drawer, DrawerProps } from '@material-ui/core'
+import React from 'react'
+import styled from 'styled-components'
+
+interface leftDrawerProps extends DrawerProps {
+  width: number
+}
+export const LeftDrawer = styled((props: leftDrawerProps) => <Drawer {...props} />)`
+  width: ${(props) => (props.width ? props.width : '500px')};
+  flex-shrink: 0;
+  margin-right: ${(props) => (props.width ? props.width : '500px')};
+  z-index: 1;
+`
diff --git a/client/src/pages/login/components/AdminLogin.tsx b/client/src/pages/login/components/AdminLogin.tsx
index 531ae22e..cdff4d3f 100644
--- a/client/src/pages/login/components/AdminLogin.tsx
+++ b/client/src/pages/login/components/AdminLogin.tsx
@@ -2,10 +2,10 @@ import { Button, TextField } from '@material-ui/core'
 import { Alert, AlertTitle } from '@material-ui/lab'
 import { Formik, FormikHelpers } from 'formik'
 import React, { useEffect, useState } from 'react'
-import { connect } from 'react-redux'
 import { useHistory } from 'react-router-dom'
 import * as Yup from 'yup'
 import { loginUser } from '../../../actions/user'
+import { useAppDispatch, useAppSelector } from '../../../hooks'
 import { AccountLoginModel } from '../../../interfaces/models'
 import { CenteredCircularProgress, LoginForm } from './styled'
 
@@ -14,11 +14,6 @@ interface AccountLoginFormModel {
   error?: string
 }
 
-interface ServerResponse {
-  code: number
-  message: string
-}
-
 interface formError {
   message: string
 }
@@ -33,17 +28,20 @@ const accountSchema: Yup.SchemaOf<AccountLoginFormModel> = Yup.object({
   error: Yup.string().optional(),
 })
 
-const AdminLogin: React.FC = (props: any) => {
+const AdminLogin: React.FC = () => {
   const [errors, setErrors] = useState({} as formError)
   const [loading, setLoading] = useState(false)
+  const dispatch = useAppDispatch()
+  const UIErrors = useAppSelector((state) => state.UI.errors)
+  const UILoading = useAppSelector((state) => state.UI.loading)
   useEffect(() => {
-    if (props.UI.errors) {
-      setErrors(props.UI.errors)
+    if (UIErrors) {
+      setErrors(UIErrors)
     }
-    setLoading(props.UI.loading)
-  }, [props.UI])
+    setLoading(UILoading)
+  }, [UIErrors, UILoading])
   const handleAccountSubmit = (values: AccountLoginFormModel, actions: FormikHelpers<AccountLoginFormModel>) => {
-    props.loginUser(values.model, history)
+    dispatch(loginUser(values.model, history))
   }
 
   const history = useHistory()
@@ -94,11 +92,4 @@ const AdminLogin: React.FC = (props: any) => {
     </Formik>
   )
 }
-const mapStateToProps = (state: any) => ({
-  user: state.user,
-  UI: state.UI,
-})
-const mapDispatchToProps = {
-  loginUser,
-}
-export default connect(mapStateToProps, mapDispatchToProps)(AdminLogin)
+export default AdminLogin
diff --git a/client/src/pages/login/components/CompetitionLogin.tsx b/client/src/pages/login/components/CompetitionLogin.tsx
index 42b9af4e..9ebf658f 100644
--- a/client/src/pages/login/components/CompetitionLogin.tsx
+++ b/client/src/pages/login/components/CompetitionLogin.tsx
@@ -30,7 +30,6 @@ const handleCompetitionSubmit = async (
   values: CompetitionLoginFormModel,
   actions: FormikHelpers<CompetitionLoginFormModel>
 ) => {
-  console.log(values.model)
   await axios
     .post<ServerResponse>(`users/login`, { code: values.model.code })
     .then(() => {
diff --git a/client/src/reducers/allReducers.ts b/client/src/reducers/allReducers.ts
index 98260e16..b12b5346 100644
--- a/client/src/reducers/allReducers.ts
+++ b/client/src/reducers/allReducers.ts
@@ -1,6 +1,8 @@
 // Combines all the reducers so that we only have to pass "one" reducer to the store in src/index.tsx
 
 import { combineReducers } from 'redux'
+import citiesReducer from './citiesReducer'
+import competitionsReducer from './competitionsReducer'
 import uiReducer from './uiReducer'
 import userReducer from './userReducer'
 
@@ -8,5 +10,7 @@ const allReducers = combineReducers({
   // name: state
   user: userReducer,
   UI: uiReducer,
+  competitions: competitionsReducer,
+  cities: citiesReducer,
 })
 export default allReducers
diff --git a/client/src/reducers/citiesReducer.ts b/client/src/reducers/citiesReducer.ts
new file mode 100644
index 00000000..871123d8
--- /dev/null
+++ b/client/src/reducers/citiesReducer.ts
@@ -0,0 +1,14 @@
+import { AnyAction } from 'redux'
+import Types from '../actions/types'
+import { City } from '../interfaces/City'
+
+const initialState: City[] = []
+
+export default function (state = initialState, action: AnyAction) {
+  switch (action.type) {
+    case Types.SET_CITIES:
+      return action.payload as City[]
+    default:
+      return state
+  }
+}
diff --git a/client/src/reducers/competitionsReducer.ts b/client/src/reducers/competitionsReducer.ts
new file mode 100644
index 00000000..b3003d4a
--- /dev/null
+++ b/client/src/reducers/competitionsReducer.ts
@@ -0,0 +1,45 @@
+import { AnyAction } from 'redux'
+import Types from '../actions/types'
+import { Competition } from '../interfaces/Competition'
+import { CompetitionFilterParams } from './../interfaces/CompetitionFilterParams'
+
+interface CompetitionState {
+  competitions: Competition[]
+  total: number
+  count: number
+  filterParams: CompetitionFilterParams
+}
+
+const initialState: CompetitionState = {
+  competitions: [],
+  total: 0,
+  count: 0,
+  filterParams: { pageSize: 10, page: 0 },
+}
+
+export default function (state = initialState, action: AnyAction) {
+  switch (action.type) {
+    case Types.SET_COMPETITIONS:
+      return {
+        ...state,
+        competitions: action.payload as Competition[],
+      }
+    case Types.SET_COMPETITIONS_FILTER_PARAMS:
+      return {
+        ...state,
+        filterParams: action.payload as CompetitionFilterParams,
+      }
+    case Types.SET_COMPETITIONS_TOTAL:
+      return {
+        ...state,
+        total: action.payload as number,
+      }
+    case Types.SET_COMPETITIONS_COUNT:
+      return {
+        ...state,
+        count: action.payload as number,
+      }
+    default:
+      return state
+  }
+}
diff --git a/client/src/reducers/uiReducer.ts b/client/src/reducers/uiReducer.ts
index 8aac6daf..4d06d1e2 100644
--- a/client/src/reducers/uiReducer.ts
+++ b/client/src/reducers/uiReducer.ts
@@ -1,16 +1,27 @@
+import { AnyAction } from 'redux'
 import Types from '../actions/types'
-const initialState = {
+
+interface UIError {
+  message: string
+}
+
+interface UIState {
+  loading: boolean
+  errors: null | UIError
+}
+
+const initialState: UIState = {
   loading: false,
   errors: null,
 }
 
-export default function (state = initialState, action: any) {
+export default function (state = initialState, action: AnyAction) {
   switch (action.type) {
     case Types.SET_ERRORS:
       return {
         ...state,
         loading: false,
-        errors: action.payload,
+        errors: action.payload as UIError,
       }
     case Types.CLEAR_ERRORS:
       return {
diff --git a/client/src/reducers/userReducer.ts b/client/src/reducers/userReducer.ts
index fff6875b..b60221e6 100644
--- a/client/src/reducers/userReducer.ts
+++ b/client/src/reducers/userReducer.ts
@@ -1,13 +1,27 @@
 //in userReducer.ts
+import { AnyAction } from 'redux'
 import Types from '../actions/types'
 
-const initialState = {
+interface UserInfo {
+  name: string
+  email: string
+  roleId: number
+  cityId: number
+}
+
+interface UserState {
+  authenticated: boolean
+  userInfo: UserInfo | null
+  loading: boolean
+}
+
+const initialState: UserState = {
   authenticated: false,
-  credentials: {},
-  loading: false,
+  loading: true,
+  userInfo: null,
 }
 
-export default function (state = initialState, action: any) {
+export default function (state = initialState, action: AnyAction) {
   switch (action.type) {
     case Types.SET_AUTHENTICATED:
       return {
@@ -20,7 +34,7 @@ export default function (state = initialState, action: any) {
       return {
         authenticated: true,
         loading: false,
-        ...action.payload,
+        userInfo: action.payload as UserInfo,
       }
     case Types.LOADING_USER:
       return {
diff --git a/client/src/store.ts b/client/src/store.ts
index bab656fa..8eec0a48 100644
--- a/client/src/store.ts
+++ b/client/src/store.ts
@@ -1,6 +1,6 @@
-import { applyMiddleware, compose, createStore } from 'redux'
+import { AnyAction, applyMiddleware, compose, createStore } from 'redux'
 import { composeWithDevTools } from 'redux-devtools-extension'
-import thunk from 'redux-thunk'
+import thunk, { ThunkAction, ThunkDispatch } from 'redux-thunk'
 import allReducers from './reducers/allReducers'
 /*
   TypeScript does not know the type of the property. 
@@ -21,5 +21,8 @@ const middleware = [thunk]
 
 // simple store with plugin
 const store = createStore(allReducers, initialState, composeWithDevTools(applyMiddleware(...middleware)))
+export type AppThunk<ReturnType = void> = ThunkAction<ReturnType, RootState, unknown, AnyAction>
+export type RootState = ReturnType<typeof store.getState>
+export type AppDispatch = ThunkDispatch<RootState, void, AnyAction>
 
 export default store
diff --git a/client/src/utils/SecureRoute.tsx b/client/src/utils/SecureRoute.tsx
index 29b5aef5..df4eab9d 100644
--- a/client/src/utils/SecureRoute.tsx
+++ b/client/src/utils/SecureRoute.tsx
@@ -1,22 +1,21 @@
-import React, { useEffect, useState } from 'react'
-import { connect } from 'react-redux'
+import React, { useEffect } from 'react'
 import { Redirect, Route, RouteProps } from 'react-router-dom'
+import { useAppSelector } from '../hooks'
 import { CheckAuthentication } from './checkAuthentication'
 
 interface SecureRouteProps extends RouteProps {
   login?: boolean
-  component: any
-  authenticated: boolean
+  component: React.ComponentType<any>
   rest?: any
 }
 /** Utility component to use for authentication, replace all routes that should be private with secure routes*/
-const SecureRoute: React.FC<SecureRouteProps> = ({ login, component: Component, authenticated, ...rest }: any) => {
-  const [isReady, setReady] = useState(false)
+const SecureRoute: React.FC<SecureRouteProps> = ({ login, component: Component, ...rest }: SecureRouteProps) => {
+  const authenticated = useAppSelector((state) => state.user.authenticated)
+  const loading = useAppSelector((state) => state.user.loading)
   useEffect(() => {
-    CheckAuthentication().then(() => setReady(true))
+    CheckAuthentication()
   }, [])
-  if (isReady) {
-    console.log(login, authenticated, Component)
+  if (!loading) {
     if (login)
       return (
         <Route {...rest} render={(props) => (authenticated ? <Redirect to="/admin" /> : <Component {...props} />)} />
@@ -24,7 +23,4 @@ const SecureRoute: React.FC<SecureRouteProps> = ({ login, component: Component,
     else return <Route {...rest} render={(props) => (authenticated ? <Component {...props} /> : <Redirect to="/" />)} />
   } else return null
 }
-const mapStateToProps = (state: any) => ({
-  authenticated: state.user.authenticated,
-})
-export default connect(mapStateToProps)(SecureRoute)
+export default SecureRoute
diff --git a/client/src/utils/checkAuthentication.ts b/client/src/utils/checkAuthentication.ts
index dbc3113a..41294b14 100644
--- a/client/src/utils/checkAuthentication.ts
+++ b/client/src/utils/checkAuthentication.ts
@@ -4,23 +4,30 @@ import Types from '../actions/types'
 import { logoutUser } from '../actions/user'
 import store from '../store'
 
-const UnAuthorized = async () => {
-  store.dispatch(logoutUser())
+const UnAuthorized = () => {
+  logoutUser()(store.dispatch)
 }
 
-export const CheckAuthentication = async () => {
+export const CheckAuthentication = () => {
   const authToken = localStorage.token
   if (authToken) {
     const decodedToken: any = jwtDecode(authToken)
     if (decodedToken.exp * 1000 >= Date.now()) {
       axios.defaults.headers.common['Authorization'] = authToken
-      await axios
+      store.dispatch({ type: Types.LOADING_USER })
+      console.log('loading user')
+      axios
         .get('/users')
         .then((res) => {
           store.dispatch({ type: Types.SET_AUTHENTICATED })
           store.dispatch({
             type: Types.SET_USER,
-            payload: res.data,
+            payload: {
+              name: res.data.name,
+              email: res.data.email,
+              roleId: res.data.role_id,
+              cityId: res.data.city_id,
+            },
           })
         })
         .catch((error) => {
-- 
GitLab