commit b73954d12a5db3320c181f00399783635078a94e
Author: Vaclav Honzik <vaclavhonzik98@gmail.com>
Date:   Mon Mar 21 10:15:43 2022 +0100

    re #9196

diff --git a/frontend/package.json b/frontend/package.json
index 2fd6085..15a0eeb 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -16,6 +16,7 @@
     "@types/react-dom": "^17.0.9",
     "axios": "^0.26.0",
     "dotenv": "^16.0.0",
+    "formik": "^2.2.9",
     "react": "^17.0.2",
     "react-dom": "^17.0.2",
     "react-router-dom": "^6.2.2",
diff --git a/frontend/src/api/ApiClient.ts b/frontend/src/api/ApiClient.ts
deleted file mode 100644
index 3898f52..0000000
--- a/frontend/src/api/ApiClient.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import axios from 'axios'
-import Config from '../config/Config'
-
-const ApiClient = axios.create({
-    baseURL: Config.baseUrl
-})
\ No newline at end of file
diff --git a/frontend/src/api/axios.ts b/frontend/src/api/axios.ts
new file mode 100644
index 0000000..6ed8ce4
--- /dev/null
+++ b/frontend/src/api/axios.ts
@@ -0,0 +1,8 @@
+import axios from 'axios'
+import Config from '../config/Config'
+
+export default axios.create({
+    baseURL: Config.baseUrl,
+
+})
+
diff --git a/frontend/src/components/StubComponent.tsx b/frontend/src/components/StubComponent.tsx
deleted file mode 100644
index 80662c2..0000000
--- a/frontend/src/components/StubComponent.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-// Example component that connects to the backend API and fetches dummy data
-
-import { useState } from 'react'
-import AxiosClient from '../services/AxiosClient'
-
-const StubComponent = () => {
-
-    const [text, setText] = useState<string>('')
-
-    // Sends swagger request to the backend
-    const fetchStub = async () => {
-        try {
-            const { data } = await AxiosClient.get('/stub')
-            if (!!data && data.success) {
-                setText(data.message as string)
-            }
-        }
-        catch (err) {
-            console.log(`An error has occurred: ${err}`)
-        }
-    }
-
-    return (
-        <>
-            <h1>Welcome to the internet</h1>
-            <p>Click the button to fetch the message</p>
-            <p>The message: {text}</p>
-            <button onClick={fetchStub}>Fetch the message</button>
-        </>
-    )
-
-}
-
-export default StubComponent
\ No newline at end of file
diff --git a/frontend/src/features/Auth/AuthService.ts b/frontend/src/features/Auth/AuthService.ts
index 4fc47a8..f56df00 100644
--- a/frontend/src/features/Auth/AuthService.ts
+++ b/frontend/src/features/Auth/AuthService.ts
@@ -1,2 +1,33 @@
+import axios from "../../api/axios"
+import { UserDto } from "../../swagger/data-contracts"
+import ApiCallError from "../../utils/ApiCallError"
 
-export default  {}
\ No newline at end of file
+export const getAccessToken = () => localStorage.getItem('accessToken') as string | null
+export const getRefreshToken = () => localStorage.getItem('refreshToken') as string | null
+
+export const setAccessToken = (accessToken: string) => localStorage.setItem('accessToken', accessToken)
+export const setRefreshToken = (refreshToken: string) => localStorage.setItem('refreshToken', refreshToken)
+
+
+export const sendRegisterRequest = (userDto: UserDto) => {
+    
+}
+
+export const sendLoginRequest = async (username: string, password: string, setLoggedInState: (loggedIn: boolean) => void) => {
+    try {
+        const { data } = await axios.post('/login', {username, password})
+
+        if (!data) {
+            throw new ApiCallError("An authentication error has occurred. Please try again later")
+        }
+
+        // TODO - set state as logged in
+        const { accessToken, refreshToken } = data
+        setAccessToken(accessToken)
+        setRefreshToken(refreshToken)
+        setLoggedInState(true)
+    }
+    catch (err) {
+
+    }
+}
\ No newline at end of file
diff --git a/frontend/src/features/Auth/Login.tsx b/frontend/src/features/Auth/Login.tsx
new file mode 100644
index 0000000..7550e8f
--- /dev/null
+++ b/frontend/src/features/Auth/Login.tsx
@@ -0,0 +1,11 @@
+
+const Login = () => {
+
+    return (
+        <>
+
+        </>
+    )
+}
+
+export default Login
\ No newline at end of file
diff --git a/frontend/src/features/Auth/Register.tsx b/frontend/src/features/Auth/Register.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/src/features/Catalog/CatalogItemDetail.tsx b/frontend/src/features/Catalog/CatalogItemDetail.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/src/features/Catalog/CatalogService.ts b/frontend/src/features/Catalog/CatalogService.ts
new file mode 100644
index 0000000..d5ecc33
--- /dev/null
+++ b/frontend/src/features/Catalog/CatalogService.ts
@@ -0,0 +1,15 @@
+import axios from "../../api/axios"
+import { CatalogDto } from "../../swagger/data-contracts"
+
+export const getCatalogItems = async (componentMounted: boolean, setCatalog: (catalogItems: CatalogDto[]) => void, ) => {
+    try {
+        const { data } = await axios.get('/catalog')
+    }
+    catch (err: any) {
+
+    }
+}
+
+export const getCatalogItem = (id: string) => {
+
+}
\ No newline at end of file
diff --git a/frontend/src/scripts/GenerateDtosFromSwagger.ts b/frontend/src/scripts/GenerateDtosFromSwagger.ts
deleted file mode 100644
index 38ca083..0000000
--- a/frontend/src/scripts/GenerateDtosFromSwagger.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { generateApi } from 'swagger-typescript-api'
-import Config from '../config/Config'
-
-await generateApi({
-    name: 'apiDefinitions.ts',
-    output: '../swagger',
-    generateUnionEnums: true,
-    url: Config.baseUrl,
-    prettier: {
-        printWidth: 100,
-        trailingComma: "es5",
-        singleQuote: true,
-        semi: true,
-        arrowParens: "avoid",
-        tabWidth: 2,
-        parser: "typescript",
-    }
-})
diff --git a/frontend/src/services/AxiosClient.ts b/frontend/src/services/AxiosClient.ts
deleted file mode 100644
index 9cb88b1..0000000
--- a/frontend/src/services/AxiosClient.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import axios from 'axios'
-import Config from '../config/Config'
-
-export default axios.create({
-    baseURL: Config.baseUrl,
-})
\ No newline at end of file
diff --git a/frontend/src/swagger/Catalog.ts b/frontend/src/swagger/Catalog.ts
new file mode 100644
index 0000000..43b7bf7
--- /dev/null
+++ b/frontend/src/swagger/Catalog.ts
@@ -0,0 +1,72 @@
+/* eslint-disable */
+/* tslint:disable */
+/*
+ * ---------------------------------------------------------------
+ * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##
+ * ##                                                           ##
+ * ## AUTHOR: acacode                                           ##
+ * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
+ * ---------------------------------------------------------------
+ */
+
+import { CatalogDto } from "./data-contracts";
+import { ContentType, HttpClient, RequestParams } from "./http-client";
+
+export class Catalog<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
+  /**
+   * No description
+   *
+   * @tags catalog-controller
+   * @name UpdateCatalogEntry
+   * @request PUT:/catalog/{id}
+   */
+  updateCatalogEntry = (id: string, data: CatalogDto, params: RequestParams = {}) =>
+    this.request<void, any>({
+      path: `/catalog/${id}`,
+      method: "PUT",
+      body: data,
+      type: ContentType.Json,
+      ...params,
+    });
+  /**
+   * No description
+   *
+   * @tags catalog-controller
+   * @name DeleteCatalogEntry
+   * @request DELETE:/catalog/{id}
+   */
+  deleteCatalogEntry = (id: string, params: RequestParams = {}) =>
+    this.request<void, any>({
+      path: `/catalog/${id}`,
+      method: "DELETE",
+      ...params,
+    });
+  /**
+   * No description
+   *
+   * @tags catalog-controller
+   * @name GetAllUsers1
+   * @request GET:/catalog
+   */
+  getAllUsers1 = (params: RequestParams = {}) =>
+    this.request<CatalogDto[], any>({
+      path: `/catalog`,
+      method: "GET",
+      ...params,
+    });
+  /**
+   * No description
+   *
+   * @tags catalog-controller
+   * @name AddCatalogEntry
+   * @request POST:/catalog
+   */
+  addCatalogEntry = (data: CatalogDto, params: RequestParams = {}) =>
+    this.request<void, any>({
+      path: `/catalog`,
+      method: "POST",
+      body: data,
+      type: ContentType.Json,
+      ...params,
+    });
+}
diff --git a/frontend/src/swagger/Register.ts b/frontend/src/swagger/Register.ts
new file mode 100644
index 0000000..8039706
--- /dev/null
+++ b/frontend/src/swagger/Register.ts
@@ -0,0 +1,31 @@
+/* eslint-disable */
+/* tslint:disable */
+/*
+ * ---------------------------------------------------------------
+ * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##
+ * ##                                                           ##
+ * ## AUTHOR: acacode                                           ##
+ * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
+ * ---------------------------------------------------------------
+ */
+
+import { UserDto } from "./data-contracts";
+import { ContentType, HttpClient, RequestParams } from "./http-client";
+
+export class Register<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
+  /**
+   * No description
+   *
+   * @tags user-controller
+   * @name RegisterNewUser
+   * @request POST:/register
+   */
+  registerNewUser = (data: UserDto, params: RequestParams = {}) =>
+    this.request<void, any>({
+      path: `/register`,
+      method: "POST",
+      body: data,
+      type: ContentType.Json,
+      ...params,
+    });
+}
diff --git a/frontend/src/swagger/Token.ts b/frontend/src/swagger/Token.ts
new file mode 100644
index 0000000..823b432
--- /dev/null
+++ b/frontend/src/swagger/Token.ts
@@ -0,0 +1,28 @@
+/* eslint-disable */
+/* tslint:disable */
+/*
+ * ---------------------------------------------------------------
+ * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##
+ * ##                                                           ##
+ * ## AUTHOR: acacode                                           ##
+ * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
+ * ---------------------------------------------------------------
+ */
+
+import { HttpClient, RequestParams } from "./http-client";
+
+export class Token<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
+  /**
+   * No description
+   *
+   * @tags user-controller
+   * @name RefreshToken
+   * @request GET:/token/refresh
+   */
+  refreshToken = (params: RequestParams = {}) =>
+    this.request<void, any>({
+      path: `/token/refresh`,
+      method: "GET",
+      ...params,
+    });
+}
diff --git a/frontend/src/swagger/User.ts b/frontend/src/swagger/User.ts
new file mode 100644
index 0000000..75281e2
--- /dev/null
+++ b/frontend/src/swagger/User.ts
@@ -0,0 +1,44 @@
+/* eslint-disable */
+/* tslint:disable */
+/*
+ * ---------------------------------------------------------------
+ * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##
+ * ##                                                           ##
+ * ## AUTHOR: acacode                                           ##
+ * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
+ * ---------------------------------------------------------------
+ */
+
+import { UserDto } from "./data-contracts";
+import { ContentType, HttpClient, RequestParams } from "./http-client";
+
+export class User<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
+  /**
+   * No description
+   *
+   * @tags user-controller
+   * @name UpdateUser
+   * @request PUT:/user/{username}
+   */
+  updateUser = (username: string, data: UserDto, params: RequestParams = {}) =>
+    this.request<void, any>({
+      path: `/user/${username}`,
+      method: "PUT",
+      body: data,
+      type: ContentType.Json,
+      ...params,
+    });
+  /**
+   * No description
+   *
+   * @tags user-controller
+   * @name DeleteUser
+   * @request DELETE:/user/{username}
+   */
+  deleteUser = (username: string, params: RequestParams = {}) =>
+    this.request<void, any>({
+      path: `/user/${username}`,
+      method: "DELETE",
+      ...params,
+    });
+}
diff --git a/frontend/src/swagger/Users.ts b/frontend/src/swagger/Users.ts
new file mode 100644
index 0000000..de4349e
--- /dev/null
+++ b/frontend/src/swagger/Users.ts
@@ -0,0 +1,29 @@
+/* eslint-disable */
+/* tslint:disable */
+/*
+ * ---------------------------------------------------------------
+ * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##
+ * ##                                                           ##
+ * ## AUTHOR: acacode                                           ##
+ * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
+ * ---------------------------------------------------------------
+ */
+
+import { UserDto } from "./data-contracts";
+import { HttpClient, RequestParams } from "./http-client";
+
+export class Users<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
+  /**
+   * No description
+   *
+   * @tags user-controller
+   * @name GetAllUsers
+   * @request GET:/users
+   */
+  getAllUsers = (params: RequestParams = {}) =>
+    this.request<UserDto[], any>({
+      path: `/users`,
+      method: "GET",
+      ...params,
+    });
+}
diff --git a/frontend/src/swagger/data-contracts.ts b/frontend/src/swagger/data-contracts.ts
new file mode 100644
index 0000000..41589a4
--- /dev/null
+++ b/frontend/src/swagger/data-contracts.ts
@@ -0,0 +1,38 @@
+/* eslint-disable */
+/* tslint:disable */
+/*
+ * ---------------------------------------------------------------
+ * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##
+ * ##                                                           ##
+ * ## AUTHOR: acacode                                           ##
+ * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
+ * ---------------------------------------------------------------
+ */
+
+export interface UserDto {
+  name?: string;
+  email: string;
+  canRead?: boolean;
+  canWrite?: boolean;
+  canDelete?: boolean;
+}
+
+export interface CatalogDto {
+  /** @format uuid */
+  id?: string;
+  name?: string;
+
+  /** @format int32 */
+  certainty?: number;
+
+  /** @format double */
+  longitude?: number;
+
+  /** @format double */
+  latitude?: number;
+  bibliography?: string[];
+  countries?: string[];
+  writtenForms?: string[];
+  alternativeNames?: string[];
+  types?: string[];
+}
diff --git a/frontend/src/swagger/http-client.ts b/frontend/src/swagger/http-client.ts
new file mode 100644
index 0000000..ab33905
--- /dev/null
+++ b/frontend/src/swagger/http-client.ts
@@ -0,0 +1,218 @@
+/* eslint-disable */
+/* tslint:disable */
+/*
+ * ---------------------------------------------------------------
+ * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##
+ * ##                                                           ##
+ * ## AUTHOR: acacode                                           ##
+ * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
+ * ---------------------------------------------------------------
+ */
+
+export type QueryParamsType = Record<string | number, any>;
+export type ResponseFormat = keyof Omit<Body, "body" | "bodyUsed">;
+
+export interface FullRequestParams extends Omit<RequestInit, "body"> {
+  /** set parameter to `true` for call `securityWorker` for this request */
+  secure?: boolean;
+  /** request path */
+  path: string;
+  /** content type of request body */
+  type?: ContentType;
+  /** query params */
+  query?: QueryParamsType;
+  /** format of response (i.e. response.json() -> format: "json") */
+  format?: ResponseFormat;
+  /** request body */
+  body?: unknown;
+  /** base url */
+  baseUrl?: string;
+  /** request cancellation token */
+  cancelToken?: CancelToken;
+}
+
+export type RequestParams = Omit<FullRequestParams, "body" | "method" | "query" | "path">;
+
+export interface ApiConfig<SecurityDataType = unknown> {
+  baseUrl?: string;
+  baseApiParams?: Omit<RequestParams, "baseUrl" | "cancelToken" | "signal">;
+  securityWorker?: (securityData: SecurityDataType | null) => Promise<RequestParams | void> | RequestParams | void;
+  customFetch?: typeof fetch;
+}
+
+export interface HttpResponse<D extends unknown, E extends unknown = unknown> extends Response {
+  data: D;
+  error: E;
+}
+
+type CancelToken = Symbol | string | number;
+
+export enum ContentType {
+  Json = "application/json",
+  FormData = "multipart/form-data",
+  UrlEncoded = "application/x-www-form-urlencoded",
+}
+
+export class HttpClient<SecurityDataType = unknown> {
+  public baseUrl: string = "http://localhost:8080";
+  private securityData: SecurityDataType | null = null;
+  private securityWorker?: ApiConfig<SecurityDataType>["securityWorker"];
+  private abortControllers = new Map<CancelToken, AbortController>();
+  private customFetch = (...fetchParams: Parameters<typeof fetch>) => fetch(...fetchParams);
+
+  private baseApiParams: RequestParams = {
+    credentials: "same-origin",
+    headers: {},
+    redirect: "follow",
+    referrerPolicy: "no-referrer",
+  };
+
+  constructor(apiConfig: ApiConfig<SecurityDataType> = {}) {
+    Object.assign(this, apiConfig);
+  }
+
+  public setSecurityData = (data: SecurityDataType | null) => {
+    this.securityData = data;
+  };
+
+  private encodeQueryParam(key: string, value: any) {
+    const encodedKey = encodeURIComponent(key);
+    return `${encodedKey}=${encodeURIComponent(typeof value === "number" ? value : `${value}`)}`;
+  }
+
+  private addQueryParam(query: QueryParamsType, key: string) {
+    return this.encodeQueryParam(key, query[key]);
+  }
+
+  private addArrayQueryParam(query: QueryParamsType, key: string) {
+    const value = query[key];
+    return value.map((v: any) => this.encodeQueryParam(key, v)).join("&");
+  }
+
+  protected toQueryString(rawQuery?: QueryParamsType): string {
+    const query = rawQuery || {};
+    const keys = Object.keys(query).filter((key) => "undefined" !== typeof query[key]);
+    return keys
+      .map((key) => (Array.isArray(query[key]) ? this.addArrayQueryParam(query, key) : this.addQueryParam(query, key)))
+      .join("&");
+  }
+
+  protected addQueryParams(rawQuery?: QueryParamsType): string {
+    const queryString = this.toQueryString(rawQuery);
+    return queryString ? `?${queryString}` : "";
+  }
+
+  private contentFormatters: Record<ContentType, (input: any) => any> = {
+    [ContentType.Json]: (input: any) =>
+      input !== null && (typeof input === "object" || typeof input === "string") ? JSON.stringify(input) : input,
+    [ContentType.FormData]: (input: any) =>
+      Object.keys(input || {}).reduce((formData, key) => {
+        const property = input[key];
+        formData.append(
+          key,
+          property instanceof Blob
+            ? property
+            : typeof property === "object" && property !== null
+            ? JSON.stringify(property)
+            : `${property}`,
+        );
+        return formData;
+      }, new FormData()),
+    [ContentType.UrlEncoded]: (input: any) => this.toQueryString(input),
+  };
+
+  private mergeRequestParams(params1: RequestParams, params2?: RequestParams): RequestParams {
+    return {
+      ...this.baseApiParams,
+      ...params1,
+      ...(params2 || {}),
+      headers: {
+        ...(this.baseApiParams.headers || {}),
+        ...(params1.headers || {}),
+        ...((params2 && params2.headers) || {}),
+      },
+    };
+  }
+
+  private createAbortSignal = (cancelToken: CancelToken): AbortSignal | undefined => {
+    if (this.abortControllers.has(cancelToken)) {
+      const abortController = this.abortControllers.get(cancelToken);
+      if (abortController) {
+        return abortController.signal;
+      }
+      return void 0;
+    }
+
+    const abortController = new AbortController();
+    this.abortControllers.set(cancelToken, abortController);
+    return abortController.signal;
+  };
+
+  public abortRequest = (cancelToken: CancelToken) => {
+    const abortController = this.abortControllers.get(cancelToken);
+
+    if (abortController) {
+      abortController.abort();
+      this.abortControllers.delete(cancelToken);
+    }
+  };
+
+  public request = async <T = any, E = any>({
+    body,
+    secure,
+    path,
+    type,
+    query,
+    format,
+    baseUrl,
+    cancelToken,
+    ...params
+  }: FullRequestParams): Promise<HttpResponse<T, E>> => {
+    const secureParams =
+      ((typeof secure === "boolean" ? secure : this.baseApiParams.secure) &&
+        this.securityWorker &&
+        (await this.securityWorker(this.securityData))) ||
+      {};
+    const requestParams = this.mergeRequestParams(params, secureParams);
+    const queryString = query && this.toQueryString(query);
+    const payloadFormatter = this.contentFormatters[type || ContentType.Json];
+    const responseFormat = format || requestParams.format;
+
+    return this.customFetch(`${baseUrl || this.baseUrl || ""}${path}${queryString ? `?${queryString}` : ""}`, {
+      ...requestParams,
+      headers: {
+        ...(type && type !== ContentType.FormData ? { "Content-Type": type } : {}),
+        ...(requestParams.headers || {}),
+      },
+      signal: cancelToken ? this.createAbortSignal(cancelToken) : void 0,
+      body: typeof body === "undefined" || body === null ? null : payloadFormatter(body),
+    }).then(async (response) => {
+      const r = response as HttpResponse<T, E>;
+      r.data = null as unknown as T;
+      r.error = null as unknown as E;
+
+      const data = !responseFormat
+        ? r
+        : await response[responseFormat]()
+            .then((data) => {
+              if (r.ok) {
+                r.data = data;
+              } else {
+                r.error = data;
+              }
+              return r;
+            })
+            .catch((e) => {
+              r.error = e;
+              return r;
+            });
+
+      if (cancelToken) {
+        this.abortControllers.delete(cancelToken);
+      }
+
+      if (!response.ok) throw data;
+      return data;
+    });
+  };
+}
diff --git a/frontend/src/utils/ApiCallError.ts b/frontend/src/utils/ApiCallError.ts
new file mode 100644
index 0000000..b3cf28b
--- /dev/null
+++ b/frontend/src/utils/ApiCallError.ts
@@ -0,0 +1 @@
+export default class ApiCallError extends Error {}
