commit a69da1e39b8aacc2086c45909da5e11d64e1e7d7
Author: Vaclav Honzik <vaclavhonzik98@gmail.com>
Date:   Thu Mar 31 16:58:55 2022 +0200

    re #9367 - redux-persist for data persistence

diff --git a/frontend/package.json b/frontend/package.json
index 072ef7c..bdb87d0 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -9,13 +9,6 @@
     "@faker-js/faker": "^6.0.0",
     "@mui/icons-material": "^5.5.1",
     "@mui/material": "^5.5.2",
-    "@testing-library/jest-dom": "^5.14.1",
-    "@testing-library/react": "^12.0.0",
-    "@testing-library/user-event": "^13.2.1",
-    "@types/jest": "^27.0.1",
-    "@types/node": "^16.7.13",
-    "@types/react": "^17.0.20",
-    "@types/react-dom": "^17.0.9",
     "axios": "^0.26.0",
     "dotenv": "^16.0.0",
     "formik": "^2.2.9",
@@ -25,6 +18,7 @@
     "react-router-dom": "^6.2.2",
     "react-scripts": "5.0.0",
     "redux": "^4.1.2",
+    "redux-persist": "^6.0.0",
     "swagger-typescript-api": "^9.3.1",
     "ts-node": "^10.7.0",
     "typescript": "^4.4.2",
@@ -54,5 +48,16 @@
       "last 1 firefox version",
       "last 1 safari version"
     ]
+  },
+  "devDependencies": {
+    "@types/react-redux": "^7.1.23",
+    "@types/redux-persist": "^4.3.1",
+    "@testing-library/jest-dom": "^5.14.1",
+    "@testing-library/react": "^12.0.0",
+    "@testing-library/user-event": "^13.2.1",
+    "@types/jest": "^27.0.1",
+    "@types/node": "^16.7.13",
+    "@types/react": "^17.0.20",
+    "@types/react-dom": "^17.0.9"
   }
 }
diff --git a/frontend/src/api/axiosInstance.ts b/frontend/src/api/axiosInstance.ts
index 96c7c34..4b562ba 100644
--- a/frontend/src/api/axiosInstance.ts
+++ b/frontend/src/api/axiosInstance.ts
@@ -16,14 +16,14 @@ const axiosInstance = axios.create({
 axiosInstance.interceptors.request.use(config => {
     config.headers = {
         ...config.headers,
-        Authorization: store.getState().auth.token
+        Authorization: store.getState().user.accessToken ?? ''
     }
 })
 
 axiosInstance.interceptors.response.use(response => response, error => {
 
     if (error?.response?.status === 401) {
-        const refreshToken = store.getState().auth.refreshToken
+        const refreshToken = store.getState().user.refreshToken
         // TODO send the refresh token correctly
         console.log('401 called they want their token refreshed');
     }
diff --git a/frontend/src/features/Auth/authReducer.ts b/frontend/src/features/Auth/authReducer.ts
deleted file mode 100644
index 0fe1a13..0000000
--- a/frontend/src/features/Auth/authReducer.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import { AnyAction } from 'redux'
-import { persist, load } from '../../utils/statePersistence'
-
-export interface UserInfo {
-    accessToken: string | undefined // to send api requests
-    refreshToken: string | undefined // to refresh the api key
-    username: string // to display the username
-    roles: string[]
-}
-
-export interface AuthState extends UserInfo {
-    isAuthenticated: boolean // if this is false all other values should be ignored
-}
-
-const statePersistName = 'auth'
-
-// Initial state when the user first starts the application
-const initialState: AuthState = (load(statePersistName) as AuthState) || {
-    isAuthenticated: false,
-    username: '',
-    roles: [],
-}
-
-// All possible actions
-export enum AuthStateActions {
-    LOG_IN = 'LOG_IN',
-    LOG_OUT = 'LOG_OUT',
-    UPDATE_ACCESS_TOKEN = 'REFRESH_ACCESS_TOKEN',
-    UPDATE_REFRESH_TOKEN = 'UPDATE_REFRESH_TOKEN',
-    UPDATE_TOKENS = 'UPDATE_TOKENS',
-}
-
-// Actions
-const authReducer = (state: AuthState = initialState, action: AnyAction) => {
-    switch (action.type) {
-        case AuthStateActions.LOG_IN:
-            return persist(statePersistName, {
-                ...action.payload,
-                isAuthenticated: true,
-            })
-
-        case AuthStateActions.LOG_OUT:
-            return persist(statePersistName, initialState)
-
-        case AuthStateActions.UPDATE_ACCESS_TOKEN:
-            return persist(statePersistName, {
-                ...state,
-                accessToken: action.payload,
-            })
-
-        case AuthStateActions.UPDATE_REFRESH_TOKEN:
-            return persist(statePersistName, {
-                ...state,
-                refreshToken: action.payload,
-            })
-
-        case AuthStateActions.UPDATE_TOKENS:
-            return persist(statePersistName, { ...state, ...action.payload })
-
-        default:
-            return state
-    }
-}
-
-export default authReducer
diff --git a/frontend/src/features/Auth/userReducer.ts b/frontend/src/features/Auth/userReducer.ts
new file mode 100644
index 0000000..3c86a0a
--- /dev/null
+++ b/frontend/src/features/Auth/userReducer.ts
@@ -0,0 +1,69 @@
+import { AnyAction } from 'redux'
+import persistReducer from 'redux-persist/es/persistReducer'
+import storage from 'redux-persist/lib/storage'
+
+export interface UserState {
+    accessToken?: string
+    refreshToken?: string
+    username: string
+    roles: string[]
+    isLoggedIn: boolean
+}
+
+const initialState: UserState = {
+    roles: [],
+    isLoggedIn: false,
+    username: '',
+}
+
+// All possible actions
+export enum AuthStateActions {
+    LOG_IN = 'LOG_IN',
+    LOG_OUT = 'LOG_OUT',
+    UPDATE_ACCESS_TOKEN = 'REFRESH_ACCESS_TOKEN',
+    UPDATE_REFRESH_TOKEN = 'UPDATE_REFRESH_TOKEN',
+    UPDATE_TOKENS = 'UPDATE_TOKENS',
+}
+
+// Only needed if the state is to be persisted
+const persistConfig = {
+    key: 'auth',
+    storage
+}
+
+const _authReducer = (
+    state: UserState = initialState,
+    action: AnyAction
+): UserState => {
+    switch (action.type) {
+        case AuthStateActions.LOG_IN:
+            return {
+                ...action.payload,
+                isAuthenticated: true,
+            }
+        case AuthStateActions.LOG_OUT:
+            return initialState
+
+        case AuthStateActions.UPDATE_ACCESS_TOKEN:
+            return {
+                ...state,
+                accessToken: action.payload,
+            }
+
+        case AuthStateActions.UPDATE_REFRESH_TOKEN:
+            return {
+                ...state,
+                refreshToken: action.payload,
+            }
+
+        case AuthStateActions.UPDATE_TOKENS:
+            return { ...state, ...action.payload }
+
+        default:
+            return state
+    }
+}
+
+const authReducer = persistReducer(persistConfig, _authReducer)
+
+export default authReducer
diff --git a/frontend/src/features/Catalog/Catalog.tsx b/frontend/src/features/Catalog/Catalog.tsx
index 9ce34bf..8915087 100644
--- a/frontend/src/features/Catalog/Catalog.tsx
+++ b/frontend/src/features/Catalog/Catalog.tsx
@@ -8,7 +8,7 @@ import {
     Stack,
     TextField,
 } from '@mui/material'
-import { CatalogDto } from '../../swagger/data-contracts'
+import { CatalogItemDto } from '../../swagger/data-contracts'
 import CatalogTable from './CatalogTable'
 import { faker } from '@faker-js/faker'
 import { useState } from 'react'
@@ -20,7 +20,7 @@ const Catalog = () => {
     }
 
     // Creates a generic row for the table
-    const createRow = (): CatalogDto => ({
+    const createRow = (): CatalogItemDto => ({
         name: faker.commerce.product(),
         certainty: faker.random.number({ min: 1, max: 1000 }),
         longitude: faker.address.latitude() as unknown as number,
@@ -31,7 +31,7 @@ const Catalog = () => {
         countries: [faker.address.country()],
         alternativeNames: [faker.commerce.productName()],
     })
-    const data: CatalogDto[] = Array(33)
+    const data: CatalogItemDto[] = Array(33)
         .fill({})
         .map(() => createRow())
 
diff --git a/frontend/src/features/Catalog/CatalogTable.tsx b/frontend/src/features/Catalog/CatalogTable.tsx
index d929b9b..7078539 100644
--- a/frontend/src/features/Catalog/CatalogTable.tsx
+++ b/frontend/src/features/Catalog/CatalogTable.tsx
@@ -2,18 +2,16 @@ import { useTheme } from '@mui/material/styles'
 import {
     Box,
     Paper,
-    Stack,
     Table,
     TableBody,
     TableCell,
     TableContainer,
-    TableFooter,
     TableHead,
     TablePagination,
     TableRow,
     Typography,
 } from '@mui/material'
-import TablePaginationActions, {
+import {
     TablePaginationActionsProps,
 } from '@mui/material/TablePagination/TablePaginationActions'
 import { FunctionComponent, useState } from 'react'
@@ -22,7 +20,7 @@ import FirstPageIcon from '@mui/icons-material/FirstPage'
 import KeyboardArrowLeft from '@mui/icons-material/KeyboardArrowLeft'
 import KeyboardArrowRight from '@mui/icons-material/KeyboardArrowRight'
 import LastPageIcon from '@mui/icons-material/LastPage'
-import { CatalogDto } from '../../swagger/data-contracts'
+import { CatalogItemDto } from '../../swagger/data-contracts'
 
 // Currently adapted from https://mui.com/components/tables/
 
@@ -129,7 +127,7 @@ const rows = [
 ].sort((a, b) => (a.calories < b.calories ? -1 : 1))
 
 export interface CatalogTableProps {
-    data: CatalogDto[]
+    data: CatalogItemDto[]
 }
 
 const CatalogTable: FunctionComponent<CatalogTableProps> = ({ data }) => {
diff --git a/frontend/src/features/redux/store.ts b/frontend/src/features/redux/store.ts
index 2be8c62..d589664 100644
--- a/frontend/src/features/redux/store.ts
+++ b/frontend/src/features/redux/store.ts
@@ -1,18 +1,17 @@
-// Store that holds the state of the application
 
 import { combineReducers, createStore } from 'redux'
-import authReducer from '../Auth/authReducer'
+import { persistStore } from 'redux-persist'
+import userReducer from '../Auth/userReducer'
 import themeReducer from '../Theme/themeReducer'
 
+// Store holds shared state in the application
 const store = createStore(
-    combineReducers({ auth: authReducer, theme: themeReducer }),
+    combineReducers({ user: userReducer, theme: themeReducer }),
     {}
 )
 
 export default store
-
+export const persistor = persistStore(store)
 export type AppStore = typeof store
-
 export type RootState = ReturnType<typeof store.getState>
-
 export type AppDispatch = typeof store.dispatch
diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx
index 498b837..47c841f 100644
--- a/frontend/src/index.tsx
+++ b/frontend/src/index.tsx
@@ -4,20 +4,23 @@ import './index.css'
 import App from './App'
 import reportWebVitals from './reportWebVitals'
 import { BrowserRouter } from 'react-router-dom'
-import store from './features/redux/store'
+import store, { persistor } from './features/redux/store'
 import { Provider } from 'react-redux'
 import { injectStore } from './api/axiosInstance'
+import { PersistGate } from 'redux-persist/integration/react'
 
 // Injects store to the axios instance in ./api/axiosInstance
 injectStore(store)
 
 ReactDOM.render(
     <Provider store={store}>
-        <React.StrictMode>
-            <BrowserRouter>
-                <App />
-            </BrowserRouter>
-        </React.StrictMode>
+        <PersistGate loading={null} persistor={persistor}>
+            <React.StrictMode>
+                <BrowserRouter>
+                    <App />
+                </BrowserRouter>
+            </React.StrictMode>
+        </PersistGate>
     </Provider>,
     document.getElementById('root')
 )
diff --git a/frontend/src/swagger/Catalog.ts b/frontend/src/swagger/Catalog.ts
index 43b7bf7..9fc6bf8 100644
--- a/frontend/src/swagger/Catalog.ts
+++ b/frontend/src/swagger/Catalog.ts
@@ -9,7 +9,7 @@
  * ---------------------------------------------------------------
  */
 
-import { CatalogDto } from "./data-contracts";
+import { CatalogItemDto } from "./data-contracts";
 import { ContentType, HttpClient, RequestParams } from "./http-client";
 
 export class Catalog<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
@@ -20,7 +20,7 @@ export class Catalog<SecurityDataType = unknown> extends HttpClient<SecurityData
    * @name UpdateCatalogEntry
    * @request PUT:/catalog/{id}
    */
-  updateCatalogEntry = (id: string, data: CatalogDto, params: RequestParams = {}) =>
+  updateCatalogEntry = (id: string, data: CatalogItemDto, params: RequestParams = {}) =>
     this.request<void, any>({
       path: `/catalog/${id}`,
       method: "PUT",
@@ -49,7 +49,7 @@ export class Catalog<SecurityDataType = unknown> extends HttpClient<SecurityData
    * @request GET:/catalog
    */
   getAllUsers1 = (params: RequestParams = {}) =>
-    this.request<CatalogDto[], any>({
+    this.request<CatalogItemDto[], any>({
       path: `/catalog`,
       method: "GET",
       ...params,
@@ -61,7 +61,7 @@ export class Catalog<SecurityDataType = unknown> extends HttpClient<SecurityData
    * @name AddCatalogEntry
    * @request POST:/catalog
    */
-  addCatalogEntry = (data: CatalogDto, params: RequestParams = {}) =>
+  addCatalogEntry = (data: CatalogItemDto, params: RequestParams = {}) =>
     this.request<void, any>({
       path: `/catalog`,
       method: "POST",
diff --git a/frontend/src/swagger/CatalogItems.ts b/frontend/src/swagger/CatalogItems.ts
new file mode 100644
index 0000000..3e81a5b
--- /dev/null
+++ b/frontend/src/swagger/CatalogItems.ts
@@ -0,0 +1,77 @@
+/* eslint-disable */
+/* tslint:disable */
+/*
+ * ---------------------------------------------------------------
+ * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##
+ * ##                                                           ##
+ * ## AUTHOR: acacode                                           ##
+ * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
+ * ---------------------------------------------------------------
+ */
+
+import { CatalogItemDto } from "./data-contracts";
+import { ContentType, HttpClient, RequestParams } from "./http-client";
+
+export class CatalogItems<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
+  /**
+   * No description
+   *
+   * @tags catalog-controller
+   * @name UpdateCatalogItem
+   * @summary updates catalog item with given ID
+   * @request PUT:/catalog-items/{id}
+   */
+  updateCatalogItem = (id: string, data: CatalogItemDto, params: RequestParams = {}) =>
+    this.request<void, any>({
+      path: `/catalog-items/${id}`,
+      method: "PUT",
+      body: data,
+      type: ContentType.Json,
+      ...params,
+    });
+  /**
+   * No description
+   *
+   * @tags catalog-controller
+   * @name DeleteCatalogItem
+   * @summary deletes catalog item with given ID
+   * @request DELETE:/catalog-items/{id}
+   */
+  deleteCatalogItem = (id: string, params: RequestParams = {}) =>
+    this.request<void, any>({
+      path: `/catalog-items/${id}`,
+      method: "DELETE",
+      ...params,
+    });
+  /**
+   * No description
+   *
+   * @tags catalog-controller
+   * @name GetCatalog
+   * @summary returns catalog items based on filter
+   * @request GET:/catalog-items
+   */
+  getCatalog = (query?: { name?: string; country?: string; type?: string }, params: RequestParams = {}) =>
+    this.request<CatalogItemDto[], any>({
+      path: `/catalog-items`,
+      method: "GET",
+      query: query,
+      ...params,
+    });
+  /**
+   * No description
+   *
+   * @tags catalog-controller
+   * @name AddCatalogItem
+   * @summary creates new catalog item
+   * @request POST:/catalog-items
+   */
+  addCatalogItem = (data: CatalogItemDto, params: RequestParams = {}) =>
+    this.request<void, any>({
+      path: `/catalog-items`,
+      method: "POST",
+      body: data,
+      type: ContentType.Json,
+      ...params,
+    });
+}
diff --git a/frontend/src/swagger/Users.ts b/frontend/src/swagger/Users.ts
index de4349e..27d5c1b 100644
--- a/frontend/src/swagger/Users.ts
+++ b/frontend/src/swagger/Users.ts
@@ -9,8 +9,8 @@
  * ---------------------------------------------------------------
  */
 
-import { UserDto } from "./data-contracts";
-import { HttpClient, RequestParams } from "./http-client";
+import { PasswordDto, PermissionDto, UserDto } from "./data-contracts";
+import { ContentType, HttpClient, RequestParams } from "./http-client";
 
 export class Users<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
   /**
@@ -18,6 +18,7 @@ export class Users<SecurityDataType = unknown> extends HttpClient<SecurityDataTy
    *
    * @tags user-controller
    * @name GetAllUsers
+   * @summary returns all users
    * @request GET:/users
    */
   getAllUsers = (params: RequestParams = {}) =>
@@ -26,4 +27,97 @@ export class Users<SecurityDataType = unknown> extends HttpClient<SecurityDataTy
       method: "GET",
       ...params,
     });
+  /**
+   * No description
+   *
+   * @tags user-controller
+   * @name RegisterNewUser
+   * @summary registers new user
+   * @request POST:/users
+   */
+  registerNewUser = (data: UserDto, params: RequestParams = {}) =>
+    this.request<void, any>({
+      path: `/users`,
+      method: "POST",
+      body: data,
+      type: ContentType.Json,
+      ...params,
+    });
+  /**
+   * No description
+   *
+   * @tags user-controller
+   * @name UpdatePermissions
+   * @summary changes permissions to given user
+   * @request PATCH:/users/{username}/permissions
+   */
+  updatePermissions = (username: string, data: PermissionDto, params: RequestParams = {}) =>
+    this.request<void, any>({
+      path: `/users/${username}/permissions`,
+      method: "PATCH",
+      body: data,
+      type: ContentType.Json,
+      ...params,
+    });
+  /**
+   * No description
+   *
+   * @tags user-controller
+   * @name ResetPassword
+   * @summary changes password to given user
+   * @request PATCH:/users/{username}/password
+   */
+  resetPassword = (username: string, data: PasswordDto, params: RequestParams = {}) =>
+    this.request<void, any>({
+      path: `/users/${username}/password`,
+      method: "PATCH",
+      body: data,
+      type: ContentType.Json,
+      ...params,
+    });
+  /**
+   * No description
+   *
+   * @tags user-controller
+   * @name ChangePassword
+   * @summary changes password to logged-in user
+   * @request PATCH:/users/password
+   */
+  changePassword = (query: { oldPassword: string }, data: PasswordDto, params: RequestParams = {}) =>
+    this.request<void, any>({
+      path: `/users/password`,
+      method: "PATCH",
+      query: query,
+      body: data,
+      type: ContentType.Json,
+      ...params,
+    });
+  /**
+   * No description
+   *
+   * @tags user-controller
+   * @name RefreshToken
+   * @summary returns a new access token and a refresh token to user
+   * @request GET:/users/token
+   */
+  refreshToken = (params: RequestParams = {}) =>
+    this.request<void, any>({
+      path: `/users/token`,
+      method: "GET",
+      ...params,
+    });
+  /**
+   * No description
+   *
+   * @tags user-controller
+   * @name DeleteUser
+   * @summary deletes user with given username
+   * @request DELETE:/users/{username}
+   */
+  deleteUser = (username: string, params: RequestParams = {}) =>
+    this.request<void, any>({
+      path: `/users/${username}`,
+      method: "DELETE",
+      ...params,
+    });
 }
diff --git a/frontend/src/swagger/data-contracts.ts b/frontend/src/swagger/data-contracts.ts
index 41589a4..ac10c37 100644
--- a/frontend/src/swagger/data-contracts.ts
+++ b/frontend/src/swagger/data-contracts.ts
@@ -9,15 +9,7 @@
  * ---------------------------------------------------------------
  */
 
-export interface UserDto {
-  name?: string;
-  email: string;
-  canRead?: boolean;
-  canWrite?: boolean;
-  canDelete?: boolean;
-}
-
-export interface CatalogDto {
+export interface CatalogItemDto {
   /** @format uuid */
   id?: string;
   name?: string;
@@ -36,3 +28,26 @@ export interface CatalogDto {
   alternativeNames?: string[];
   types?: string[];
 }
+
+export interface PasswordDto {
+  password: string;
+  confirmationPassword: string;
+}
+
+export interface PermissionDto {
+  canRead?: boolean;
+  canWrite?: boolean;
+  canDelete?: boolean;
+}
+
+export interface UserDto {
+  name?: string;
+  email: string;
+  permissions?: PermissionDto;
+  passwords: PasswordDto;
+}
+
+export interface TitlePage {
+  title?: string;
+  content?: string;
+}
