diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4450c01a894a1f0f4f582edb67019753499d9332
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,20 @@
+stages:
+  - install
+  - build
+
+cache:
+  paths:
+    - node_modules/
+  key: "$CI_BUILD_REF_NAME" # Separate cache for each branch
+
+install_dependencies:
+  stage: install
+  image: node:latest
+  script:
+    - npm install
+
+build_project:
+  stage: build
+  image: node:latest
+  script:
+    - npm run build
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 969552f258803cb182fd8f4a5cff36ae102f2fcb..380793cab4d9aec8226d36377a5a0f5f9ef907f5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,10 +9,14 @@
       "version": "0.0.0",
       "dependencies": {
         "@popperjs/core": "^2.11.8",
+        "axios": "^1.6.8",
         "bootstrap": "^5.3.3",
+        "js-cookie": "^3.0.5",
+        "oh-vue-icons": "^1.0.0-rc3",
         "pinia": "^2.1.7",
         "vue": "^3.4.21",
-        "vue-router": "^4.3.0"
+        "vue-router": "^4.3.0",
+        "xml2js": "^0.6.2"
       },
       "devDependencies": {
         "@rushstack/eslint-patch": "^1.8.0",
@@ -2657,8 +2661,7 @@
     "node_modules/asynckit": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
-      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
-      "dev": true
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
     },
     "node_modules/at-least-node": {
       "version": "1.0.0",
@@ -2688,7 +2691,6 @@
       "version": "1.6.8",
       "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz",
       "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==",
-      "dev": true,
       "dependencies": {
         "follow-redirects": "^1.15.6",
         "form-data": "^4.0.0",
@@ -2699,7 +2701,6 @@
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
       "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
-      "dev": true,
       "dependencies": {
         "asynckit": "^0.4.0",
         "combined-stream": "^1.0.8",
@@ -2712,8 +2713,7 @@
     "node_modules/axios/node_modules/proxy-from-env": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
-      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
-      "dev": true
+      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
     },
     "node_modules/balanced-match": {
       "version": "1.0.2",
@@ -3116,7 +3116,6 @@
       "version": "1.0.8",
       "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
       "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
-      "dev": true,
       "dependencies": {
         "delayed-stream": "~1.0.0"
       },
@@ -3539,7 +3538,6 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
       "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
-      "dev": true,
       "engines": {
         "node": ">=0.4.0"
       }
@@ -4486,7 +4484,6 @@
       "version": "1.15.6",
       "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
       "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
-      "dev": true,
       "funding": [
         {
           "type": "individual",
@@ -5248,7 +5245,6 @@
       "version": "3.0.5",
       "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
       "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
-      "dev": true,
       "engines": {
         "node": ">=14"
       }
@@ -5843,7 +5839,6 @@
       "version": "1.52.0",
       "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
       "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
-      "dev": true,
       "engines": {
         "node": ">= 0.6"
       }
@@ -5852,7 +5847,6 @@
       "version": "2.1.35",
       "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
       "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
-      "dev": true,
       "dependencies": {
         "mime-db": "1.52.0"
       },
@@ -6070,6 +6064,48 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/oh-vue-icons": {
+      "version": "1.0.0-rc3",
+      "resolved": "https://registry.npmjs.org/oh-vue-icons/-/oh-vue-icons-1.0.0-rc3.tgz",
+      "integrity": "sha512-+k2YC6piK7sEZnwbkQF3UokFPMmgqpiLP6f/H0ovQFLl20QA5V4U8EcI6EclD2Lt5NMQ3k6ilLGo8XyXqdVSvg==",
+      "dependencies": {
+        "vue-demi": "^0.12.5"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^2.0.0 || >=3.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/oh-vue-icons/node_modules/vue-demi": {
+      "version": "0.12.5",
+      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.12.5.tgz",
+      "integrity": "sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q==",
+      "hasInstallScript": true,
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^3.0.0-0 || ^2.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/once": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -6868,6 +6904,11 @@
       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
       "dev": true
     },
+    "node_modules/sax": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz",
+      "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA=="
+    },
     "node_modules/saxes": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
@@ -8567,6 +8608,26 @@
         "node": ">=12"
       }
     },
+    "node_modules/xml2js": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
+      "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
+      "dependencies": {
+        "sax": ">=0.6.0",
+        "xmlbuilder": "~11.0.0"
+      },
+      "engines": {
+        "node": ">=4.0.0"
+      }
+    },
+    "node_modules/xmlbuilder": {
+      "version": "11.0.1",
+      "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
+      "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
+      "engines": {
+        "node": ">=4.0"
+      }
+    },
     "node_modules/xmlchars": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
diff --git a/package.json b/package.json
index 8e38aa925053d6df7ce85891ec58823811d25d33..abf7692db221f6ba8f6bcb90e1978cafe97b4ee2 100644
--- a/package.json
+++ b/package.json
@@ -17,10 +17,14 @@
   },
   "dependencies": {
     "@popperjs/core": "^2.11.8",
+    "axios": "^1.6.8",
     "bootstrap": "^5.3.3",
+    "js-cookie": "^3.0.5",
+    "oh-vue-icons": "^1.0.0-rc3",
     "pinia": "^2.1.7",
     "vue": "^3.4.21",
-    "vue-router": "^4.3.0"
+    "vue-router": "^4.3.0",
+    "xml2js": "^0.6.2"
   },
   "devDependencies": {
     "@rushstack/eslint-patch": "^1.8.0",
diff --git a/src/App.vue b/src/App.vue
index 461e3d52db193794b9b516bce015595a8eeb985d..1a140ee58a2d942ddacb457b5fbd6f5b5ad23e0b 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,9 +1,18 @@
 <script setup lang="ts">
 import { RouterView } from 'vue-router'
+import ErrorBoundaryCatcher from '@/components/Exceptions/ErrorBoundaryCatcher.vue';
 </script>
 
 <template>
   <main>
-    <RouterView />
+    <error-boundary-catcher>
+      <RouterView />
+    </error-boundary-catcher>
   </main>
-</template>
\ No newline at end of file
+</template>
+
+<style>
+  main {
+    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+  }
+</style>
\ No newline at end of file
diff --git a/src/api/core/ApiError.ts b/src/api/core/ApiError.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ec7b16af6f41b1323a8e3aa3d529bf2324959e66
--- /dev/null
+++ b/src/api/core/ApiError.ts
@@ -0,0 +1,25 @@
+/* generated using openapi-typescript-codegen -- do not edit */
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+import type { ApiRequestOptions } from './ApiRequestOptions';
+import type { ApiResult } from './ApiResult';
+
+export class ApiError extends Error {
+    public readonly url: string;
+    public readonly status: number;
+    public readonly statusText: string;
+    public readonly body: any;
+    public readonly request: ApiRequestOptions;
+
+    constructor(request: ApiRequestOptions, response: ApiResult, message: string) {
+        super(message);
+
+        this.name = 'ApiError';
+        this.url = response.url;
+        this.status = response.status;
+        this.statusText = response.statusText;
+        this.body = response.body;
+        this.request = request;
+    }
+}
diff --git a/src/api/core/ApiRequestOptions.ts b/src/api/core/ApiRequestOptions.ts
new file mode 100644
index 0000000000000000000000000000000000000000..93143c3ce1ba5323894d4ac10299f62493f030f6
--- /dev/null
+++ b/src/api/core/ApiRequestOptions.ts
@@ -0,0 +1,17 @@
+/* generated using openapi-typescript-codegen -- do not edit */
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+export type ApiRequestOptions = {
+    readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH';
+    readonly url: string;
+    readonly path?: Record<string, any>;
+    readonly cookies?: Record<string, any>;
+    readonly headers?: Record<string, any>;
+    readonly query?: Record<string, any>;
+    readonly formData?: Record<string, any>;
+    readonly body?: any;
+    readonly mediaType?: string;
+    readonly responseHeader?: string;
+    readonly errors?: Record<number, string>;
+};
diff --git a/src/api/core/ApiResult.ts b/src/api/core/ApiResult.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ee1126e2ccd1e37dba97511c38c56a282ceac4dc
--- /dev/null
+++ b/src/api/core/ApiResult.ts
@@ -0,0 +1,11 @@
+/* generated using openapi-typescript-codegen -- do not edit */
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+export type ApiResult = {
+    readonly url: string;
+    readonly ok: boolean;
+    readonly status: number;
+    readonly statusText: string;
+    readonly body: any;
+};
diff --git a/src/api/core/CancelablePromise.ts b/src/api/core/CancelablePromise.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d70de92946d977e9da7970871375117a8b04770a
--- /dev/null
+++ b/src/api/core/CancelablePromise.ts
@@ -0,0 +1,131 @@
+/* generated using openapi-typescript-codegen -- do not edit */
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+export class CancelError extends Error {
+
+    constructor(message: string) {
+        super(message);
+        this.name = 'CancelError';
+    }
+
+    public get isCancelled(): boolean {
+        return true;
+    }
+}
+
+export interface OnCancel {
+    readonly isResolved: boolean;
+    readonly isRejected: boolean;
+    readonly isCancelled: boolean;
+
+    (cancelHandler: () => void): void;
+}
+
+export class CancelablePromise<T> implements Promise<T> {
+    #isResolved: boolean;
+    #isRejected: boolean;
+    #isCancelled: boolean;
+    readonly #cancelHandlers: (() => void)[];
+    readonly #promise: Promise<T>;
+    #resolve?: (value: T | PromiseLike<T>) => void;
+    #reject?: (reason?: any) => void;
+
+    constructor(
+        executor: (
+            resolve: (value: T | PromiseLike<T>) => void,
+            reject: (reason?: any) => void,
+            onCancel: OnCancel
+        ) => void
+    ) {
+        this.#isResolved = false;
+        this.#isRejected = false;
+        this.#isCancelled = false;
+        this.#cancelHandlers = [];
+        this.#promise = new Promise<T>((resolve, reject) => {
+            this.#resolve = resolve;
+            this.#reject = reject;
+
+            const onResolve = (value: T | PromiseLike<T>): void => {
+                if (this.#isResolved || this.#isRejected || this.#isCancelled) {
+                    return;
+                }
+                this.#isResolved = true;
+                if (this.#resolve) this.#resolve(value);
+            };
+
+            const onReject = (reason?: any): void => {
+                if (this.#isResolved || this.#isRejected || this.#isCancelled) {
+                    return;
+                }
+                this.#isRejected = true;
+                if (this.#reject) this.#reject(reason);
+            };
+
+            const onCancel = (cancelHandler: () => void): void => {
+                if (this.#isResolved || this.#isRejected || this.#isCancelled) {
+                    return;
+                }
+                this.#cancelHandlers.push(cancelHandler);
+            };
+
+            Object.defineProperty(onCancel, 'isResolved', {
+                get: (): boolean => this.#isResolved,
+            });
+
+            Object.defineProperty(onCancel, 'isRejected', {
+                get: (): boolean => this.#isRejected,
+            });
+
+            Object.defineProperty(onCancel, 'isCancelled', {
+                get: (): boolean => this.#isCancelled,
+            });
+
+            return executor(onResolve, onReject, onCancel as OnCancel);
+        });
+    }
+
+    get [Symbol.toStringTag]() {
+        return "Cancellable Promise";
+    }
+
+    public then<TResult1 = T, TResult2 = never>(
+        onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
+        onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
+    ): Promise<TResult1 | TResult2> {
+        return this.#promise.then(onFulfilled, onRejected);
+    }
+
+    public catch<TResult = never>(
+        onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
+    ): Promise<T | TResult> {
+        return this.#promise.catch(onRejected);
+    }
+
+    public finally(onFinally?: (() => void) | null): Promise<T> {
+        return this.#promise.finally(onFinally);
+    }
+
+    public cancel(): void {
+        if (this.#isResolved || this.#isRejected || this.#isCancelled) {
+            return;
+        }
+        this.#isCancelled = true;
+        if (this.#cancelHandlers.length) {
+            try {
+                for (const cancelHandler of this.#cancelHandlers) {
+                    cancelHandler();
+                }
+            } catch (error) {
+                console.warn('Cancellation threw an error', error);
+                return;
+            }
+        }
+        this.#cancelHandlers.length = 0;
+        if (this.#reject) this.#reject(new CancelError('Request aborted'));
+    }
+
+    public get isCancelled(): boolean {
+        return this.#isCancelled;
+    }
+}
diff --git a/src/api/core/OpenAPI.ts b/src/api/core/OpenAPI.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bae5eb773e38a6fa187a68f3ac9f3dbecc88eaf5
--- /dev/null
+++ b/src/api/core/OpenAPI.ts
@@ -0,0 +1,32 @@
+/* generated using openapi-typescript-codegen -- do not edit */
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+import type { ApiRequestOptions } from './ApiRequestOptions';
+
+type Resolver<T> = (options: ApiRequestOptions) => Promise<T>;
+type Headers = Record<string, string>;
+
+export type OpenAPIConfig = {
+    BASE: string;
+    VERSION: string;
+    WITH_CREDENTIALS: boolean;
+    CREDENTIALS: 'include' | 'omit' | 'same-origin';
+    TOKEN?: string | Resolver<string> | undefined;
+    USERNAME?: string | Resolver<string> | undefined;
+    PASSWORD?: string | Resolver<string> | undefined;
+    HEADERS?: Headers | Resolver<Headers> | undefined;
+    ENCODE_PATH?: ((path: string) => string) | undefined;
+};
+
+export const OpenAPI: OpenAPIConfig = {
+    BASE: 'http://localhost:8080',
+    VERSION: '0',
+    WITH_CREDENTIALS: false,
+    CREDENTIALS: 'include',
+    TOKEN: undefined,
+    USERNAME: undefined,
+    PASSWORD: undefined,
+    HEADERS: undefined,
+    ENCODE_PATH: undefined,
+};
diff --git a/src/api/core/request.ts b/src/api/core/request.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1dc6fef4aab4086ff57b48d26b20ec26bb8fa472
--- /dev/null
+++ b/src/api/core/request.ts
@@ -0,0 +1,323 @@
+/* generated using openapi-typescript-codegen -- do not edit */
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+import axios from 'axios';
+import type { AxiosError, AxiosRequestConfig, AxiosResponse, AxiosInstance } from 'axios';
+import FormData from 'form-data';
+
+import { ApiError } from './ApiError';
+import type { ApiRequestOptions } from './ApiRequestOptions';
+import type { ApiResult } from './ApiResult';
+import { CancelablePromise } from './CancelablePromise';
+import type { OnCancel } from './CancelablePromise';
+import type { OpenAPIConfig } from './OpenAPI';
+
+export const isDefined = <T>(value: T | null | undefined): value is Exclude<T, null | undefined> => {
+    return value !== undefined && value !== null;
+};
+
+export const isString = (value: any): value is string => {
+    return typeof value === 'string';
+};
+
+export const isStringWithValue = (value: any): value is string => {
+    return isString(value) && value !== '';
+};
+
+export const isBlob = (value: any): value is Blob => {
+    return (
+        typeof value === 'object' &&
+        typeof value.type === 'string' &&
+        typeof value.stream === 'function' &&
+        typeof value.arrayBuffer === 'function' &&
+        typeof value.constructor === 'function' &&
+        typeof value.constructor.name === 'string' &&
+        /^(Blob|File)$/.test(value.constructor.name) &&
+        /^(Blob|File)$/.test(value[Symbol.toStringTag])
+    );
+};
+
+export const isFormData = (value: any): value is FormData => {
+    return value instanceof FormData;
+};
+
+export const isSuccess = (status: number): boolean => {
+    return status >= 200 && status < 300;
+};
+
+export const base64 = (str: string): string => {
+    try {
+        return btoa(str);
+    } catch (err) {
+        // @ts-ignore
+        return Buffer.from(str).toString('base64');
+    }
+};
+
+export const getQueryString = (params: Record<string, any>): string => {
+    const qs: string[] = [];
+
+    const append = (key: string, value: any) => {
+        qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
+    };
+
+    const process = (key: string, value: any) => {
+        if (isDefined(value)) {
+            if (Array.isArray(value)) {
+                value.forEach(v => {
+                    process(key, v);
+                });
+            } else if (typeof value === 'object') {
+                Object.entries(value).forEach(([k, v]) => {
+                    process(`${key}[${k}]`, v);
+                });
+            } else {
+                append(key, value);
+            }
+        }
+    };
+
+    Object.entries(params).forEach(([key, value]) => {
+        process(key, value);
+    });
+
+    if (qs.length > 0) {
+        return `?${qs.join('&')}`;
+    }
+
+    return '';
+};
+
+const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => {
+    const encoder = config.ENCODE_PATH || encodeURI;
+
+    const path = options.url
+        .replace('{api-version}', config.VERSION)
+        .replace(/{(.*?)}/g, (substring: string, group: string) => {
+            if (options.path?.hasOwnProperty(group)) {
+                return encoder(String(options.path[group]));
+            }
+            return substring;
+        });
+
+    const url = `${config.BASE}${path}`;
+    if (options.query) {
+        return `${url}${getQueryString(options.query)}`;
+    }
+    return url;
+};
+
+export const getFormData = (options: ApiRequestOptions): FormData | undefined => {
+    if (options.formData) {
+        const formData = new FormData();
+
+        const process = (key: string, value: any) => {
+            if (isString(value) || isBlob(value)) {
+                formData.append(key, value);
+            } else {
+                formData.append(key, JSON.stringify(value));
+            }
+        };
+
+        Object.entries(options.formData)
+            .filter(([_, value]) => isDefined(value))
+            .forEach(([key, value]) => {
+                if (Array.isArray(value)) {
+                    value.forEach(v => process(key, v));
+                } else {
+                    process(key, value);
+                }
+            });
+
+        return formData;
+    }
+    return undefined;
+};
+
+type Resolver<T> = (options: ApiRequestOptions) => Promise<T>;
+
+export const resolve = async <T>(options: ApiRequestOptions, resolver?: T | Resolver<T>): Promise<T | undefined> => {
+    if (typeof resolver === 'function') {
+        return (resolver as Resolver<T>)(options);
+    }
+    return resolver;
+};
+
+export const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptions, formData?: FormData): Promise<Record<string, string>> => {
+    const [token, username, password, additionalHeaders] = await Promise.all([
+        resolve(options, config.TOKEN),
+        resolve(options, config.USERNAME),
+        resolve(options, config.PASSWORD),
+        resolve(options, config.HEADERS),
+    ]);
+
+    const formHeaders = typeof formData?.getHeaders === 'function' && formData?.getHeaders() || {}
+
+    const headers = Object.entries({
+        Accept: 'application/json',
+        ...additionalHeaders,
+        ...options.headers,
+        ...formHeaders,
+    })
+    .filter(([_, value]) => isDefined(value))
+    .reduce((headers, [key, value]) => ({
+        ...headers,
+        [key]: String(value),
+    }), {} as Record<string, string>);
+
+    if (isStringWithValue(token)) {
+        headers['Authorization'] = `Bearer ${token}`;
+    }
+
+    if (isStringWithValue(username) && isStringWithValue(password)) {
+        const credentials = base64(`${username}:${password}`);
+        headers['Authorization'] = `Basic ${credentials}`;
+    }
+
+    if (options.body !== undefined) {
+        if (options.mediaType) {
+            headers['Content-Type'] = options.mediaType;
+        } else if (isBlob(options.body)) {
+            headers['Content-Type'] = options.body.type || 'application/octet-stream';
+        } else if (isString(options.body)) {
+            headers['Content-Type'] = 'text/plain';
+        } else if (!isFormData(options.body)) {
+            headers['Content-Type'] = 'application/json';
+        }
+    }
+
+    return headers;
+};
+
+export const getRequestBody = (options: ApiRequestOptions): any => {
+    if (options.body) {
+        return options.body;
+    }
+    return undefined;
+};
+
+export const sendRequest = async <T>(
+    config: OpenAPIConfig,
+    options: ApiRequestOptions,
+    url: string,
+    body: any,
+    formData: FormData | undefined,
+    headers: Record<string, string>,
+    onCancel: OnCancel,
+    axiosClient: AxiosInstance
+): Promise<AxiosResponse<T>> => {
+    const source = axios.CancelToken.source();
+
+    const requestConfig: AxiosRequestConfig = {
+        url,
+        headers,
+        data: body ?? formData,
+        method: options.method,
+        withCredentials: config.WITH_CREDENTIALS,
+        withXSRFToken: config.CREDENTIALS === 'include' ? config.WITH_CREDENTIALS : false,
+        cancelToken: source.token,
+    };
+
+    onCancel(() => source.cancel('The user aborted a request.'));
+
+    try {
+        return await axiosClient.request(requestConfig);
+    } catch (error) {
+        const axiosError = error as AxiosError<T>;
+        if (axiosError.response) {
+            return axiosError.response;
+        }
+        throw error;
+    }
+};
+
+export const getResponseHeader = (response: AxiosResponse<any>, responseHeader?: string): string | undefined => {
+    if (responseHeader) {
+        const content = response.headers[responseHeader];
+        if (isString(content)) {
+            return content;
+        }
+    }
+    return undefined;
+};
+
+export const getResponseBody = (response: AxiosResponse<any>): any => {
+    if (response.status !== 204) {
+        return response.data;
+    }
+    return undefined;
+};
+
+export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => {
+    const errors: Record<number, string> = {
+        400: 'Bad Request',
+        401: 'Unauthorized',
+        403: 'Forbidden',
+        404: 'Not Found',
+        500: 'Internal Server Error',
+        502: 'Bad Gateway',
+        503: 'Service Unavailable',
+        ...options.errors,
+    }
+
+    const error = errors[result.status];
+    if (error) {
+        throw new ApiError(options, result, error);
+    }
+
+    if (!result.ok) {
+        const errorStatus = result.status ?? 'unknown';
+        const errorStatusText = result.statusText ?? 'unknown';
+        const errorBody = (() => {
+            try {
+                return JSON.stringify(result.body, null, 2);
+            } catch (e) {
+                return undefined;
+            }
+        })();
+
+        throw new ApiError(options, result,
+            `Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}`
+        );
+    }
+};
+
+/**
+ * Request method
+ * @param config The OpenAPI configuration object
+ * @param options The request options from the service
+ * @param axiosClient The axios client instance to use
+ * @returns CancelablePromise<T>
+ * @throws ApiError
+ */
+export const request = <T>(config: OpenAPIConfig, options: ApiRequestOptions, axiosClient: AxiosInstance = axios): CancelablePromise<T> => {
+    return new CancelablePromise(async (resolve, reject, onCancel) => {
+        try {
+            const url = getUrl(config, options);
+            const formData = getFormData(options);
+            const body = getRequestBody(options);
+            const headers = await getHeaders(config, options, formData);
+
+            if (!onCancel.isCancelled) {
+                const response = await sendRequest<T>(config, options, url, body, formData, headers, onCancel, axiosClient);
+                const responseBody = getResponseBody(response);
+                const responseHeader = getResponseHeader(response, options.responseHeader);
+
+                const result: ApiResult = {
+                    url,
+                    ok: isSuccess(response.status),
+                    status: response.status,
+                    statusText: response.statusText,
+                    body: responseHeader ?? responseBody,
+                };
+
+                catchErrorCodes(options, result);
+
+                resolve(result.body);
+            }
+        } catch (error) {
+            reject(error);
+        }
+    });
+};
diff --git a/src/api/index.ts b/src/api/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7bfbd7a82dcdde4a855e2ee242508c2268633a83
--- /dev/null
+++ b/src/api/index.ts
@@ -0,0 +1,8 @@
+/* generated using openapi-typescript-codegen -- do not edit */
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+export { ApiError } from './core/ApiError';
+export { CancelablePromise, CancelError } from './core/CancelablePromise';
+export { OpenAPI } from './core/OpenAPI';
+export type { OpenAPIConfig } from './core/OpenAPI';
diff --git a/src/assets/icons/danger.svg b/src/assets/icons/danger.svg
new file mode 100644
index 0000000000000000000000000000000000000000..609fd17d0541b7258e1374e6c7422b8ee3e696d9
--- /dev/null
+++ b/src/assets/icons/danger.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="m40-120 440-760 440 760H40Zm104-60h672L480-760 144-180Zm340.175-57q12.825 0 21.325-8.675 8.5-8.676 8.5-21.5 0-12.825-8.675-21.325-8.676-8.5-21.5-8.5-12.825 0-21.325 8.675-8.5 8.676-8.5 21.5 0 12.825 8.675 21.325 8.676 8.5 21.5 8.5ZM454-348h60v-224h-60v224Zm26-122Z" fill="#ffffff"/></svg>
\ No newline at end of file
diff --git a/src/components/Buttons/Button1.vue b/src/components/Buttons/Button1.vue
new file mode 100644
index 0000000000000000000000000000000000000000..33b300d7139220c9388160810541bafdf579332e
--- /dev/null
+++ b/src/components/Buttons/Button1.vue
@@ -0,0 +1,17 @@
+<template>
+    <button type="button" class="btn btn-success" id="buttonStyle">{{ buttonText }}</button>
+</template>
+
+<script>
+export default {
+    props: ['buttonText']
+}
+</script>
+
+<style scoped>
+    #buttonStyle {
+        padding: 0.5rem 4rem;
+        font-size: 1.5rem;
+        font-weight: 600;
+    }
+</style>
diff --git a/src/components/Exceptions/ErrorBoundaryCatcher.vue b/src/components/Exceptions/ErrorBoundaryCatcher.vue
new file mode 100644
index 0000000000000000000000000000000000000000..3bc046fb271f7d3b3ce3acee559329d18dce53bb
--- /dev/null
+++ b/src/components/Exceptions/ErrorBoundaryCatcher.vue
@@ -0,0 +1,19 @@
+<template>
+    <error-box :error-message="errorStore.getFirstError" @update:errorMessage="errorStore.removeCurrentError" />
+    <slot />
+  </template>
+  
+  <script setup lang="ts">
+  import { onErrorCaptured } from 'vue';
+  import { useErrorStore } from '@/stores/ErrorStore';
+  import ErrorBox from '@/components/Exceptions/ErrorBox.vue';
+  import handleUnknownError from '@/components/Exceptions/unkownErrorHandler';
+  
+  const errorStore = useErrorStore();
+  
+  onErrorCaptured((err, _vm, _info): boolean => {
+    const message = handleUnknownError(err.message);
+    errorStore.addError(message); //If no openAPI axios error, use err.message
+    return false;
+  });
+  </script>
\ No newline at end of file
diff --git a/src/components/Exceptions/ErrorBox.vue b/src/components/Exceptions/ErrorBox.vue
new file mode 100644
index 0000000000000000000000000000000000000000..f6dbe0d4adfb07e7ae1b4720ecf322b08fa3de71
--- /dev/null
+++ b/src/components/Exceptions/ErrorBox.vue
@@ -0,0 +1,90 @@
+<template>
+    <div class="error-box" v-if="errorMessage" data-testid="error-box">
+      <span>
+        <v-icon name="bi-exclamation-triangle" />
+        <button @click="wrapText = !wrapText" class="error-message-button">
+          <h4>{{ errorMessage }}</h4>
+        </button>
+        <button class="error-remove-button" @click="$emit('update:errorMessage', '')" data-testid="hide-button">
+          <v-icon scale="2" name="bi-dash" />
+        </button>
+      </span>
+    </div>
+  </template>
+  
+  <script lang="ts">
+  import { defineComponent } from 'vue';
+  import { OhVueIcon, addIcons } from 'oh-vue-icons';
+  import { BiDash, BiExclamationTriangle } from 'oh-vue-icons/icons';
+  
+  addIcons(BiDash, BiExclamationTriangle);
+  
+  export default defineComponent({
+    components: {
+      'v-icon': OhVueIcon,
+    },
+    props: {
+      errorMessage: {
+        type: String,
+        default: '',
+      },
+    },
+    data() {
+      return {
+        wrapText: false,
+      };
+    },
+  });
+  </script>
+  
+  <style scoped>
+  .error-box {
+    position: fixed;
+    top: 2em;
+    left: 50%;
+    transform: translate(-50%, 0);
+    width: min(100%, 700px);
+    background-color: var(--red-color);
+    padding: 7px;
+    border-radius: 5px;
+    z-index: 1000;
+  }
+  
+  .error-box span {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
+  
+  .error-message-button {
+    white-space: v-bind('wrapText ? "normal" : "nowrap"');
+    overflow: hidden;
+    border: none;
+  }
+  
+  .error-message-button h4 {
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+  
+  .error-box * {
+    margin: 2px;
+  }
+  
+  .error-box button {
+    background-color: transparent;
+    box-shadow: none;
+    color: black;
+    cursor: pointer;
+  }
+  
+  .error-remove-button {
+    color: rgb(79, 77, 77);
+    border: solid black 0.5px;
+    padding: 0.3em;
+  }
+  
+  .error-box button:hover {
+    color: rgb(40, 38, 38);
+  }
+  </style>
\ No newline at end of file
diff --git a/src/components/Exceptions/unkownErrorHandler.ts b/src/components/Exceptions/unkownErrorHandler.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6f7b6d914edc88b03029a68b907f7ef45f05ee7e
--- /dev/null
+++ b/src/components/Exceptions/unkownErrorHandler.ts
@@ -0,0 +1,19 @@
+import { ApiError as BackendApiError } from '@/api';
+import { AxiosError } from 'axios';
+
+/**
+ * Finds the correct error message for the given error
+ * The message is then put into the error store
+ * which an error component can then display
+ * @param error The unknown error to handle
+ */
+const handleUnknownError = (error: any): string => {
+  if (error instanceof AxiosError) {
+    return error.code!!;
+  } else if (error instanceof BackendApiError) {
+    return error.body.detail ?? error.body;
+  }
+  return 'ContextErrorMessage';
+};
+
+export default handleUnknownError;
\ No newline at end of file
diff --git a/src/components/InputFields/BaseInput.vue b/src/components/InputFields/BaseInput.vue
new file mode 100644
index 0000000000000000000000000000000000000000..5558323b9a95e53825a222ac444c8a5ee5f8f23a
--- /dev/null
+++ b/src/components/InputFields/BaseInput.vue
@@ -0,0 +1,37 @@
+<script setup lang="ts">
+
+const props = defineProps({
+  label: {
+    type: String,
+    default: ""
+  },
+  type: {
+    type: String,
+    default: "text"
+  },
+  placeholder: {
+    type: String,
+    default: ""
+  },
+  inputId: {
+    type: String,
+    required: true
+  }
+});
+</script>
+
+<template>
+  <div>
+    <label :for="inputId">{{ label }}</label>
+    <input :type="props.type"
+           class="form-control"
+           :placeholder="props.placeholder"
+           :id="inputId" required />
+    <div class="invalid-feedback">Invalid {{ label }}</div>
+    <div class="valid-feedback">Correct {{ label }}</div>
+  </div>
+</template>
+
+<style scoped>
+
+</style>
\ No newline at end of file
diff --git a/src/components/Login/Login.vue b/src/components/Login/Login.vue
new file mode 100644
index 0000000000000000000000000000000000000000..25469a19d2cbd3c352545218fe480114ae8c6181
--- /dev/null
+++ b/src/components/Login/Login.vue
@@ -0,0 +1,13 @@
+<script setup lang="ts">
+import LoginForm from '@/components/Login/LoginForm.vue'
+</script>
+
+<template>
+  <div class="container-fluid">
+    <LoginForm/>
+  </div>
+</template>
+
+<style>
+
+</style>
\ No newline at end of file
diff --git a/src/components/Login/LoginForm.vue b/src/components/Login/LoginForm.vue
new file mode 100644
index 0000000000000000000000000000000000000000..6b0da2a71cf6b6b87f27c8feaff2ef259c951520
--- /dev/null
+++ b/src/components/Login/LoginForm.vue
@@ -0,0 +1,50 @@
+<script setup lang="ts">
+import BaseInput from '@/components/InputFields/BaseInput.vue'
+import Button1 from '@/components/Buttons/Button1.vue'
+
+const handleSubmit = () => {
+  alert("Expected to be logged in when backend are finished") // Todo remove this line
+}
+</script>
+
+<template>
+  <div class="container-fluid">
+    <form id="loginForm" @submit.prevent="handleSubmit">
+      <BaseInput id="usernameInput"
+                 input-id="username"
+                 type="text"
+                 label="Username"
+                 placeholder="Enter username"/>
+      <BaseInput id="passwordInput"
+                 input-id="password"
+                 type="password"
+                 label="Password"
+                 placeholder="Enter password"/>
+      <button1 id="confirmButton" @click="handleSubmit" button-text="Login"></button1>
+    </form>
+  </div>
+</template>
+
+<style scoped>
+.container-fluid {
+  height: 91vh;
+  display: grid;
+  justify-items: center;
+  align-items: center;
+}
+
+#usernameInput, #passwordInput, #confirmButton {
+  margin: 15px 0;
+}
+
+#confirmButton {
+  justify-content: center;
+}
+
+#loginForm {
+  display: flex;
+  flex-direction: column;
+  min-width: 280px;
+  width: 40%;
+}
+</style>
\ No newline at end of file
diff --git a/src/components/NewsComponents/NewsComponent.vue b/src/components/NewsComponents/NewsComponent.vue
new file mode 100644
index 0000000000000000000000000000000000000000..4fe743f4a3af6146dfb226d11e5ac842e27d0677
--- /dev/null
+++ b/src/components/NewsComponents/NewsComponent.vue
@@ -0,0 +1,106 @@
+<script lang="ts">
+export default {
+  data() {
+    return {
+      articles: []
+    };
+  },
+  mounted() {
+    this.fetchFinanceNews();
+    // Call fetchFinanceNews() every 5 minutes (300,000 milliseconds)
+    // Done so the user does not need to refresh for news to be updated
+    // Might remove for consistent reading
+    setInterval(this.fetchFinanceNews, 300000);
+  },
+  methods: {
+    async fetchFinanceNews() {
+      try {
+        const response = await fetch(
+            'https://newsapi.org/v2/everything?q=saving%20money&pageSize=10&apiKey=f092756b3b6b41369b047cb7ae980db5'
+        );
+        const data = await response.json();
+
+        //English articles, might want to translate to norwegian
+        this.articles = data.articles;
+
+
+      } catch (error) {
+        console.error('Error fetching saving money news:', error);
+      }
+    }
+  }
+};
+</script>
+
+
+<template>
+  <div class="center-box">
+    <div class="box">
+      <br>
+      <h1>Nyheter</h1>
+      <br>
+      <div v-for="(article, index) in articles" :key="index" class="article-container">
+        <div class="content">
+          <h3>{{ article.title }}</h3>
+          <p>{{ article.description }}</p>
+          <a :href="article.url" target="_blank">Read more</a>
+        </div>
+        <div class="image">
+          <img :src="article.urlToImage" alt="Article Image"/>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.center-box {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.box {
+  width:90%;
+}
+
+.article-container {
+  display: flex;
+  align-items: center;
+  margin-bottom: 30px;
+}
+
+.image {
+  flex: 1;
+  text-align: center;
+  padding: 0 20px;
+}
+
+.image img {
+  max-width: 100%;
+  border-radius: 1em;
+}
+
+.content {
+  flex: 3;
+  padding: 0 20px;
+}
+
+.content h3 {
+  margin-top: 0;
+}
+
+.content a {
+  display: inline-block;
+  padding: 10px 20px;
+  background-color: #007bff;
+  color: #fff;
+  text-decoration: none;
+  border-radius: 5px;
+}
+
+.content a:hover {
+  background-color: #0056b3;
+}
+
+</style>
\ No newline at end of file
diff --git a/src/components/__tests__/HelloWorld.spec.ts b/src/components/__tests__/HelloWorld.spec.ts
deleted file mode 100644
index 2533202008f7270910420c60a420efaf9b505c90..0000000000000000000000000000000000000000
--- a/src/components/__tests__/HelloWorld.spec.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { describe, it, expect } from 'vitest'
-
-import { mount } from '@vue/test-utils'
-import HelloWorld from '../HelloWorld.vue'
-
-describe('HelloWorld', () => {
-  it('renders properly', () => {
-    const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } })
-    expect(wrapper.text()).toContain('Hello Vitest')
-  })
-})
diff --git a/src/router/index.ts b/src/router/index.ts
index b5719b3468524ac9b958344ecafa5df349c6e0d3..5f3a683a89ec1edd6c1e2d20c2b31f46102ff1e1 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -1,6 +1,7 @@
 // Import necessary dependencies from Vue Router and your views
 import { createRouter, createWebHistory } from 'vue-router';
 import LoginView from '../views/Authentication/LoginView.vue';
+import { useUserInfoStore } from '@/stores/UserStore';
 
 const routes = [
   {
@@ -19,6 +20,27 @@ const routes = [
         name: 'not-found',
         component: () => import('@/views/NotFoundView.vue'),
       },
+      {
+        path: '/news',
+        name: 'news',
+        component: () => import('@/views/NewsView.vue'),
+      },
+      {
+        path: 'test',
+        name: 'test',
+        component: () => import('@/views/TestView.vue'),
+      },
+      {
+        path: 'admin',
+        name: 'admin',
+        component: () => import('@/views/TestView.vue'),
+        meta: { requiresAdmin: true }
+      },
+      {
+        path: 'unauthorized',
+        name: 'unauthorized',
+        component: () => import('@/views/UnauthorizedView.vue'),
+      },
     ]
   },
   {
@@ -41,7 +63,15 @@ const router = createRouter({
 });
 
 router.beforeEach((to, from, next) => {
+  const requiresAuth = to.matched.some(record => record.meta.requiresAuth);
+  const requiresAdmin = to.matched.some(record => record.meta.requiresAdmin);
+  const userRole = useUserInfoStore().role;
+
+  if (requiresAdmin && userRole !== 'admin') {
+    next({ name: 'unauthorized' });
+  } else {
     next();
+  }
 });
 
 export default router;
\ No newline at end of file
diff --git a/src/stores/ErrorStore.ts b/src/stores/ErrorStore.ts
new file mode 100644
index 0000000000000000000000000000000000000000..32ffb91af9d857c3ad747853265724d3843f4ea4
--- /dev/null
+++ b/src/stores/ErrorStore.ts
@@ -0,0 +1,33 @@
+import { defineStore } from 'pinia';
+
+// A userstore which can be used to store several errors at the same time
+export const useErrorStore = defineStore('ErrorStore', {
+  state: () => ({
+    errors: [] as string[],
+  }),
+  actions: {
+    addError(error: string) {
+      console.log(error);
+      this.errors = [error];
+    },
+    removeCurrentError() {
+      if (this.errors.length > 0) {
+        this.errors.shift();
+      }
+    },
+  },
+  getters: {
+    getFirstError(): string {
+      if (this.errors.length > 0) {
+        return `Exceptions.${this.errors[0]}`;
+      }
+      return '';
+    },
+    getLastError(): string {
+      if (this.errors.length > 0) {
+        return `Exceptions.${this.errors[this.errors.length - 1]}`;
+      }
+      return '';
+    },
+  },
+});
\ No newline at end of file
diff --git a/src/stores/UserStore.ts b/src/stores/UserStore.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bd1a32d7b5dab2a33b1b73b1df068f9688fd9415
--- /dev/null
+++ b/src/stores/UserStore.ts
@@ -0,0 +1,74 @@
+import { OpenAPI } from '@/api';
+import Cookies from 'js-cookie';
+import { defineStore } from 'pinia';
+
+const cookiesStorage: Storage = {
+  setItem(key, state) {
+    return Cookies.set(key, state, { expires: 3 });
+  },
+  getItem(key) {
+    const store = Cookies.get(key);
+    if (store === undefined) {
+      OpenAPI.TOKEN = '';
+      return '';
+    }
+
+    OpenAPI.TOKEN = JSON.parse(Cookies.get(key) || '').accessToken;
+    return Cookies.get(key) || '';
+  },
+  length: 0,
+  clear: function (): void {
+    Cookies.remove('userInfo');
+  },
+  key: function (index: number): string | null {
+    throw new Error('Function not implemented.');
+  },
+  removeItem: function (key: string): void {
+    throw new Error('Function not implemented.');
+  },
+};
+
+export type UserStoreInfo = {
+  username?: string;
+  firstname?: string;
+  lastname?: string;
+  accessToken?: string;
+  role?: string;
+};
+
+export const useUserInfoStore = defineStore('UserInfoStore', {
+  state: () => ({
+    username: '',
+    firstname: '',
+    lastname: '',
+    accessToken: '',
+    role: '',
+  }),
+  actions: {
+    setUserInfo(userinfo: UserStoreInfo) {
+      userinfo.username && (this.$state.username = userinfo.username);
+      userinfo.firstname && (this.$state.firstname = userinfo.firstname);
+      userinfo.lastname && (this.$state.lastname = userinfo.lastname);
+      userinfo.accessToken && (this.$state.accessToken = userinfo.accessToken);
+      userinfo.accessToken && (OpenAPI.TOKEN = this.$state.accessToken);
+      userinfo.role && (this.$state.role = userinfo.role);
+    },
+    clearUserInfo() {
+      this.$state.username = '';
+      this.$state.firstname = '';
+      this.$state.lastname = '';
+      this.$state.accessToken = '';
+      this.$state.role = '';
+      OpenAPI.TOKEN = undefined;
+    },
+  },
+  getters: {
+    isLoggedIn(): boolean {
+      return this.accessToken !== '';
+    },
+  },
+  persist: {
+    enabled: true,
+    strategies: [{ key: 'userInfo', storage: cookiesStorage }],
+  },
+});
\ No newline at end of file
diff --git a/src/views/Authentication/LoginView.vue b/src/views/Authentication/LoginView.vue
index 9095328987e0c5479f039dd59d667f0533d4cb71..a2aa0f410c32b81ef253338a1b657b19d732e442 100644
--- a/src/views/Authentication/LoginView.vue
+++ b/src/views/Authentication/LoginView.vue
@@ -1,3 +1,15 @@
+<script setup lang="ts">
+import Footer from '@/components/BaseComponents/Footer.vue'
+import Menu from '@/components/BaseComponents/Menu.vue'
+import Login from '@/components/Login/Login.vue'
+</script>
+
 <template>
-    Hallo
-</template>
\ No newline at end of file
+  <Menu/>
+  <Login/>
+  <Footer/>
+</template>
+
+<style scoped>
+
+</style>
\ No newline at end of file
diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue
index 58542e7572cfd117267c3f71e29c25dc9ea000f7..f30d4244a9ec682c7a3c4d1c8a9bc3575779e181 100644
--- a/src/views/HomeView.vue
+++ b/src/views/HomeView.vue
@@ -1,7 +1,7 @@
 <script setup lang="ts">
-import TheWelcome from '../components/TheWelcome.vue'
+
 </script>
 
 <template>
-    Hallo
+    <RouterLink to="login">Login</RouterLink>
 </template>
diff --git a/src/views/NewsView.vue b/src/views/NewsView.vue
new file mode 100644
index 0000000000000000000000000000000000000000..bdfb7c611f43f0301e460f56b150a97213393a9d
--- /dev/null
+++ b/src/views/NewsView.vue
@@ -0,0 +1,8 @@
+<script setup lang="ts">
+import NewsComponent from "@/components/NewsComponents/NewsComponent.vue";
+</script>
+
+
+<template>
+  <NewsComponent></NewsComponent>
+</template>
\ No newline at end of file
diff --git a/src/views/NotFoundView.vue b/src/views/NotFoundView.vue
index 946391e88dbcaa2ac6d0d4964c5be9adda5a2421..650e2b20ce9dca2abb7fa351c48948a304fbb28e 100644
--- a/src/views/NotFoundView.vue
+++ b/src/views/NotFoundView.vue
@@ -1,5 +1,44 @@
 <template>
-    <div>
-        <h1 id="errorMessage">404 - Not Found</h1>
+    <div class="container-fluid"> <!-- Changed from 'container' to 'container-fluid' -->
+    <div class="row">
+        <div class="col-md-12">
+            <div class="error-template text-center"> <!-- 'text-center' for centering text content -->
+                <h1>
+                    Oops!</h1>
+                <h2>
+                    404 Not Found</h2>
+                <div class="error-details">
+                    Sorry, an error has occurred, Requested page not found!
+                </div>
+                <div class="error-actions">
+                    <Button1 button-text="Take Me Home" @click="home" />
+                </div>
+            </div>
+        </div>
     </div>
-</template>
\ No newline at end of file
+</div>
+</template>
+
+<script setup lang="ts">
+import { useRouter } from 'vue-router';
+import Button1 from '@/components/Buttons/Button1.vue';
+
+const router = useRouter();
+
+const home = () => {
+    router.push('/');  // Assuming the root URL '/' is your home route
+};
+</script>
+
+
+<style scoped>
+    .error-template {
+    text-align: center; /* Ensures all text and inline elements within are centered */
+    display: flex;
+    flex-direction: column;
+    align-items: center; /* Aligns child elements (which are block-level) centrally */
+    justify-content: center; /* Optional: if you want vertical centering */
+    margin: 2rem;
+}
+
+</style>
\ No newline at end of file
diff --git a/src/views/TestView.vue b/src/views/TestView.vue
new file mode 100644
index 0000000000000000000000000000000000000000..a7fce444288c4df4c5f9f531c3e92e3da43935a4
--- /dev/null
+++ b/src/views/TestView.vue
@@ -0,0 +1,11 @@
+<template>
+    <div>
+        <Button1 :buttonText="hallo"></Button1>
+    </div>
+</template>
+
+<script setup lang="ts">
+    import Button1 from '@/components/Buttons/Button1.vue'
+
+    const hallo = 'Hallo'
+</script>
\ No newline at end of file
diff --git a/src/views/UnauthorizedView.vue b/src/views/UnauthorizedView.vue
new file mode 100644
index 0000000000000000000000000000000000000000..8a3f78ed6bdc87283db0ea40dbcdd5b2306c371d
--- /dev/null
+++ b/src/views/UnauthorizedView.vue
@@ -0,0 +1,28 @@
+<template>
+    <body class="bg-dark text-white py-5">
+        <div class="container py-5">
+            <div class="row">
+                <div class="col-md-2 text-center">
+                    <p><img src="@/assets/icons/danger.svg"> <br/>Status Code: 403</p>
+                </div>
+                <div class="col-md-10">
+                    <h3>OPPSSS!!!! Sorry...</h3>
+                    <p>Sorry, your access is refused due to security reasons of our server and also our sensitive data.<br/>Please go back to the home page to continue browsing.</p>
+                    <Button1 :button-text="'Take Me Home'" @click="home" />
+                </div>
+            </div>
+        </div>
+    </body>
+</template>
+
+<script setup lang="ts">
+import { defineComponent } from 'vue';
+import { useRouter } from 'vue-router';
+import Button1 from '@/components/Buttons/Button1.vue';
+
+const router = useRouter();
+
+const home = () => {
+    router.push('/'); 
+};
+</script>