From 232cc5a76effc6e526c8a6a6eea3c412c8cba538 Mon Sep 17 00:00:00 2001
From: Milho001 <l.milhomemfrancochristino@uu.nl>
Date: Tue, 9 May 2023 10:49:05 +0000
Subject: [PATCH] feat(integration): Integrated backend and frontend through
 websocket

Added a main webpage to web/app with a navbar to control login/logout and add/select/remove a database to be queried. Restructured the main dashboard to use tailwindcss. Ported the websockets and broker implementation to V1 and tied all components (schema, querybuilder, and visualizer) together.

Solves #21
---
 README.md                                     |   53 +-
 apps/docs/package.json                        |    7 +-
 apps/web/.env                                 |    2 +
 apps/web/.env.production                      |    2 +
 apps/web/.eslintrc.json                       |    2 +-
 apps/web/node.d.ts                            |    6 +
 apps/web/package.json                         |    3 +
 apps/web/postcss.config.js                    |    6 +
 apps/web/src/app/app.tsx                      |  213 ++--
 apps/web/src/app/panels/Visualization.tsx     |   47 +
 apps/web/src/components/login/loginScreen.tsx |   16 +-
 .../add-database-form.module.scss             |    9 +-
 .../navbar/AddDatabaseForm/index.tsx          |    4 +-
 .../src/components/navbar/navbar.module.scss  |    2 +-
 apps/web/src/components/navbar/navbar.tsx     |  131 +-
 apps/web/src/components/panels/panel.tsx      |   16 +-
 apps/web/src/environments/variables.ts        |    1 +
 apps/web/src/main.tsx                         |    1 +
 apps/web/src/styles.css                       |    3 +
 apps/web/tailwind.config.js                   |    8 +
 apps/web/tsconfig.json                        |    2 +-
 apps/web/tsconfig.node.json                   |    3 +-
 apps/web/vite.config.ts                       |   11 +-
 libs/shared/README.md                         |    1 +
 libs/shared/lib/data-access/api/database.ts   |  141 +-
 libs/shared/lib/data-access/api/index.ts      |    3 +-
 libs/shared/lib/data-access/api/query.ts      |   52 +
 libs/shared/lib/data-access/api/schema.ts     |   40 +-
 libs/shared/lib/data-access/api/user.ts       |   54 +-
 .../authorization/authorizationHandler.ts     |  187 ---
 .../authorization/authorizationHook.tsx       |  206 ++-
 .../lib/data-access/authorization/index.ts    |    3 +-
 .../DatabaseRepository.tsx                    |   36 +
 .../QueryRepository.tsx                       |   27 +
 .../TranslatedJSONQuery.tsx                   |   85 ++
 .../BackendMessageReceiver.tsx                |   12 +
 .../BackendMessageReceiverMock.tsx            |   31 +
 .../BackendMessengerRepository.tsx            |   13 +
 .../WebSocketHandler.test.tsx                 |   28 +
 .../WebSocketHandler.tsx                      |   84 ++
 .../socket/backend-message-receiver/index.ts  |    1 +
 .../BackendMessenger.test.tsx                 |   18 +
 .../BackendMessengerMock.tsx                  |   87 ++
 .../socket/backend-messenger/index.tsx        |   64 +
 .../data-access/socket/broker/broker.test.tsx |   76 ++
 .../lib/data-access/socket/broker/index.tsx   |  102 ++
 libs/shared/lib/data-access/socket/index.ts   |    3 +
 .../socket/listeners/SchemaViewModelImpl.tsx  |  897 +++++++++++++
 .../lib/data-access/socket/query/QueryApi.tsx |   30 +
 .../shared/lib/data-access/store/authSlice.ts |   37 +
 .../store/graphQueryResultSlice.ts            |    4 +-
 libs/shared/lib/data-access/store/hooks.ts    |    2 +
 .../data-access/store/querybuilderSlice.ts    |    1 +
 .../lib/data-access/store/schemaSlice.ts      |    9 +-
 .../lib/data-access/store/sessionSlice.ts     |   67 +-
 libs/shared/lib/data-access/store/store.ts    |    2 +
 .../querybuilder/graph/graphology/utils.ts    |    2 +-
 libs/shared/lib/querybuilder/index.ts         |    3 +-
 .../panel/querybuilder.module.scss            |    7 +-
 .../lib/querybuilder/panel/querybuilder.tsx   |    5 +-
 .../query-utils/BackendQueryFormat.tsx        |  112 ++
 .../lib/querybuilder/query-utils/index.ts     |    2 +
 .../querybuilder/query-utils/query-utils.ts   |   78 ++
 .../lib/schema/panel/schema.module.scss       |   17 +-
 libs/shared/lib/schema/panel/schema.tsx       |    9 +-
 .../schema/pills/nodes/entity/entity-node.tsx |    1 +
 .../lib/schema/pills/nodes/entity/entity.scss |  180 ---
 .../pills/nodes/relation/relation-node.tsx    |    3 +-
 libs/shared/lib/schema/schema-utils/Types.tsx |    1 +
 .../nodelink/ResultNodeLinkParserUseCase.tsx  |   28 +-
 .../lib/vis/nodelink/nodelinkvis.stories.tsx  |   32 +-
 libs/shared/lib/vis/nodelink/nodelinkvis.tsx  |    2 +-
 .../lib/vis/rawjsonvis/rawjsonvis.stories.tsx |   22 +-
 libs/shared/lib/vis/rawjsonvis/rawjsonvis.tsx |    4 +-
 libs/shared/tsconfig.json                     |    1 +
 libs/shared/tsconfig.node.json                |    3 +-
 libs/storybook/.env                           |    2 +
 package.json                                  |    2 +-
 pnpm-lock.yaml                                | 1132 ++++++++++++++++-
 79 files changed, 3787 insertions(+), 812 deletions(-)
 create mode 100644 apps/web/.env
 create mode 100644 apps/web/.env.production
 create mode 100644 apps/web/node.d.ts
 create mode 100644 apps/web/postcss.config.js
 create mode 100644 apps/web/src/app/panels/Visualization.tsx
 create mode 100644 apps/web/src/environments/variables.ts
 create mode 100644 apps/web/src/styles.css
 create mode 100644 apps/web/tailwind.config.js
 create mode 100644 libs/shared/README.md
 create mode 100644 libs/shared/lib/data-access/api/query.ts
 delete mode 100644 libs/shared/lib/data-access/authorization/authorizationHandler.ts
 create mode 100644 libs/shared/lib/data-access/socket/DELETE-repository-interfaces/DatabaseRepository.tsx
 create mode 100644 libs/shared/lib/data-access/socket/DELETE-repository-interfaces/QueryRepository.tsx
 create mode 100644 libs/shared/lib/data-access/socket/DELETE-repository-interfaces/TranslatedJSONQuery.tsx
 create mode 100644 libs/shared/lib/data-access/socket/backend-message-receiver/BackendMessageReceiver.tsx
 create mode 100644 libs/shared/lib/data-access/socket/backend-message-receiver/BackendMessageReceiverMock.tsx
 create mode 100644 libs/shared/lib/data-access/socket/backend-message-receiver/BackendMessengerRepository.tsx
 create mode 100644 libs/shared/lib/data-access/socket/backend-message-receiver/WebSocketHandler.test.tsx
 create mode 100644 libs/shared/lib/data-access/socket/backend-message-receiver/WebSocketHandler.tsx
 create mode 100644 libs/shared/lib/data-access/socket/backend-message-receiver/index.ts
 create mode 100644 libs/shared/lib/data-access/socket/backend-messenger/BackendMessenger.test.tsx
 create mode 100644 libs/shared/lib/data-access/socket/backend-messenger/BackendMessengerMock.tsx
 create mode 100644 libs/shared/lib/data-access/socket/backend-messenger/index.tsx
 create mode 100644 libs/shared/lib/data-access/socket/broker/broker.test.tsx
 create mode 100644 libs/shared/lib/data-access/socket/broker/index.tsx
 create mode 100644 libs/shared/lib/data-access/socket/index.ts
 create mode 100644 libs/shared/lib/data-access/socket/listeners/SchemaViewModelImpl.tsx
 create mode 100644 libs/shared/lib/data-access/socket/query/QueryApi.tsx
 create mode 100644 libs/shared/lib/data-access/store/authSlice.ts
 create mode 100644 libs/shared/lib/querybuilder/query-utils/BackendQueryFormat.tsx
 create mode 100644 libs/shared/lib/querybuilder/query-utils/index.ts
 create mode 100644 libs/shared/lib/querybuilder/query-utils/query-utils.ts
 delete mode 100644 libs/shared/lib/schema/pills/nodes/entity/entity.scss
 create mode 100644 libs/storybook/.env

diff --git a/README.md b/README.md
index 7ddbb344e..2e9ae1cf4 100644
--- a/README.md
+++ b/README.md
@@ -5,44 +5,45 @@
 Be sure that you have node.js (v18 or up) and pnpm installed. Please use pnpm for building and running the scripts of package.json.
 Due to the way auth works (using a sameSite cookie), the procedure for running locally is a little different than usual. These steps will only have to be done, after that everything should 'just' work.
 
-### MacOS / Linux
+## Running Locally
 
-1. `sudo vim /etc/hosts` open the hosts file with your prefered text editor as root
-2. Add a new row containing `127.0.0.1 local.graphpolaris.com`, this will route traffic from `local.graphpolaris.com` to `127.0.0.1`
-3. `brew install mkcert` install mkcert utility
-4. `mkcert -install` generate local CA (certificate authority)
-5. Move into the /certs folder at the project root using `cd`
-6. `mkcert --key-file local-key.pem --cert-file local-cert.pem local.graphpolaris.com` create certificates for local SSL
+### Install
 
-### Windows
+First run `pnpm i` on the root of the workspace to install all dependencies. It will create a node_module folder in the root of the workspace as well as one for each app/library within it.
 
-1. Open the `hosts` file under `C:\Windows\System32\drivers\etc` using a text editor, as administrator
-2. Add a new row containing `127.0.0.1 local.graphpolaris.com`, this will route traffic from `local.graphpolaris.com` to `127.0.0.1`
-3. Install mkcert using any of the ways described [here](https://github.com/FiloSottile/mkcert#windows)
-4. Open an elevated Powershell or CMD session
-5. Move into the /certs folder at the project root using `cd`
-6. `mkcert --key-file local-key.pem --cert-file local-cert.pem local.graphpolaris.com` create certificates for local SSL
+If you need to install a new dependency or upgrade an existing one, do so in the respective package.json and rerun `pnpm i` in the root of the workspace. If you find any issues, feel free to delete the node_modules folder from the workspace root and from the app/lib which you are changing the dependency and rerun the `pnpm i` command from the root of the workspace. Most issues are solved by then.
 
-> No idea if the Windows steps work
+### Running Storybook.js
 
-## Running Locally
+To run the dev storybook (implementing visualizations) simply run `pnpm sb` and once it is running, ctrl-click the link what appears in the terminal. The url should be [http://localhost:6006].
 
-### Install
+### Dev server
 
-First run `pnpm i` on the root of the workspace to install all dependencies. It will create a node_module folder in the root of the workspace as well as one for each app/library within it.
+The dev server (as of now) expects the backend to be also running in your machine. Therefore, for it to be useful you will need first to have the Backend
+running. After that you also need to be sure tp update your hosts file. Finaly, to run the application simply run `pnpm dev` from the workspace root.
 
-If you need to install a new dependency or upgrade an existing one, do so in the respective package.json and rerun `pnpm i` in the root of the workspace. If you find any issues, feel free to delete the node_modules folder from the workspace root and from the app/lib which you are changing the dependency and rerun the `pnpm i` command from the root of the workspace. Most issues are solved by then.
+#### Hosts file
 
-### Commands
+To configure the hosts file, you need to first find it in your machine.
 
-You can run pnpm commands (see available ones in packages.json) from the root of the workspace, which will trigger turborepo to run it in all libraries and apps of the workspace. You can run `pnpm test` or `pnpm lint` this way to test or lint the entire workspace.
+- Windows: c:\Windows\System32\Drivers\etc\hosts
+- Mac/Linux: /etc/hosts
 
-You can also go into a specific lib/app and run pnpm commands from there to scope the task to only that part of the workspace.
+Open the file with administrative privileges (e.g. sudo nano, sudo vim, or use vscode and when you try to save it will prompt you to save as an administrator) and add these two lines at the end:
 
-### Dev server
+```
+127.0.0.1 local.graphpolaris.com
+127.0.0.1 api.graphpolaris.com
+```
 
-To run the application using SSL (with these keys) simply run `pnpm dev` from the workspace root.
+This will make the dev server to try to connect to the backend running in your own machine. If you want to make the dev server to call the production (cloud-based) backend of graph polaris, only add the first line of the two like so:
 
-### Running Storybook.js
+```
+127.0.0.1 local.graphpolaris.com
+```
 
-To run the dev storybook (implementing visualizations) simply run `pnpm sb` and once it is running, ctrl-click the link what appears in the terminal.
+### Other Commands
+
+You can run pnpm commands (see available ones in packages.json) from the root of the workspace, which will trigger turborepo to run it in all libraries and apps of the workspace. You can run `pnpm test` or `pnpm lint` this way to test or lint the entire workspace.
+
+You can also go into a specific lib/app and run pnpm commands from there to scope the task to only that part of the workspace.
diff --git a/apps/docs/package.json b/apps/docs/package.json
index 01d7b1a8c..97c3bc1f0 100644
--- a/apps/docs/package.json
+++ b/apps/docs/package.json
@@ -2,12 +2,7 @@
   "name": "docs",
   "version": "0.0.0",
   "private": true,
-  "scripts": {
-    "dev": "next dev --port 3001",
-    "build": "next build",
-    "start": "next start",
-    "lint": "next lint"
-  },
+  "scripts": {},
   "dependencies": {
     "next": "^13.1.1",
     "react": "18.2.0",
diff --git a/apps/web/.env b/apps/web/.env
new file mode 100644
index 000000000..374809122
--- /dev/null
+++ b/apps/web/.env
@@ -0,0 +1,2 @@
+VITE_BACKEND_URL=api.graphpolaris.com
+VITE_STAGING=local
\ No newline at end of file
diff --git a/apps/web/.env.production b/apps/web/.env.production
new file mode 100644
index 000000000..895239e15
--- /dev/null
+++ b/apps/web/.env.production
@@ -0,0 +1,2 @@
+VITE_BACKEND_URL=api.graphpolaris.com
+VITE_STAGING=prod
\ No newline at end of file
diff --git a/apps/web/.eslintrc.json b/apps/web/.eslintrc.json
index 2707c1f41..351d714ab 100644
--- a/apps/web/.eslintrc.json
+++ b/apps/web/.eslintrc.json
@@ -7,7 +7,7 @@
       "jsx": true
     }
   },
-  "ignorePatterns": ["!**/*"],
+  "ignorePatterns": ["!**/*", "node.d.ts"],
   "overrides": [
     {
       "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
diff --git a/apps/web/node.d.ts b/apps/web/node.d.ts
new file mode 100644
index 000000000..015611148
--- /dev/null
+++ b/apps/web/node.d.ts
@@ -0,0 +1,6 @@
+interface ImportMeta {
+    env: {
+        VITE_BACKEND_URL: string;
+        VITE_STAGING: string;
+    }
+}
\ No newline at end of file
diff --git a/apps/web/package.json b/apps/web/package.json
index 7b5586514..9dc529d75 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -35,8 +35,11 @@
     "@types/styled-components": "^5.1.26",
     "@vitejs/plugin-basic-ssl": "^1.0.1",
     "@vitejs/plugin-react-swc": "^3.0.0",
+    "autoprefixer": "^10.4.14",
     "graphology-types": "^0.24.7",
+    "postcss": "^8.4.21",
     "react-is": "^18.2.0",
+    "tailwindcss": "^3.3.1",
     "typescript": "^4.9.3",
     "vite": "^4.2.0",
     "vite-plugin-dts": "^2.1.0",
diff --git a/apps/web/postcss.config.js b/apps/web/postcss.config.js
new file mode 100644
index 000000000..2f20e4a74
--- /dev/null
+++ b/apps/web/postcss.config.js
@@ -0,0 +1,6 @@
+export default {
+  plugins: {
+    tailwindcss: { config: './tailwind.config.js' },
+    autoprefixer: {},
+  },
+};
diff --git a/apps/web/src/app/app.tsx b/apps/web/src/app/app.tsx
index 9ae2badfa..171a7a690 100644
--- a/apps/web/src/app/app.tsx
+++ b/apps/web/src/app/app.tsx
@@ -1,107 +1,160 @@
-import { useEffect, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
 import GridLayout from 'react-grid-layout';
-import LoginScreen from '../components/login/loginScreen';
 import Panel from '../components/panels/panel';
 import { RawJSONVis } from '@graphpolaris/shared/lib/vis/rawjsonvis/rawjsonvis';
 import SemanticSubstrates from '@graphpolaris/shared/lib/vis/semanticsubstrates/semanticsubstrates';
 import { Schema } from '@graphpolaris/shared/lib/schema/panel';
 import {
-  GetAllDatabases,
-  GetUserInfo,
-} from '@graphpolaris/shared/lib/data-access/api';
-import { QueryBuilder } from '@graphpolaris/shared/lib/querybuilder';
+  Query2BackendQuery,
+  QueryBuilder,
+} from '@graphpolaris/shared/lib/querybuilder';
 import {
   assignNewGraphQueryResult,
   useAppDispatch,
 } from '@graphpolaris/shared/lib/data-access/store';
+import { Navbar } from '../components/navbar/navbar';
+import { VisualizationPanel } from './panels/Visualization';
 import {
-  AuthorizationHandler,
+  readInSchemaFromBackend,
   useAuthorization,
-} from '@graphpolaris/shared/lib/data-access/authorization';
-import { Navbar } from '../components/navbar/navbar';
+  useAuthorizationCache,
+  useDatabaseAPI,
+  useQueryAPI,
+  useQuerybuilderGraph,
+  useQuerybuilderGraphology,
+  useSchemaAPI,
+  useSessionCache,
+} from '@graphpolaris/shared/lib/data-access';
+import LoginScreen from '../components/login/loginScreen';
+import { WebSocketHandler } from '@graphpolaris/shared/lib/data-access/socket';
+import Broker from '@graphpolaris/shared/lib/data-access/socket/broker';
+import { SchemaFromBackend } from '@graphpolaris/shared/lib/model/backend';
+import { domain } from '../environments/variables';
+import { QueryMultiGraphExport } from '@graphpolaris/shared/lib/querybuilder/graph/graphology/utils';
+import { GraphQueryResultFromBackend } from '@graphpolaris/shared/lib/data-access/store/graphQueryResultSlice';
 
 export function App() {
+  const { AuthorizeFromCache, auth } = useAuthorization(domain);
+  const api = useDatabaseAPI(domain);
+  const api_schema = useSchemaAPI(domain);
+  const api_query = useQueryAPI(domain);
   const dispatch = useAppDispatch();
-  const authorization = useAuthorization();
+  const session = useSessionCache();
+  const query = useQuerybuilderGraph();
+  const ws = useRef(new WebSocketHandler(domain));
 
   useEffect(() => {
-    if (authorization.userAuthorized) {
-      GetAllDatabases().then((d) => {
-        console.log(d);
+    // Default
+    AuthorizeFromCache();
+    Broker.instance().subscribe(
+      (data: SchemaFromBackend) => dispatch(readInSchemaFromBackend(data)),
+      'schema_result'
+    );
+
+    Broker.instance().subscribe(
+      (data: GraphQueryResultFromBackend) =>
+        dispatch(assignNewGraphQueryResult(data)),
+      'query_result'
+    );
+
+    return () => {
+      Broker.instance().unSubscribeAll('schema_result');
+      Broker.instance().unSubscribeAll('query_result');
+    };
+  }, []);
+
+  useEffect(() => {
+    // New active database
+    if (session.currentDatabase) {
+      ws.current.useToken(auth.accessToken).connect(() => {
+        api_schema.RequestSchema(session.currentDatabase);
       });
     }
-  }, [authorization.userAuthorized]);
+  }, [session.currentDatabase]);
+
+  useEffect(() => {
+    // Newly (un)authorized
+    console.log(auth.authorized);
+    if (auth.authorized) {
+      api.GetAllDatabases({ updateSessionCache: true });
+    }
+  }, [auth.authorized]);
+
+  useEffect(() => {
+    // New query
+    if (session?.currentDatabase && query) {
+      api_query.execute(Query2BackendQuery(session.currentDatabase, query));
+    }
+  }, [query]);
 
   return (
-    <>
-      {!authorization.userAuthorized && <LoginScreen />}
-      <Navbar />
-      <GridLayout
-        className="layout"
-        cols={10}
-        rowHeight={30}
-        width={window.innerWidth}
+    <div className="h-screen w-screen">
+      {!auth.authorized && <LoginScreen />}
+      <div
+        className={
+          'flex h-screen w-screen overflow-hidden ' +
+          (!auth.authorized ? 'blur-sm pointer-events-none ' : '')
+        }
       >
-        <div
-          key="schema-panel"
-          data-grid={{ x: 0, y: 0, w: 3, h: 30, maxW: 5, isDraggable: false }}
-        >
-          <Panel content="Schema Panel" color="red">
-            <Schema />
-          </Panel>
-        </div>
-        <div
-          key="query-panel"
-          data-grid={{ x: 3, y: 20, w: 5, h: 10, maxH: 20, isDraggable: false }}
-        >
-          <Panel content="Query Panel" color="blue">
-            <QueryBuilder />
-          </Panel>
-        </div>
-        <div
-          key="visualisation-panel"
-          data-grid={{ x: 3, y: 0, w: 7, h: 20, isDraggable: false }}
-        >
-          <Panel content="Visualisation Panel" color="green">
-            <div>
-              <button
-                onClick={() =>
-                  dispatch(
-                    assignNewGraphQueryResult({
-                      nodes: [
-                        {
-                          id: 'agent/007',
-                          attributes: { name: 'Daniel Craig' },
-                        },
-                      ],
-                      links: [],
-                    })
-                  )
-                }
-              >
-                Load in mock result
-              </button>
-              <button
-                onClick={() =>
-                  dispatch(assignNewGraphQueryResult({ nodes: [], links: [] }))
-                }
-              >
-                Remove mock result
-              </button>
+        <div className="h-full relative flex flex-col flex-1 overflow-y-auto overflow-x-hidden">
+          <div className="h-fit flex-grow-0">
+            <Navbar />
+          </div>
+          <main className="h-full flex gap-4 p-10">
+            <div className="w-full basis-[33%]">
+              <Panel content="Schema">
+                <Schema />
+              </Panel>
             </div>
-            {/* <RawJSONVis /> */}
-            {/* <SemanticSubstrates /> */}
-            <div />
-          </Panel>
-        </div>
-        <div
-          key="history-panel"
-          data-grid={{ x: 8, y: 20, w: 2, h: 10, isDraggable: false }}
-        >
-          <Panel content="History Channel" color="purple"></Panel>
+            <div className="w-full basis-[67%] flex flex-col gap-4">
+              <div className="h-full basis-[67%] overflow-y-clip">
+                <VisualizationPanel />
+              </div>
+              <div className="h-full basis-[33%] flex-grow-0">
+                <Panel content="Query Panel">
+                  <QueryBuilder />
+                </Panel>
+              </div>
+            </div>
+          </main>
         </div>
-      </GridLayout>
-    </>
+      </div>
+      {/*<GridLayout*/}
+      {/*  className="layout"*/}
+      {/*  cols={10}*/}
+      {/*  rowHeight={30}*/}
+      {/*  width={window.innerWidth}*/}
+      {/*>*/}
+      {/*  <div*/}
+      {/*    key="schema-panel"*/}
+      {/*    data-grid={{ x: 0, y: 0, w: 3, h: 30, maxW: 5, isDraggable: false }}*/}
+      {/*  >*/}
+      {/*    <Panel content="Schema Panel" color="red">*/}
+      {/*      <Schema />*/}
+      {/*    </Panel>*/}
+      {/*  </div>*/}
+      {/*  <div*/}
+      {/*    key="query-panel"*/}
+      {/*    data-grid={{ x: 3, y: 20, w: 5, h: 10, maxH: 20, isDraggable: false }}*/}
+      {/*  >*/}
+      {/*    <Panel content="Query Panel" color="blue">*/}
+      {/*      <QueryBuilder />*/}
+      {/*    </Panel>*/}
+      {/*  </div>*/}
+      {/*  <div*/}
+      {/*    key="visualisation-panel"*/}
+      {/*    data-grid={{ x: 3, y: 0, w: 7, h: 20, isDraggable: false }}*/}
+      {/*  >*/}
+      {/*    <VisualizationPanel />*/}
+      {/*  </div>*/}
+      {/*  <div*/}
+      {/*    key="history-panel"*/}
+      {/*    data-grid={{ x: 8, y: 20, w: 2, h: 10, isDraggable: false }}*/}
+      {/*  >*/}
+      {/*    <Panel content="History Channel" color="purple"></Panel>*/}
+      {/*  </div>*/}
+      {/*</GridLayout>*/}
+    </div>
   );
 }
 
diff --git a/apps/web/src/app/panels/Visualization.tsx b/apps/web/src/app/panels/Visualization.tsx
new file mode 100644
index 000000000..53fefc572
--- /dev/null
+++ b/apps/web/src/app/panels/Visualization.tsx
@@ -0,0 +1,47 @@
+import { RawJSONVis, NodeLinkVis } from '@graphpolaris/shared/lib/vis';
+import Panel from '../../components/panels/panel';
+import {
+  assignNewGraphQueryResult,
+  useAppDispatch,
+} from '@graphpolaris/shared/lib/data-access';
+
+export const VisualizationPanel = () => {
+  const dispatch = useAppDispatch();
+
+  return (
+    <Panel content="Visualization Panel">
+      {/* <div>
+        <button
+          onClick={() =>
+            dispatch(
+              assignNewGraphQueryResult({
+                nodes: [
+                  {
+                    id: 'agent/007',
+                    attributes: { name: 'Daniel Craig' },
+                  },
+                ],
+                edges: [],
+              })
+            )
+          }
+        >
+          Load in mock result
+        </button>
+        <button
+          onClick={() =>
+            dispatch(assignNewGraphQueryResult({ nodes: [], edges: [] }))
+          }
+        >
+          Remove mock result
+        </button>
+      </div> */}
+      <div className="h-[48rem] overflow-y-scroll">
+        <RawJSONVis />
+        {/* <NodeLinkVis /> */}
+      </div>
+      {/* <SemanticSubstrates /> */}
+      {/* <div /> */}
+    </Panel>
+  );
+};
diff --git a/apps/web/src/components/login/loginScreen.tsx b/apps/web/src/components/login/loginScreen.tsx
index 2e4463709..9bf756ad5 100644
--- a/apps/web/src/components/login/loginScreen.tsx
+++ b/apps/web/src/components/login/loginScreen.tsx
@@ -1,8 +1,10 @@
-import { AuthorizationHandler } from '@graphpolaris/shared/lib/data-access/authorization';
+import { useAuthorizationCache } from '@graphpolaris/shared/lib/data-access';
+import { useAuthorization } from '@graphpolaris/shared/lib/data-access/authorization';
+import { domain } from '../../environments/variables';
 import styled from 'styled-components';
 
 const Wrapper = styled.div`
-  font-family: 'Arial';
+  font-family: Arial, serif;
   position: absolute;
   left: 0;
   top: 0;
@@ -53,7 +55,7 @@ const Content = styled.div`
     padding: 0;
 
     // Same width flexbox items
-    flex: 1 1 0px;
+    flex: 1 1 0;
 
     max-height: 3em;
 
@@ -64,6 +66,8 @@ const Content = styled.div`
 `;
 
 const LoginScreen = () => {
+  const { SetAccessToken } = useAuthorization(domain);
+
   const openSignInWindow = (url: string) => {
     // remove any existing event listeners
     window.removeEventListener('auth_message', receiveMessage);
@@ -88,17 +92,17 @@ const LoginScreen = () => {
     if (window.location.hostname !== event.detail.origin) {
       return;
     }
-    console.log(window.location.hostname, event.detail.origin);
+    console.log(event.detail.token);
 
     // Set access token
-    AuthorizationHandler.instance().SetAccessToken(event.detail.token);
+    SetAccessToken(event.detail.token);
   };
 
   return (
     <Wrapper>
       <Background></Background>
       <Content>
-        <h1>Sign In</h1>
+        <h1 className="pointer-events-none">Sign In</h1>
         <img
           onClick={() =>
             openSignInWindow(
diff --git a/apps/web/src/components/navbar/AddDatabaseForm/add-database-form.module.scss b/apps/web/src/components/navbar/AddDatabaseForm/add-database-form.module.scss
index 97642ae37..d3db36500 100644
--- a/apps/web/src/components/navbar/AddDatabaseForm/add-database-form.module.scss
+++ b/apps/web/src/components/navbar/AddDatabaseForm/add-database-form.module.scss
@@ -14,7 +14,8 @@
   flex-direction: column;
   flex-wrap: wrap;
   justify-content: center;
-  max-width: 400px;
+  gap: 1em;
+  max-width: 600px;
   margin: auto;
   overflow: auto;
   min-height: 300px;
@@ -29,6 +30,10 @@
 }
 
 .formWrapper {
+  display: flex;
+  flex-direction: column;
+  flex-wrap: wrap;
+  gap: 1em;
 }
 
 .loginContainer {
@@ -38,6 +43,7 @@
 .loginContainerRow {
   display: flex;
   flex-direction: row;
+  gap: 0.5em;
   margin: 5px 0px;
 }
 
@@ -72,4 +78,3 @@ passLabel {
 cancelButton {
   margin-top: 2.5%;
 }
-
diff --git a/apps/web/src/components/navbar/AddDatabaseForm/index.tsx b/apps/web/src/components/navbar/AddDatabaseForm/index.tsx
index 54c1b0ac0..a92c13025 100644
--- a/apps/web/src/components/navbar/AddDatabaseForm/index.tsx
+++ b/apps/web/src/components/navbar/AddDatabaseForm/index.tsx
@@ -110,7 +110,9 @@ export default function AddDatabaseForm(props: AddDatabaseFormProps) {
               }}
             >
               {databaseNameMapping.map((dbName) => (
-                <option value={dbName}>{dbName}</option>
+                <option value={dbName} key={dbName}>
+                  {dbName}
+                </option>
               ))}
             </NativeSelect>
           </div>
diff --git a/apps/web/src/components/navbar/navbar.module.scss b/apps/web/src/components/navbar/navbar.module.scss
index e1faa9a78..339c62c0b 100644
--- a/apps/web/src/components/navbar/navbar.module.scss
+++ b/apps/web/src/components/navbar/navbar.module.scss
@@ -1,6 +1,7 @@
 .root {
   display: flex;
   overflow: hidden;
+  height: 50px;
 }
 
 .appBar {
@@ -41,4 +42,3 @@
   color: black;
   font-weight: bold;
 }
-
diff --git a/apps/web/src/components/navbar/navbar.tsx b/apps/web/src/components/navbar/navbar.tsx
index ef3d9c91e..8c78781d5 100644
--- a/apps/web/src/components/navbar/navbar.tsx
+++ b/apps/web/src/components/navbar/navbar.tsx
@@ -8,7 +8,7 @@
 /* The comment above was added so the code coverage wouldn't count this file towards code coverage.
  * We do not test components/renderfunctions/styling files.
  * See testing plan for more details.*/
-import React, { useState } from 'react';
+import React, { useEffect, useState } from 'react';
 import {
   AppBar,
   Toolbar,
@@ -26,17 +26,19 @@ import logo_white from './logogpwhite.png';
 import AddDatabaseForm from './AddDatabaseForm';
 import { useTheme } from '@mui/material/styles';
 import styles from './navbar.module.scss';
-import {
-  AddDatabase,
-  AddDatabaseRequest,
-  GetAllDatabases,
-  RequestSchema,
-  useAuthorization,
-} from '@graphpolaris/shared/lib/data-access';
+
 import {
   updateCurrentDatabase,
   updateDatabaseList,
 } from '@graphpolaris/shared/lib/data-access/store/sessionSlice';
+import {
+  AddDatabaseRequest,
+  useAppDispatch,
+  useAuthorizationCache,
+  useDatabaseAPI,
+  useSchemaAPI,
+  useSessionCache,
+} from '@graphpolaris/shared/lib/data-access';
 
 /** NavbarComponentProps is an interface containing the NavbarViewModel. */
 export interface NavbarComponentProps {
@@ -45,16 +47,11 @@ export interface NavbarComponentProps {
 
 /** NavbarComponentState is an interface containing the type of visualizations. */
 export interface NavbarComponentState {
-  isAuthorized: boolean;
-  clientID: string;
-  sessionID: string;
-  databases: string[];
-  currentDatabase: string;
-
   // The anchor for rendering the user data (clientID, sessionID, add database, etc) menu
   userMenuAnchor?: Element;
   // The anchor for rendering the menu for selecting a database to use
   selectDatabaseMenuAnchor?: Element;
+  deleteDatabaseMenuAnchor?: Element;
   // Determines if the addDatabaseForm will be shown
   showAddDatabaseForm: boolean;
 }
@@ -62,30 +59,35 @@ export interface NavbarComponentState {
 /** NavbarComponent is the View implementation for Navbar */
 export const Navbar = (props: NavbarComponentProps) => {
   const theme = useTheme();
-  const authorization = useAuthorization();
+  const auth = useAuthorizationCache();
+  const session = useSessionCache();
+  const api = useDatabaseAPI();
+  const schemaApi = useSchemaAPI();
+  const dispatch = useAppDispatch();
+
+  useEffect(() => {
+    console.log(auth);
+  }, [auth.accessToken]);
 
   // const { navbarViewModel, currentColours } = props;
   // this.navbarViewModel = navbarViewModel;
 
   const [state, setState] = useState<NavbarComponentState>({
-    isAuthorized: false,
-    clientID: authorization.userAuthorized ? authorization.userId || '' : '',
-    sessionID: authorization.sessionId || '',
-    databases: [],
-    currentDatabase: '',
-
     userMenuAnchor: undefined,
     selectDatabaseMenuAnchor: undefined,
+    deleteDatabaseMenuAnchor: undefined,
     showAddDatabaseForm: false,
   });
 
   /** Closes the user menu. Also closes all nested menu's. */
   function closeUserMenu(): void {
     // If a nested window is open, close the main user menu a bit later
-    if (state.selectDatabaseMenuAnchor != undefined) {
-      setState({ ...state, selectDatabaseMenuAnchor: undefined });
-      setTimeout(() => setState({ ...state, userMenuAnchor: undefined }), 100);
-    } else setState({ ...state, userMenuAnchor: undefined });
+    setState({
+      ...state,
+      selectDatabaseMenuAnchor: undefined,
+      deleteDatabaseMenuAnchor: undefined,
+    });
+    setTimeout(() => setState({ ...state, userMenuAnchor: undefined }), 100);
   }
 
   function changeColourPalette() {
@@ -98,15 +100,11 @@ export const Navbar = (props: NavbarComponentProps) => {
   function onAddDatabaseFormSubmit(
     request: AddDatabaseRequest
   ): Promise<void | Response> {
-    return AddDatabase(request).then(() =>
-      GetAllDatabases().then((databases) => {
-        updateDatabaseList(databases);
-        updateCurrentDatabase(request.name);
-        // When the database changes, request the new schema
-        console.log('databases ' + databases);
-        RequestSchema(request.name);
-      })
-    );
+    return api
+      .AddDatabase(request, { updateDatabaseCache: true, setAsCurrent: true })
+      .then(() => {
+        schemaApi.RequestSchema(request.name);
+      });
   }
 
   const currentLogo = theme.palette.custom.logo == 'white' ? logo_white : logo;
@@ -117,7 +115,6 @@ export const Navbar = (props: NavbarComponentProps) => {
       <AppBar
         title="GraphPolaris"
         style={{
-          zIndex: 1250,
           backgroundColor: theme.palette.custom.background,
         }}
         position="fixed"
@@ -128,13 +125,13 @@ export const Navbar = (props: NavbarComponentProps) => {
             <img src={currentLogo} className={styles.logo} />
           </a>
           <div className={styles.menubox}>
-            <Button
+            {/* <Button
               className={styles.menuText}
               style={{ color: theme.palette.custom.menuText }}
               onClick={changeColourPalette}
             >
               Change Palette
-            </Button>
+            </Button> */}
             <Button
               href="https://graphpolaris.com/"
               className={styles.menuText}
@@ -163,7 +160,7 @@ export const Navbar = (props: NavbarComponentProps) => {
             >
               Contact
             </Button>
-            {state.isAuthorized ? (
+            {auth.authorized ? (
               <div>
                 <IconButton
                   color="inherit"
@@ -186,10 +183,10 @@ export const Navbar = (props: NavbarComponentProps) => {
                   onClose={() => closeUserMenu()}
                 >
                   <MenuItem>
-                    <ListItemText primary={'clientID: ' + state.clientID} />
+                    <ListItemText primary={'userID: ' + auth.userID} />
                   </MenuItem>
                   <MenuItem>
-                    <ListItemText primary={'sessionID: ' + state.sessionID} />
+                    <ListItemText primary={'sessionID: ' + auth.sessionID} />
                   </MenuItem>
                   {/* <MenuItem
                     onClick={() =>
@@ -232,16 +229,58 @@ export const Navbar = (props: NavbarComponentProps) => {
                     open={Boolean(state.selectDatabaseMenuAnchor)}
                     onClose={() => closeUserMenu()}
                   >
-                    {state.databases.length > 0 ? (
-                      state.databases.map((database) => (
+                    {session.databases.length > 0 ? (
+                      session.databases.map((database) => (
+                        <MenuItem
+                          key={database}
+                          selected={database == session.currentDatabase}
+                          onClick={() => {
+                            if (session.currentDatabase != database) {
+                              dispatch(updateCurrentDatabase(database));
+                            }
+                            closeUserMenu();
+                          }}
+                        >
+                          <ListItemText primary={database} />
+                        </MenuItem>
+                      ))
+                    ) : (
+                      <MenuItem key="placeholder" value="" disabled>
+                        no databases connected
+                      </MenuItem>
+                    )}
+                  </Menu>
+                  <MenuItem
+                    onClick={(event) =>
+                      setState({
+                        ...state,
+                        deleteDatabaseMenuAnchor: event.currentTarget,
+                      })
+                    }
+                  >
+                    <ListItemText primary={'Delete database'} />
+                  </MenuItem>
+                  <Menu
+                    id="delete-databases-menus"
+                    anchorOrigin={{ vertical: 'top', horizontal: 'left' }}
+                    transformOrigin={{ vertical: 'top', horizontal: 'right' }}
+                    anchorEl={state.deleteDatabaseMenuAnchor}
+                    keepMounted
+                    disableAutoFocusItem
+                    open={Boolean(state?.deleteDatabaseMenuAnchor)}
+                    onClose={() => closeUserMenu()}
+                  >
+                    {session.databases.length > 0 ? (
+                      session.databases.map((database) => (
                         <MenuItem
                           key={database}
-                          selected={database == state.currentDatabase}
+                          selected={database == session.currentDatabase}
                           onClick={() => {
-                            if (state.currentDatabase != database) {
-                              updateCurrentDatabase(database);
-                              closeUserMenu();
+                            if (session.currentDatabase === database) {
+                              dispatch(updateCurrentDatabase(''));
                             }
+                            api.DeleteDatabase(database);
+                            closeUserMenu();
                           }}
                         >
                           <ListItemText primary={database} />
diff --git a/apps/web/src/components/panels/panel.tsx b/apps/web/src/components/panels/panel.tsx
index 077dbe366..04f694ecf 100644
--- a/apps/web/src/components/panels/panel.tsx
+++ b/apps/web/src/components/panels/panel.tsx
@@ -3,7 +3,7 @@ import { ReactNode } from 'react';
 import { useTheme } from '@mui/material/styles';
 interface Props {
   content: string;
-  color: string;
+  color?: string;
   children?: ReactNode;
 }
 
@@ -31,15 +31,15 @@ const Content = styled.div`
 `;
 
 const Panel = (props: Props) => {
-  const theme = useTheme();
-
   return (
-    <Wrapper color={props.color}>
-      <Content>
+    <Wrapper color={props?.color || 'white'} className="">
+      <Content className="">
         <h1
-          style={{
-            backgroundColor: theme.palette.custom.dataPointColors[1],
-          }}
+          style={
+            {
+              // backgroundColor: theme.palette.custom.dataPointColors[1],
+            }
+          }
         >
           {props.content}
         </h1>
diff --git a/apps/web/src/environments/variables.ts b/apps/web/src/environments/variables.ts
new file mode 100644
index 000000000..f1a86d7fb
--- /dev/null
+++ b/apps/web/src/environments/variables.ts
@@ -0,0 +1 @@
+export const domain = import.meta.env.VITE_BACKEND_URL;
\ No newline at end of file
diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx
index 6bfe07d13..588f0faf3 100644
--- a/apps/web/src/main.tsx
+++ b/apps/web/src/main.tsx
@@ -11,6 +11,7 @@ import App from './app/app';
 import LoginPopupComponent from './components/login/popup';
 import { CssBaseline } from '@mui/material';
 import { createRoot } from 'react-dom/client';
+import './styles.css';
 
 const domNode = document.getElementById('root');
 
diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css
new file mode 100644
index 000000000..b5c61c956
--- /dev/null
+++ b/apps/web/src/styles.css
@@ -0,0 +1,3 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
diff --git a/apps/web/tailwind.config.js b/apps/web/tailwind.config.js
new file mode 100644
index 000000000..d21f1cdae
--- /dev/null
+++ b/apps/web/tailwind.config.js
@@ -0,0 +1,8 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+  content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
+  theme: {
+    extend: {},
+  },
+  plugins: [],
+};
diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json
index 55b05d754..3dae4981c 100644
--- a/apps/web/tsconfig.json
+++ b/apps/web/tsconfig.json
@@ -25,6 +25,6 @@
   },
   "exclude": ["node_modules", "public"],
   "include": ["src", "../../libs/shared/lib/querybuilder/panel/attributepill"],
-  "files": ["./cssmodule.d.ts", "./image.d.ts"],
+  "files": ["./node.d.ts"],
   "references": [{ "path": "./tsconfig.node.json" }]
 }
diff --git a/apps/web/tsconfig.node.json b/apps/web/tsconfig.node.json
index 9d31e2aed..a54ca1adb 100644
--- a/apps/web/tsconfig.node.json
+++ b/apps/web/tsconfig.node.json
@@ -3,7 +3,8 @@
     "composite": true,
     "module": "ESNext",
     "moduleResolution": "Node",
-    "allowSyntheticDefaultImports": true
+    "allowSyntheticDefaultImports": true,
+    "types": ["node", "vite/client"]
   },
   "include": ["vite.config.ts"]
 }
diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts
index 4fcc69729..bc88eb31f 100644
--- a/apps/web/vite.config.ts
+++ b/apps/web/vite.config.ts
@@ -7,10 +7,13 @@ import dts from 'vite-plugin-dts';
 
 // https://vitejs.dev/config/
 export default defineConfig({
-  plugins: [react(), basicSsl(),
-  dts({
-    insertTypesEntry: true,
-  }),],
+  plugins: [
+    react(),
+    basicSsl(),
+    dts({
+      insertTypesEntry: true,
+    }),
+  ],
   resolve: {
     alias: {
       '@graphpolaris/shared/lib': path.resolve(
diff --git a/libs/shared/README.md b/libs/shared/README.md
new file mode 100644
index 000000000..464090415
--- /dev/null
+++ b/libs/shared/README.md
@@ -0,0 +1 @@
+# TODO
diff --git a/libs/shared/lib/data-access/api/database.ts b/libs/shared/lib/data-access/api/database.ts
index b627ba76d..5764cfe0f 100644
--- a/libs/shared/lib/data-access/api/database.ts
+++ b/libs/shared/lib/data-access/api/database.ts
@@ -1,6 +1,7 @@
 // All database related API calls
 
-import { AuthorizationHandler } from '@graphpolaris/shared/lib/data-access/authorization';
+import { useAppDispatch, useAuthorizationCache, useSessionCache } from "../store";
+import { updateCurrentDatabase, updateDatabaseList } from "../store/sessionSlice";
 
 export enum DatabaseType {
   ArangoDB = 0,
@@ -19,66 +20,100 @@ export type AddDatabaseRequest = {
   type: DatabaseType; // Database type. 0 = ArangoDB, 1 = Neo4j
 };
 
-export function AddDatabase(request: AddDatabaseRequest): Promise<void> {
-  return new Promise((resolve, reject) => {
-    fetch('https://api.graphpolaris.com/user/database', {
-      method: 'POST',
-      credentials: 'same-origin',
-      headers: new Headers({
-        Authorization:
-          'Bearer ' + AuthorizationHandler.instance().AccessToken(),
-      }),
-      body: JSON.stringify(request),
-    }).then((response: Response) => {
-      if (!response.ok) {
-        reject(response.statusText);
-      }
+export type AddDatabaseOptions = {
+  setAsCurrent?: boolean, updateDatabaseCache?: boolean
+}
 
-      resolve();
-    });
-  });
+export type GetDatabasesOptions = {
+  updateSessionCache?: boolean
 }
 
-export function GetAllDatabases(): Promise<Array<string>> {
-  return new Promise<Array<string>>((resolve, reject) => {
-    console.log(AuthorizationHandler.instance().AccessToken());
-    fetch('https://api.graphpolaris.com/user/database', {
-      method: 'GET',
-      // credentials: 'same-origin',
-      headers: new Headers({
-        Authorization:
-          'Bearer ' + AuthorizationHandler.instance().AccessToken(),
-      }),
-    })
-      .then((response: Response) => {
-        // if (!response.ok) {
-        //   reject(response.statusText);
-        // }
-
-        return response.json();
-      })
-      .then((json: any) => {
-        console.log(json);
-        resolve(json.databases);
-      });
-  });
+export type DeleteDatabasesOptions = {
+  updateSessionCache?: boolean
 }
 
-export function DeleteDatabase(name: string): Promise<void> {
-  return new Promise((resolve, reject) => {
-    fetch('https://api.graphpolaris.com/user/database/' + name, {
-      method: 'DELETE',
+export const useDatabaseAPI = (domain: string) => {
+  const { accessToken } = useAuthorizationCache();
+  const cache = useSessionCache();
+  const dispatch = useAppDispatch();
+
+  function AddDatabase(
+    request: AddDatabaseRequest,
+    options: AddDatabaseOptions = {}
+  ): Promise<void> {
+    const { setAsCurrent = true, updateDatabaseCache = false } = options;
+    return new Promise((resolve, reject) => {
+      fetch(`https://${domain}/user/database`, {
+        method: 'POST',
+        credentials: 'same-origin',
+        headers: new Headers({
+          Authorization:
+            'Bearer ' + accessToken,
+        }),
+        body: JSON.stringify(request),
+      }).then((response: Response) => {
+        console.log(response);
+
+        if (!response.ok) {
+          reject(response.statusText);
+        }
+        if (setAsCurrent)
+          dispatch(updateCurrentDatabase(request.name));
+        if (updateDatabaseCache)
+          GetAllDatabases({ updateSessionCache: true });
+
+        resolve();
+      });
+    });
+  }
+
+  async function GetAllDatabases(options: GetDatabasesOptions = {}): Promise<Array<string>> {
+    const { updateSessionCache: updateDatabaseCache = true } = options;
+    console.log(accessToken);
+    const response = await fetch(`https://${domain}/user/database`, {
+      method: 'GET',
       credentials: 'same-origin',
       headers: new Headers({
         Authorization:
-          'Bearer ' + AuthorizationHandler.instance().AccessToken(),
+          'Bearer ' + accessToken,
       }),
-    }).then((response: Response) => {
-      if (!response.ok) {
-        reject(response.statusText);
-      }
+    });
+    console.log(response);
 
-      resolve();
+    if (!response.ok) {
+      const text = await response.text();
+      console.error(text);
+      return [];
+      // throw Error(response.statusText)
+    }
+    const json = await response.json();
+    if (updateDatabaseCache)
+      dispatch(updateDatabaseList(json.databases));
+    return json.databases;
+  }
+
+  function DeleteDatabase(name: string, options: DeleteDatabasesOptions = {}): Promise<void> {
+    const { updateSessionCache: updateDatabaseCache = true } = options;
+    return new Promise((resolve, reject) => {
+      fetch(`https://${domain}/user/database/` + name, {
+        method: 'DELETE',
+        credentials: 'same-origin',
+        headers: new Headers({
+          Authorization:
+            'Bearer ' + accessToken,
+        }),
+      }).then((response: Response) => {
+        if (!response.ok) {
+          reject(response.statusText);
+        }
+
+        if (updateDatabaseCache)
+          GetAllDatabases({ updateSessionCache: true });
+
+        resolve();
+      });
     });
-  });
-}
+  }
+
+  return { DatabaseType, AddDatabase, GetAllDatabases, DeleteDatabase }
+};
\ No newline at end of file
diff --git a/libs/shared/lib/data-access/api/index.ts b/libs/shared/lib/data-access/api/index.ts
index eed07d0e9..a378760c1 100644
--- a/libs/shared/lib/data-access/api/index.ts
+++ b/libs/shared/lib/data-access/api/index.ts
@@ -1,3 +1,4 @@
 export * from './database'
 export * from './user'
-export * from './schema'
\ No newline at end of file
+export * from './schema'
+export * from './query'
\ No newline at end of file
diff --git a/libs/shared/lib/data-access/api/query.ts b/libs/shared/lib/data-access/api/query.ts
new file mode 100644
index 000000000..36aeeb0b9
--- /dev/null
+++ b/libs/shared/lib/data-access/api/query.ts
@@ -0,0 +1,52 @@
+// All database related API calls
+
+import { BackendQueryFormat } from "../../querybuilder/query-utils/BackendQueryFormat";
+import { useAuthorizationCache, useSessionCache } from "../store";
+
+export const useQueryAPI = (domain: string) => {
+    const cache = useSessionCache();
+    const { accessToken } = useAuthorizationCache();
+
+    async function execute(query: BackendQueryFormat) {
+
+        const response = await fetch(`https://${domain}/query/execute/`, {
+            method: 'POST',
+            credentials: 'same-origin',
+            headers: new Headers({
+                Authorization:
+                    'Bearer ' + accessToken,
+            }),
+            body: JSON.stringify(query)
+        });
+
+        if (!response?.ok) {
+            const ret = await response.text();
+            console.error(response, ret);
+        }
+        const ret = await response.json();
+        console.log('Sent Query EXECUTION', ret);
+    }
+
+    async function retrieveCachedQuery(queryID: string) {
+        // TODO: check if this method is needed!
+
+        // const response = await fetch(`https://${domain}/query/retrieve-cached/`, {
+        //     method: 'POST',
+        //     credentials: 'same-origin',
+        //     headers: new Headers({
+        //         Authorization:
+        //             'Bearer ' + accessToken,
+        //     }),
+        //     body: JSON.stringify({ queryID })
+        // });
+
+        // if (!response?.ok) {
+        //     const ret = await response.text();
+        //     console.error(response, ret);
+        // }
+        // // const ret = await response.json();
+        // console.log(response);
+    }
+
+    return { execute, retrieveCachedQuery };
+};
diff --git a/libs/shared/lib/data-access/api/schema.ts b/libs/shared/lib/data-access/api/schema.ts
index 004a8b8e8..02495e7ca 100644
--- a/libs/shared/lib/data-access/api/schema.ts
+++ b/libs/shared/lib/data-access/api/schema.ts
@@ -1,7 +1,41 @@
 // All database related API calls
 
-import { AuthorizationHandler } from '@graphpolaris/shared/lib/data-access/authorization';
+import { useAuthorizationCache, useSessionCache } from "../store";
 
+export const useSchemaAPI = (domain: string) => {
+    const cache = useSessionCache();
+    const { accessToken } = useAuthorizationCache();
 
-export function RequestSchema(databaseName: string) {
-}
\ No newline at end of file
+
+    async function RequestSchema(databaseName?: string) {
+        if (!databaseName) databaseName = cache.currentDatabase;
+        if (!databaseName) {
+            throw Error('Must call with a database name');
+        }
+
+        const request = {
+            databaseName,
+            cached: false,
+        };
+
+        const response = await fetch(`https://${domain}/schema/`, {
+            method: 'POST',
+            credentials: 'same-origin',
+            headers: new Headers({
+                Authorization:
+                    'Bearer ' + accessToken,
+            }),
+            body: JSON.stringify(request)
+        });
+
+        if (!response?.ok) {
+            const ret = await response.text();
+            console.error(response, ret);
+        }
+        // const ret = await response.json();
+        console.log(response);
+
+    }
+
+    return { RequestSchema };
+};
diff --git a/libs/shared/lib/data-access/api/user.ts b/libs/shared/lib/data-access/api/user.ts
index 825538a82..30a2a6f2a 100644
--- a/libs/shared/lib/data-access/api/user.ts
+++ b/libs/shared/lib/data-access/api/user.ts
@@ -1,6 +1,6 @@
 // All user related API calls
 
-import { AuthorizationHandler } from '@graphpolaris/shared/lib/data-access/authorization';
+import { useAuthorizationCache } from "../store";
 
 export type User = {
   Name: string;
@@ -8,29 +8,35 @@ export type User = {
   SignInProvider: number;
 };
 
-export function GetUserInfo(): Promise<User> {
-  return new Promise<User>((resolve, reject) => {
-    const auth = AuthorizationHandler.instance();
-    fetch('https://api.graphpolaris.com/user/', {
-      method: 'GET',
-      credentials: 'same-origin',
-      headers: new Headers({
-        Authorization: 'Bearer ' + auth.AccessToken(),
-      }),
-    })
-      .then((response: Response) => {
-        if (!response.ok) {
-          reject(response.statusText);
-        }
+export const useUserAPI = (domain: string) => {
+  const { accessToken } = useAuthorizationCache();
 
-        return response.json();
+  function GetUserInfo(): Promise<User> {
+    return new Promise<User>((resolve, reject) => {
+      fetch(`https://${domain}/user/`, {
+        method: 'GET',
+        credentials: 'same-origin',
+        headers: new Headers({
+          Authorization: 'Bearer ' + accessToken,
+        }),
       })
-      .then((json: any) => {
-        resolve({
-          Name: json.name,
-          Email: json.email,
-          SignInProvider: json.sign_in_provider,
+        .then((response: Response) => {
+          if (!response.ok) {
+            reject(response.statusText);
+          }
+
+          return response.json();
+        })
+        .then((json: any) => {
+          resolve({
+            Name: json.name,
+            Email: json.email,
+            SignInProvider: json.sign_in_provider,
+          });
         });
-      });
-  });
-}
+    });
+  }
+
+
+  return { GetUserInfo };
+};
diff --git a/libs/shared/lib/data-access/authorization/authorizationHandler.ts b/libs/shared/lib/data-access/authorization/authorizationHandler.ts
deleted file mode 100644
index d8f2d1b74..000000000
--- a/libs/shared/lib/data-access/authorization/authorizationHandler.ts
+++ /dev/null
@@ -1,187 +0,0 @@
-export class AuthorizationHandler {
-  private static _instance: AuthorizationHandler;
-  private accessToken = '';
-  private authorized = false;
-  private userID = '';
-  private sessionID = '';
-  private callback?: () => void = undefined;
-
-  // instance gets the AuthorizationHandler singleton instance
-  public static instance(): AuthorizationHandler {
-    if (!AuthorizationHandler._instance) {
-      AuthorizationHandler._instance = new AuthorizationHandler();
-    }
-
-    return AuthorizationHandler._instance;
-  }
-
-  // SetCallback sets a function that will be called when the auth state changes
-  // TODO: This should be done with a store or custom hook
-  public setCallback(callback: () => void) {
-    this.callback = callback;
-  }
-
-  // MARK: Authorization code
-
-  /**
-   * Authorize attempts to authorize using a refresh-token set as a cookie. If the user has been inactive for more than 7 days this cookie will be gone.
-   * @returns true is authorization was successful, else returns false
-   */
-  public async Authorize(): Promise<boolean> {
-    // Attempt to log in with a refresh-token
-    const authResponse = await this.getNewAccessToken();
-
-    // If the request was a success, we have an accessToken, userID and sessionID
-    if (authResponse.success) {
-      // Store them
-      this.userID = authResponse.userID ?? '';
-      this.sessionID = authResponse.sessionID ?? '';
-
-      this.SetAccessToken(authResponse.accessToken ?? '');
-    }
-
-    return new Promise((resolve) => {
-      resolve(authResponse.success);
-    });
-  }
-
-  /**
-   * getNewAccessToken gets a new access token using the refresh-token cookie
-   * @returns an authResponse containing details
-   */
-  private async getNewAccessToken(): Promise<authResponse> {
-    // If we have an access token already, append it to the url as a query param to keep sessionID the same
-    let url = 'https://api.graphpolaris.com/auth/refresh';
-    if (this.accessToken != '') {
-      url += '?access_token=' + this.accessToken;
-    }
-
-    return new Promise<authResponse>((resolve) => {
-      fetch(url, {
-        method: 'GET',
-        credentials: 'include',
-      })
-        .then((response) => {
-          if (!response.ok) {
-            throw Error(response.statusText);
-          }
-          return response.json();
-        })
-        .then((responseJSON) => {
-          resolve({
-            success: true,
-            accessToken: responseJSON.accessToken,
-            userID: responseJSON.userID,
-            sessionID: responseJSON.sessionID,
-          });
-        })
-        .catch(() => {
-          // User is not authorized
-          resolve({ success: false });
-        });
-    });
-  }
-
-  /**
-   * refreshTokens refreshes tokens
-   */
-  private async refreshTokens() {
-    console.log('refreshing tokens');
-    // Get a new access + refresh token pair
-    const authResponse = await this.getNewAccessToken();
-
-    if (authResponse.success) {
-      // Set the new access token
-      this.accessToken = authResponse.accessToken ?? '';
-
-      if (this.accessToken !== '') {
-        // Initialise the new refresh token
-        this.initializeRefreshToken();
-      }
-    }
-  }
-
-  /**
-   * initializeRefreshToken attempts to initialize a refresh token
-   */
-  private async initializeRefreshToken() {
-    fetch(
-      'https://api.graphpolaris.com/auth/refresh?access_token=' +
-      this.accessToken,
-      {
-        method: 'GET',
-        credentials: 'include',
-      }
-    )
-      .then((response) => {
-        if (!response.ok) {
-          throw Error(response.statusText);
-        }
-      })
-      .catch((error) => {
-        console.error(error);
-      });
-  }
-
-  // MARK: Getters
-
-  /**
-   * Authorized returns the current authorization status
-   * @returns true if authorized
-   */
-  Authorized(): boolean {
-    return this.authorized;
-  }
-
-  /**
-   * AccessToken returns the current access token
-   * @returns token
-   */
-  AccessToken(): string {
-    return this.accessToken;
-  }
-
-  /**
-   * UserID returns the current user' ID
-   * @returns id
-   */
-  UserID(): string {
-    return this.userID;
-  }
-
-  /**
-   * SessionID returns the current session' ID
-   * @returns id
-   */
-  SessionID(): string {
-    return this.sessionID;
-  }
-
-  // MARK: Setters
-  /**
-   * SetAccessToken sets the current access token (should only be called by the sign-in component)
-   * @param accessToken
-   */
-  async SetAccessToken(accessToken: string) {
-    this.accessToken = accessToken;
-
-    // Activate the refresh token
-    this.initializeRefreshToken();
-
-    // Start the automatic refreshing every 10 minutes
-    setInterval(() => {
-      this.refreshTokens();
-    }, 10 * 60 * 1000);
-
-    if (this.callback) {
-      this.callback();
-    }
-  }
-}
-
-type authResponse = {
-  success: boolean;
-  accessToken?: string;
-  userID?: string;
-  sessionID?: string;
-};
diff --git a/libs/shared/lib/data-access/authorization/authorizationHook.tsx b/libs/shared/lib/data-access/authorization/authorizationHook.tsx
index 4110005e8..76fce13fa 100644
--- a/libs/shared/lib/data-access/authorization/authorizationHook.tsx
+++ b/libs/shared/lib/data-access/authorization/authorizationHook.tsx
@@ -1,41 +1,185 @@
 import { useEffect, useState } from 'react';
-import { AuthorizationHandler } from '@graphpolaris/shared/lib/data-access';
+import { useAppDispatch, useAuthorizationCache } from '../store';
+import { dispatch } from 'd3';
+import { authorized, updateAccessToken } from '../store/authSlice';
 
-interface useIsAuthorizedState {
-  userAuthorized: boolean;
-  userId?: string;
-  sessionId?: string;
-}
+/**
+ * getNewAccessToken gets a new access token using the refresh-token cookie
+ * @returns an authResponse containing details
+ */
+async function getNewAccessToken(
+  domain: string,
+  accessToken: string
+): Promise<authResponse> {
+  // If we have an access token already, append it to the url as a query param to keep sessionID the same
+  let url = `https://${domain}/auth/refresh`;
+  if (accessToken != '') {
+    url += '?access_token=' + accessToken;
+  }
 
-export function useAuthorization() {
-  const [state, setState] = useState<useIsAuthorizedState>({
-    userAuthorized: false,
-  });
-  const authInstance = AuthorizationHandler.instance();
-
-  const authCallback = async () => {
-    setState({
-      userAuthorized: authInstance.Authorized(),
-      userId: authInstance.UserID(),
-      sessionId: authInstance.SessionID(),
+  try {
+    const response = await fetch(url, {
+      method: 'GET',
+      credentials: 'include',
     });
 
-    // Print the user that is currently logged in
-    // const user = await GetUserInfo();
-    // console.log(user);
-  };
+    if (!response.ok) {
+      // User is not authorized
+      const text = await response.text();
+      console.error('User UNAUTHORIZED', text);
 
-  authInstance.setCallback(authCallback);
+      return { success: false };
+      // throw Error(response.statusText);
+    }
 
-  // Attempt to Authorize the user
-  const authorize = async () => {
-    const authorized = await authInstance.Authorize();
-    if (authorized) authCallback();
-  };
+    const json = await response.json();
+    return {
+      success: true,
+      accessToken: json.accessToken,
+      userID: json.userID,
+      sessionID: json.sessionID,
+    };
+  } catch (error) {
+    console.error('Error authorizing user', error);
+    return { success: false };
+  }
+}
+
+type authResponse = {
+  success: boolean;
+  accessToken?: string;
+  userID?: string;
+  sessionID?: string;
+};
+
+export type useIsAuthorizedState = {
+  userID?: string;
+  sessionID?: string;
+  accessToken: string;
+  authorized: boolean;
+};
+
+export function useAuthorization(domain: string) {
+  const dispatch = useAppDispatch();
+  const auth = useAuthorizationCache();
+
+  /**
+   * refreshTokens refreshes tokens
+   */
+  async function refreshTokens() {
+    console.log('refreshing tokens');
+    // Get a new access + refresh token pair
+    const authResponse = await getNewAccessToken(domain, auth.accessToken);
+
+    if (authResponse.success) {
+      // Set the new access token
+      dispatch(updateAccessToken(authResponse.accessToken ?? ''));
+
+      if (auth.accessToken !== '') {
+        // Initialize the new refresh token
+        initializeRefreshToken();
+      }
+    }
+  }
 
-  useEffect(() => {
-    authorize();
-  }, []);
+  // /**
+  //  * initializeRefreshToken attempts to initialize a refresh token
+  //  */
+  // async function refreshRefreshToken() {
+  //   fetch(
+  //     'https://${domain}/auth/refresh?access_token=' +
+  //       state.accessToken,
+  //     {
+  //       method: 'GET',
+  //       credentials: 'include',
+  //     }
+  //   )
+  //     .then((response) => {
+  //       if (!response.ok) {
+  //         throw Error(response.statusText);
+  //       }
+  //     })
+  //     .catch((error) => {
+  //       console.error(error);
+  //     });
+  // }
 
-  return state;
+  /**
+   * initializeRefreshToken attempts to initialize a refresh token
+   */
+  async function initializeRefreshToken() {
+    fetch(`https://${domain}/auth/refresh`, {
+      method: 'POST',
+      credentials: 'include',
+    })
+      .then((response) => {
+        if (!response.ok) {
+          console.error(response.statusText);
+          // throw Error(response.statusText);
+        }
+      })
+      .catch((error) => {
+        console.error(error);
+      });
+  }
+
+  // MARK: Setters
+  /**
+   * SetAccessToken sets the current access token (should only be called by the sign-in component)
+   * @param accessToken
+   */
+  async function SetAccessToken(accessToken: string) {
+    dispatch(updateAccessToken(accessToken));
+    console.log('set access token', auth.accessToken);
+
+    const result = await AuthorizeFromCache();
+    if (result) {
+      // Activate the refresh token TODO
+      initializeRefreshToken();
+
+      // Start the automatic refreshing every 10 minutes
+      setInterval(() => {
+        refreshTokens();
+      }, 10 * 60 * 1000);
+    } else {
+      console.error(
+        'Failed Logging in due to refresh token initialization',
+        result
+      );
+      // throw Error('Failed Logging in');
+    }
+  }
+
+  /**
+   * Authorize attempts to authorize using a refresh-token set as a cookie. If the user has been inactive for more than 7 days this cookie will be gone.
+   * @returns true is authorization was successful, else returns false
+   */
+  async function AuthorizeFromCache(): Promise<boolean> {
+    // Attempt to log in with a refresh-token
+    const authResponse = await getNewAccessToken(domain, auth.accessToken);
+
+    // If the request was a success, we have an accessToken, userID and sessionID
+    if (authResponse.success) {
+      console.log('SUCCESS authorize');
+
+      console.log(auth);
+      // Store them
+      await dispatch(
+        authorized({
+          userID: authResponse.userID ?? '',
+          sessionID: authResponse.sessionID ?? '',
+          accessToken: authResponse.accessToken ?? '',
+          authorized: true,
+        })
+      );
+    }
+
+    return authResponse.success;
+  }
+
+  return {
+    AuthorizeFromCache,
+    SetAccessToken,
+    auth,
+  };
 }
diff --git a/libs/shared/lib/data-access/authorization/index.ts b/libs/shared/lib/data-access/authorization/index.ts
index 0cf2dd57d..521daf509 100644
--- a/libs/shared/lib/data-access/authorization/index.ts
+++ b/libs/shared/lib/data-access/authorization/index.ts
@@ -1,2 +1 @@
-export { AuthorizationHandler } from './authorizationHandler';
-export * from './authorizationHook';
+export * from './authorizationHook';
\ No newline at end of file
diff --git a/libs/shared/lib/data-access/socket/DELETE-repository-interfaces/DatabaseRepository.tsx b/libs/shared/lib/data-access/socket/DELETE-repository-interfaces/DatabaseRepository.tsx
new file mode 100644
index 000000000..e1d3a49b2
--- /dev/null
+++ b/libs/shared/lib/data-access/socket/DELETE-repository-interfaces/DatabaseRepository.tsx
@@ -0,0 +1,36 @@
+/**
+ * This program has been developed by students from the bachelor Computer Science at
+ * Utrecht University within the Software Project course.
+ * © Copyright Utrecht University (Department of Information and Computing Sciences)
+ */
+
+/** Contains interface for adding user databases and fetching the connected databases */
+export type Database = {
+  // The user specified database, can be anything
+  databaseName: string;
+  username: string;
+  password: string;
+  port: number;
+  hostname: string;
+  // The database for the internal database
+  internalDatabaseName: string;
+  // The database type vendor, currently only arangodb and neo4j are supported
+  databaseType: string;
+  // Password for speficied databasehost
+};
+
+// Interface for a database api repository
+export default interface DatabaseRepository {
+  /**
+   * Adds a database for the currently logged in user.
+   * @param database {Database} The database to add for the user.
+   * @returns A promise with a standard Response.
+   */
+  addDatabase(database: Database): Promise<Response>;
+
+  /**
+   * Fetches the currently connected database of the user.
+   * @returns The database names.
+   */
+  fetchUserDatabases(): Promise<string[]>;
+}
diff --git a/libs/shared/lib/data-access/socket/DELETE-repository-interfaces/QueryRepository.tsx b/libs/shared/lib/data-access/socket/DELETE-repository-interfaces/QueryRepository.tsx
new file mode 100644
index 000000000..244a61014
--- /dev/null
+++ b/libs/shared/lib/data-access/socket/DELETE-repository-interfaces/QueryRepository.tsx
@@ -0,0 +1,27 @@
+/**
+ * This program has been developed by students from the bachelor Computer Science at
+ * Utrecht University within the Software Project course.
+ * © Copyright Utrecht University (Department of Information and Computing Sciences)
+ */
+
+import { TranslatedJSONQuery } from './TranslatedJSONQuery';
+
+/** A type for a query to send to the backend. */
+export interface Query extends TranslatedJSONQuery {
+  databaseName: string;
+}
+
+export default interface QueryRepository {
+  /**
+   * Sends a query to the database to execute.
+   * @param query {Query} The query to execute.
+   * @returns A promise with the assigned queryID.
+   */
+  sendQuery(query: Query): Promise<string>;
+
+  /**
+   * Sends a message to the backend to retrieve a cached query
+   * @param queryID {string} The id of the query to retrieve.
+   */
+  retrieveCachedQuery(queryID: string): Promise<Response>;
+}
diff --git a/libs/shared/lib/data-access/socket/DELETE-repository-interfaces/TranslatedJSONQuery.tsx b/libs/shared/lib/data-access/socket/DELETE-repository-interfaces/TranslatedJSONQuery.tsx
new file mode 100644
index 000000000..7d645f745
--- /dev/null
+++ b/libs/shared/lib/data-access/socket/DELETE-repository-interfaces/TranslatedJSONQuery.tsx
@@ -0,0 +1,85 @@
+/**
+ * This program has been developed by students from the bachelor Computer Science at
+ * Utrecht University within the Software Project course.
+ * © Copyright Utrecht University (Department of Information and Computing Sciences)
+ */
+
+/** JSON query format used to send a query to the backend. */
+export interface TranslatedJSONQuery {
+  return: {
+    entities: number[];
+    relations: number[];
+    groupBys: number[];
+  };
+  entities: Entity[];
+  relations: Relation[];
+  groupBys: GroupBy[];
+  machineLearning: MachineLearning[];
+  limit: number;
+}
+
+/** Interface for an entity in the JSON for the query. */
+export interface Entity {
+  name: string;
+  ID: number;
+  constraints: Constraint[];
+}
+
+/** Interface for an relation in the JSON for the query. */
+export interface Relation {
+  name: string;
+  ID: number;
+  fromType: string;
+  fromID: number;
+  toType: string;
+  toID: number;
+  depth: { min: number; max: number };
+  constraints: Constraint[];
+}
+
+/**
+ * Constraint datatypes created from the attributes of a relation or entity.
+ *
+ * string MatchTypes: exact/contains/startswith/endswith.
+ * int    MatchTypes: GT/LT/EQ.
+ * bool   MatchTypes: EQ/NEQ.
+ */
+export interface Constraint {
+  attribute: string;
+  dataType: string;
+
+  matchType: string;
+  value: string;
+}
+
+/** Interface for a function in the JSON for the query. */
+export interface GroupBy {
+  ID: number;
+
+  groupType: string;
+  groupID: number[];
+  groupAttribute: string;
+
+  byType: string;
+  byID: number[];
+  byAttribute: string;
+
+  appliedModifier: string;
+  relationID: number;
+  constraints: Constraint[];
+}
+
+/** Interface for Machine Learning algorithm */
+export interface MachineLearning {
+  ID?: number;
+  queuename: string;
+  parameters: string[];
+}
+/** Interface for what the JSON needs for link predicition */
+export interface LinkPrediction {
+  queuename: string;
+  parameters: {
+    key: string;
+    value: string;
+  }[];
+}
diff --git a/libs/shared/lib/data-access/socket/backend-message-receiver/BackendMessageReceiver.tsx b/libs/shared/lib/data-access/socket/backend-message-receiver/BackendMessageReceiver.tsx
new file mode 100644
index 000000000..5d1395012
--- /dev/null
+++ b/libs/shared/lib/data-access/socket/backend-message-receiver/BackendMessageReceiver.tsx
@@ -0,0 +1,12 @@
+/**
+ * This program has been developed by students from the bachelor Computer Science at
+ * Utrecht University within the Software Project course.
+ * © Copyright Utrecht University (Department of Information and Computing Sciences)
+ */
+
+/** Interface for receiving messages from the backend */
+export default interface BackendMessageReceiver {
+  connect(onOpen: () => void): void;
+  close(): void;
+  isConnected(): boolean;
+}
diff --git a/libs/shared/lib/data-access/socket/backend-message-receiver/BackendMessageReceiverMock.tsx b/libs/shared/lib/data-access/socket/backend-message-receiver/BackendMessageReceiverMock.tsx
new file mode 100644
index 000000000..3bb7c4480
--- /dev/null
+++ b/libs/shared/lib/data-access/socket/backend-message-receiver/BackendMessageReceiverMock.tsx
@@ -0,0 +1,31 @@
+/**
+ * This program has been developed by students from the bachelor Computer Science at
+ * Utrecht University within the Software Project course.
+ * © Copyright Utrecht University (Department of Information and Computing Sciences)
+ */
+/* istanbul ignore file */
+/* The comment above was added so the code coverage wouldn't count this file towards code coverage.
+ * We do not test mocks.
+ * See testing plan for more details.*/
+
+import BackendMessageReceiver from './BackendMessageReceiver';
+
+/** A mock for the backend message receiver used for testing purposes. */
+export default class BackendMessageReceiverMock
+  implements BackendMessageReceiver
+{
+  private connected = false;
+
+  public connect(): void {
+    this.connected = true;
+  }
+  public close(): void {
+    this.connected = false;
+  }
+
+  public onMessage(msg: string): void {}
+
+  public isConnected(): boolean {
+    return this.connected;
+  }
+}
diff --git a/libs/shared/lib/data-access/socket/backend-message-receiver/BackendMessengerRepository.tsx b/libs/shared/lib/data-access/socket/backend-message-receiver/BackendMessengerRepository.tsx
new file mode 100644
index 000000000..1b78ea3f0
--- /dev/null
+++ b/libs/shared/lib/data-access/socket/backend-message-receiver/BackendMessengerRepository.tsx
@@ -0,0 +1,13 @@
+/**
+ * This program has been developed by students from the bachelor Computer Science at
+ * Utrecht University within the Software Project course.
+ * © Copyright Utrecht University (Department of Information and Computing Sciences)
+ */
+
+/** Interface for sending messages to the backend. */
+export default interface BackendMessengerRepository {
+  /** throws {Error} if credentials have not passed. */
+  SendMessage(body: string, request: string, method: string): Promise<Response>;
+
+  SendRequest(request: string): Promise<Response>;
+}
diff --git a/libs/shared/lib/data-access/socket/backend-message-receiver/WebSocketHandler.test.tsx b/libs/shared/lib/data-access/socket/backend-message-receiver/WebSocketHandler.test.tsx
new file mode 100644
index 000000000..3f2e1dfa2
--- /dev/null
+++ b/libs/shared/lib/data-access/socket/backend-message-receiver/WebSocketHandler.test.tsx
@@ -0,0 +1,28 @@
+/**
+ * This program has been developed by students from the bachelor Computer Science at
+ * Utrecht University within the Software Project course.
+ * © Copyright Utrecht University (Department of Information and Computing Sciences)
+ */
+import { describe, expect, it } from 'vitest';
+
+import { WebSocketHandler } from './WebSocketHandler';
+
+//FIXME
+describe('WebSocketHandler', () => {
+  //   let websockethandler = new WebSocketHandler('placeholderName');
+
+  //   it('Should connect', () => {
+  //     websockethandler.connect(websockethandler.close);
+  //     expect(websockethandler.isConnected()).toEqual(true);
+  //   });
+
+  //   it('Should disconnect', () => {
+  //     websockethandler.connect(websockethandler.close);
+  //     websockethandler.close();
+  //     expect(websockethandler.isConnected()).toEqual(false);
+  //   });
+
+  it('pass', () => {
+    expect(false).toEqual(false);
+  });
+});
diff --git a/libs/shared/lib/data-access/socket/backend-message-receiver/WebSocketHandler.tsx b/libs/shared/lib/data-access/socket/backend-message-receiver/WebSocketHandler.tsx
new file mode 100644
index 000000000..7645f8c00
--- /dev/null
+++ b/libs/shared/lib/data-access/socket/backend-message-receiver/WebSocketHandler.tsx
@@ -0,0 +1,84 @@
+/**
+ * This program has been developed by students from the bachelor Computer Science at
+ * Utrecht University within the Software Project course.
+ * © Copyright Utrecht University (Department of Information and Computing Sciences)
+ */
+
+import Broker from '../broker';
+import BackendMessageReceiver from './BackendMessageReceiver';
+
+/** The websockethandler creates a websocket and wait for messages send to the socket. */
+export class WebSocketHandler implements BackendMessageReceiver {
+  private webSocket: WebSocket | undefined;
+  private url: string;
+  private connected: boolean;
+  private token: string | undefined;
+
+  /** @param domain The domain to make the websocket connection with. */
+  public constructor(domain: string) {
+    this.url = 'wss://' + domain + '/socket/';
+    this.connected = false;
+  }
+
+  public useToken(token: string): WebSocketHandler {
+    this.token = token;
+    return this;
+  }
+
+  /**
+   * Create a websocket to the given URL.
+   * @param {string} URL is the URL to which the websocket connection is opened.
+   */
+  public connect(onOpen: () => void): void {
+    // If there already is already a current websocket connection, close it first.
+    if (this.webSocket) this.close();
+
+    this.webSocket = new WebSocket(
+      this.url + (!!this.token ? `?jwt=${this.token}` : '')
+    );
+    this.webSocket.onopen = () => onOpen();
+    this.webSocket.onmessage = this.onWebSocketMessage;
+    this.webSocket.onerror = this.onError;
+    this.webSocket.onclose = this.onClose;
+
+    this.connected = true;
+  }
+
+  /** Closes the current websocket connection. */
+  public close = (): void => {
+    if (this.webSocket) this.webSocket.close();
+    this.connected = false;
+  };
+
+  /** @returns A boolean which indicates if there currently is a socket connection. */
+  public isConnected = (): boolean => {
+    return this.connected;
+  };
+
+  /**
+   * Websocket connection close event handler.
+   * @param {any} event Contains the event data.
+   */
+  private onClose(event: any): void {
+    console.log(event.data);
+  }
+
+  /**
+   * Websocket connection message event handler. Called if a new message is received through the socket.
+   * @param {any} event Contains the event data.
+   */
+  public onWebSocketMessage = (event: MessageEvent<any>) => {
+    let data = JSON.parse(event.data);
+    console.log('WS message: ', data);
+
+    Broker.instance().publish(data.value, data.type);
+  };
+
+  /**
+   * Websocket connection error event handler.
+   * @param {any} event contains the event data.
+   */
+  private onError(event: any): void {
+    console.log(event);
+  }
+}
diff --git a/libs/shared/lib/data-access/socket/backend-message-receiver/index.ts b/libs/shared/lib/data-access/socket/backend-message-receiver/index.ts
new file mode 100644
index 000000000..1690ebf99
--- /dev/null
+++ b/libs/shared/lib/data-access/socket/backend-message-receiver/index.ts
@@ -0,0 +1 @@
+export * from './WebSocketHandler'
\ No newline at end of file
diff --git a/libs/shared/lib/data-access/socket/backend-messenger/BackendMessenger.test.tsx b/libs/shared/lib/data-access/socket/backend-messenger/BackendMessenger.test.tsx
new file mode 100644
index 000000000..3d347168e
--- /dev/null
+++ b/libs/shared/lib/data-access/socket/backend-messenger/BackendMessenger.test.tsx
@@ -0,0 +1,18 @@
+/**
+ * This program has been developed by students from the bachelor Computer Science at
+ * Utrecht University within the Software Project course.
+ * © Copyright Utrecht University (Department of Information and Computing Sciences)
+ */
+import { assert, describe, expect, it, vi } from 'vitest';
+
+import BackendMessenger from '.';
+describe('BackendMessenger', () => {
+  let placeholderDomain = 'placeholderDomain';
+  let backendMessenger = new BackendMessenger(placeholderDomain);
+
+  it('should create the the correct url ', () => {
+    expect(backendMessenger['url']).toEqual(
+      'https://' + placeholderDomain + '/'
+    );
+  });
+});
diff --git a/libs/shared/lib/data-access/socket/backend-messenger/BackendMessengerMock.tsx b/libs/shared/lib/data-access/socket/backend-messenger/BackendMessengerMock.tsx
new file mode 100644
index 000000000..ada2ac8a9
--- /dev/null
+++ b/libs/shared/lib/data-access/socket/backend-messenger/BackendMessengerMock.tsx
@@ -0,0 +1,87 @@
+/**
+ * This program has been developed by students from the bachelor Computer Science at
+ * Utrecht University within the Software Project course.
+ * © Copyright Utrecht University (Department of Information and Computing Sciences)
+ */
+
+/* istanbul ignore file */
+/* The comment above was added so the code coverage wouldn't count this file towards code coverage.
+ * We do not test mock implementations.
+ * See testing plan for more details.*/
+
+import BackendMessengerRepository from '../../../domain/repository-interfaces/BackendMessengerRepository';
+
+/** Mock repository used for testing. */
+export default class BackendMessengerMock implements BackendMessengerRepository {
+  /** A mock version of the sendMessage function sends a fake message. */
+  private url: string;
+
+  /** @param domain The domain to send the messages towards. */
+  constructor(domain: string) {
+    this.url = 'https://' + domain + '/';
+  }
+  public SendMessage(body: string, requestURL: string, method: string): Promise<Response> {
+    switch (requestURL) {
+      case 'query/execute/':
+        return new Promise((resolve, reject) => {
+          if (body == 'validjsonquery') resolve(this.createResponse({ queryID: 'testID' }));
+          else if (body == 'no200response') resolve(new Response(undefined, { status: 404 }));
+          else if (body == 'notvalidjsonquery') reject('invalid body');
+          else resolve(this.createResponse({ ok: 'ok' }));
+        });
+      case 'query/retrieve-cached/':
+        return new Promise((resolve, reject) => {
+          const b = JSON.parse(body);
+          if ('queryID' in b) resolve(this.createResponse({}));
+          else reject('invalid body');
+        });
+      default:
+        return new Promise((resolve) => {
+          resolve(this.createResponse({ ok: 'ok' }));
+        });
+    }
+  }
+
+  /** A mock version of the sendRequest function sends a fake request. */
+  public SendRequest(requestURL: string): Promise<Response> {
+    switch (requestURL) {
+      case 'user/callback':
+        return new Promise((resolve) => {
+          const result = {
+            clientID: 'mock-clientID',
+          };
+          resolve(this.createResponse(result));
+        });
+
+      case 'user/session/':
+        return new Promise((resolve) => {
+          const result = {
+            clientID: 'mock-clientID',
+            sessionID: 'mock-sessionID',
+          };
+          resolve(this.createResponse(result));
+        });
+      case 'user/databases/':
+        return new Promise((resolve) => {
+          resolve(this.createResponse({ databases: ['testdb'] }));
+        });
+      default:
+        return new Promise((resolve) => {
+          resolve(new Response(undefined, { status: 404 }));
+        });
+    }
+  }
+
+  /** A mock response created to test the backend messenger. */
+  private createResponse(body: any): Response {
+    const meta = {
+      'Content-Type': 'application/json',
+    };
+    const headers = new Headers(meta);
+    const responseInit = { status: 200, headers: headers };
+
+    const response = new Response(JSON.stringify(body), responseInit);
+
+    return response;
+  }
+}
diff --git a/libs/shared/lib/data-access/socket/backend-messenger/index.tsx b/libs/shared/lib/data-access/socket/backend-messenger/index.tsx
new file mode 100644
index 000000000..d0633bcab
--- /dev/null
+++ b/libs/shared/lib/data-access/socket/backend-messenger/index.tsx
@@ -0,0 +1,64 @@
+/**
+ * This program has been developed by students from the bachelor Computer Science at
+ * Utrecht University within the Software Project course.
+ * © Copyright Utrecht University (Department of Information and Computing Sciences)
+ */
+
+import BackendMessengerRepository from '../backend-message-receiver/BackendMessengerRepository';
+
+/** Sends messages and requests to the backend. */
+export default class BackendMessenger implements BackendMessengerRepository {
+  private url: string;
+
+  /** @param domain The domain to send the messages towards. */
+  constructor(domain: string) {
+    this.url = 'https://' + domain + '/';
+  }
+
+  /**
+   * Sends a fetch request to the Datastrophe domain.
+   * @param {string} body The request body you want to send. Should be stringified JSON.
+   * @param {string} requestURL The URL you want to perform this request to, appended to 'datastrophe.science.uu.nl'.
+   * @param {string} requestMethod The method of your request. Most used are: POST, GET.
+   * @returns {Promise<void>} A promise which is resolved once a response with status 200 has been received.
+   */
+  public SendMessage(
+    body: string,
+    requestURL: string,
+    requestMethod: string
+  ): Promise<Response> {
+    // Construct the URL we will request from
+    const req = this.url + requestURL;
+
+    return fetch(req, {
+      method: requestMethod,
+      headers: new Headers({
+        'Content-Type': 'application/json',
+      }),
+      // Pass this parameter to always send authorization cookies, even on localhost
+      credentials: 'include',
+      // Set the body of the request, if this is not a POST request, body is set to undefined
+      body: requestMethod == 'POST' ? body : undefined,
+      // Wait for the fetch promise to resolve
+    });
+  }
+
+  /**
+   * Sendrequest sends a GET request to the backend.
+   * @param requestURL The URL you want to perform this request to, appended to 'datastrophe.science.uu.nl'.
+   * @returns {Promise<void>} A promise which is resolved once a response with status 200 has been received.
+   */
+  public SendRequest(requestURL: string): Promise<Response> {
+    // Construct the URL we will request from
+    const req = this.url + requestURL;
+    console.log('backendmessager: ' + req);
+    return fetch(req, {
+      method: 'GET',
+      headers: new Headers({
+        'Content-Type': 'application/json',
+      }),
+      // Pass this parameter to always send authorization cookies, even on localhost
+      credentials: 'include',
+    });
+  }
+}
diff --git a/libs/shared/lib/data-access/socket/broker/broker.test.tsx b/libs/shared/lib/data-access/socket/broker/broker.test.tsx
new file mode 100644
index 000000000..514d2bb8e
--- /dev/null
+++ b/libs/shared/lib/data-access/socket/broker/broker.test.tsx
@@ -0,0 +1,76 @@
+/**
+ * This program has been developed by students from the bachelor Computer Science at
+ * Utrecht University within the Software Project course.
+ * © Copyright Utrecht University (Department of Information and Computing Sciences)
+ */
+import { assert, describe, expect, it, vi } from 'vitest';
+
+import Broker from '.';
+
+// FIXME
+// /** Testsuite for the testing of the broker and the broker listeners */
+describe('broker', () => {
+  it('pass', () => {
+    expect(false).toEqual(false);
+  });
+
+  //   it('should correctly notify a listener with the correct routingkey', () => {
+  //     const consumeFuncMock = vi.fn();
+
+  //     // Subscribe the mock listener
+  //     Broker.instance().subscribe(consumeFuncMock, 'testkey', 'k1');
+  //     Broker.instance().publish({ test: 'hoi' }, 'testkey');
+  //     expect(consumeFuncMock.mock.calls[0][0]).toEqual({ test: 'hoi' });
+
+  //     // Test if it notifies our listener with a different routingkey
+  //     Broker.instance().publish({ test: 'hoi' }, 'differentKey');
+  //     expect(consumeFuncMock).toBeCalledTimes(1);
+
+  //     // When unsubscribed we shouldn't get notified
+  //     Broker.instance().unSubscribe('testkey', 'k1');
+  //     Broker.instance().publish({ test: 'hoi' }, 'testkey');
+  //     expect(consumeFuncMock).toBeCalledTimes(1);
+  //   });
+
+  //   it('should not throw when unsubscribing an already unsubbed listener', () => {
+  //     // Test unsubscribing a listener to a routing key it was not subscribed to.
+  //     expect(() =>
+  //       Broker.instance().unSubscribe('differentkey', 'k1')
+  //     ).not.toThrow();
+  //   });
+
+  //   it('should only subscribe a listener once to the same routing key', () => {
+  //     const consumeFuncMock = vi.fn();
+  //     // Subscribe the mock listener twice.
+  //     Broker.instance().subscribe(consumeFuncMock, 'testkey', 'k1', false);
+  //     Broker.instance().subscribe(consumeFuncMock, 'testkey', 'k2', false);
+  //     Broker.instance().publish({ test: 'hahaha' }, 'testkey');
+
+  //     expect(consumeFuncMock).toBeCalledTimes(1);
+
+  //     Broker.instance().unSubscribe('testkey', 'k1');
+  //   });
+
+  //   it('should console log when there are no listeners to publish to', () => {
+  //     const mockConsoleLog = vi.fn();
+  //     console.log = mockConsoleLog;
+
+  //     Broker.instance().publish({ test: 'hoi' }, 'testkey');
+
+  //     expect(mockConsoleLog).toBeCalledWith(
+  //       'no listeners for message with routing key %ctestkey',
+  //       'font-weight:bold; color: blue; background-color: white;',
+  //       { test: 'hoi' }
+  //     );
+  //   });
+
+  //   it('should notify when there is a previous message available after subscribing', () => {
+  //     const consumeFuncMock = vi.fn();
+  //     Broker.instance().publish({ test: 'âš½' }, 'testkey');
+
+  //     // Subscribe the mock listener twice.
+  //     Broker.instance().subscribe(consumeFuncMock, 'testkey', 'k1', true);
+
+  //     expect(consumeFuncMock.mock.calls[0][0]).toEqual({ test: 'âš½' });
+  //   });
+});
diff --git a/libs/shared/lib/data-access/socket/broker/index.tsx b/libs/shared/lib/data-access/socket/broker/index.tsx
new file mode 100644
index 000000000..b67aa0da0
--- /dev/null
+++ b/libs/shared/lib/data-access/socket/broker/index.tsx
@@ -0,0 +1,102 @@
+/**
+ * This program has been developed by students from the bachelor Computer Science at
+ * Utrecht University within the Software Project course.
+ * © Copyright Utrecht University (Department of Information and Computing Sciences)
+ */
+import BrokerListener from './BrokerListenerInterface';
+
+/**
+ * A broker that handles incoming messages from the backend.
+ * It works with routingkeys, a listener can subscribe to messages from the backend with a specific routingkey.
+ * Possible routingkeys:
+ * - query_result:              Contains an object with nodes and edges or a numerical result.
+ * - query_translation_result:  Contains the query translated to the database language.
+ * - schema_result:             Contains the schema of the users database.
+ * - query_status_update:       Contains an update to if a query is being executed.
+ * - query_database_error:      Contains the error received from the database.
+ * - query_sent:                Contains the message that a query has been send.
+ * - gsa_node_result:           Contains a node that has the data for the graph-schema-analytics
+ * - gsa_edge_result:           Contains a edge that has the data for the graph-schema-analytics
+ */
+export default class Broker {
+  private static singletonInstance: Broker;
+  private listeners: Record<string, Record<string, Function>> = {};
+
+  /** mostRecentMessages is a dictionary with <routingkey, messageObject>. It stores the most recent message for that routingkey. */
+  private mostRecentMessages: Record<string, unknown> = {};
+
+  /** Get the singleton instance of the Broker. */
+  public static instance(): Broker {
+    if (!this.singletonInstance) this.singletonInstance = new Broker();
+    return this.singletonInstance;
+  }
+
+  /**
+   * Notify all listeners which are subscribed with the specified routingkey.
+   * @param {unknown} jsonObject An json object with unknown type.
+   * @param {string} routingKey The routing to publish the message to.
+   */
+  public publish(jsonObject: unknown, routingKey: string): void {
+    this.mostRecentMessages[routingKey] = jsonObject;
+
+    if (
+      this.listeners[routingKey] &&
+      Object.keys(this.listeners[routingKey]).length != 0
+    )
+      Object.values(this.listeners[routingKey]).forEach((listener) =>
+        listener(jsonObject, routingKey)
+      );
+    // If there are no listeners, log the message
+    else
+      console.log(
+        `no listeners for message with routing key %c${routingKey}`,
+        'font-weight:bold; color: blue; background-color: white;',
+        jsonObject
+      );
+  }
+
+  /**
+   * Subscribe a listener to messages with the specified routingKey.
+   * @param {Function} newListener The listener to subscribe.
+   * @param {string} routingKey The routingkey to subscribe to.
+   * @param {boolean} consumeMostRecentMessage If true and there is a message for this routingkey available, notify the new listener. Default true.
+   */
+  public subscribe(
+    newListener: Function,
+    routingKey: string,
+    key: string = (Date.now() + Math.floor(Math.random() * 100)).toString(),
+    consumeMostRecentMessage: boolean = true
+  ): string {
+    if (!this.listeners[routingKey]) this.listeners[routingKey] = {};
+
+    // Don't add a listener twice
+    if (!(key in this.listeners[routingKey])) {
+      this.listeners[routingKey][key] = newListener;
+
+      // Consume the most recent message
+      if (consumeMostRecentMessage && routingKey in this.mostRecentMessages)
+        newListener(this.mostRecentMessages[routingKey], routingKey);
+    }
+
+    return key;
+  }
+
+  /**
+   * Unsubscribes a listener from messages with specified routingkey.
+   * @param {string} routingKey The routing key to unsubscribe from
+   * @param {string} listener key of the listener to unsubscribe.
+   */
+  public unSubscribe(routingKey: string, key: string): void {
+    if (this.listeners[routingKey] && key in this.listeners[routingKey]) {
+      delete this.listeners[routingKey][key];
+    }
+  }
+
+  /**
+   * Unsubscribes all listeners from messages with specified routingkey.
+   * @param {string} routingKey The routing key to unsubscribe from
+   */
+  public unSubscribeAll(routingKey: string): void {
+    this.listeners[routingKey] = {};
+  }
+}
diff --git a/libs/shared/lib/data-access/socket/index.ts b/libs/shared/lib/data-access/socket/index.ts
new file mode 100644
index 000000000..34802e1e5
--- /dev/null
+++ b/libs/shared/lib/data-access/socket/index.ts
@@ -0,0 +1,3 @@
+export * from './backend-message-receiver';
+export * from './backend-messenger';
+export * from './broker'
\ No newline at end of file
diff --git a/libs/shared/lib/data-access/socket/listeners/SchemaViewModelImpl.tsx b/libs/shared/lib/data-access/socket/listeners/SchemaViewModelImpl.tsx
new file mode 100644
index 000000000..8347ca340
--- /dev/null
+++ b/libs/shared/lib/data-access/socket/listeners/SchemaViewModelImpl.tsx
@@ -0,0 +1,897 @@
+/**
+ * This program has been developed by students from the bachelor Computer Science at
+ * Utrecht University within the Software Project course.
+ * © Copyright Utrecht University (Department of Information and Computing Sciences)
+ */
+
+/** This class is responsible for updating and creating the graph schema. */
+export default class SchemaViewModelImpl
+  extends AbstractBaseViewModelImpl
+  implements SchemaViewModel
+{
+  private reactFlowInstance: any;
+  private relationCounter: number;
+  private drawOrderUseCase: DrawOrderUseCase;
+  private nodeUseCase: NodeUseCase;
+  private edgeUseCase: EdgeUseCase;
+  private graphUseCase: GraphUseCase;
+  private placeInQueryBuilder: (name: string, type: string) => void;
+  public elements: SchemaElements = { nodes: [], edges: [], selfEdges: [] };
+  public zoom: number;
+  public visible: boolean;
+
+  public nodeQualityPopup: NodeQualityPopupNode;
+  public attributeAnalyticsPopupMenu: AttributeAnalyticsPopupMenuNode;
+
+  public nodeQualityData: Record<
+    string,
+    NodeQualityDataForEntities | NodeQualityDataForRelations
+  >;
+  public attributeAnalyticsData: Record<string, AttributeAnalyticsData>;
+
+  private entityPopupOffsets = {
+    nodeQualityOffset: {
+      x: SchemaThemeHolder.entity.width,
+      y: SchemaThemeHolder.entity.height - 3,
+    },
+    attributeQualityOffset: {
+      x: SchemaThemeHolder.entity.width,
+      y: -SchemaThemeHolder.entity.height + 12,
+    },
+  };
+
+  private relationPopupOffsets = {
+    nodeQualityOffset: {
+      x: SchemaThemeHolder.relation.width + 50,
+      y: SchemaThemeHolder.relation.height + 2,
+    },
+    attributeQualityOffset: {
+      x: SchemaThemeHolder.relation.width + 50,
+      y: -SchemaThemeHolder.relation.height + 17,
+    },
+  };
+  // React flow reference for positioning on drop.
+  public myRef: React.RefObject<HTMLDivElement>;
+
+  public nodeTypes = {
+    entity: EntityNode,
+    relation: RelationNode,
+    nodeQualityEntityPopup: NodeQualityEntityPopupNode,
+    nodeQualityRelationPopup: NodeQualityRelationPopupNode,
+    attributeAnalyticsPopupMenu: AttributeAnalyticsPopupMenu,
+  };
+
+  public edgeTypes = {
+    nodeEdge: NodeEdge,
+    selfEdge: SelfEdge,
+  };
+
+  public constructor(
+    drawOrderUseCase: DrawOrderUseCase,
+    nodeUseCase: NodeUseCase,
+    edgeUseCase: EdgeUseCase,
+    graphUseCase: GraphUseCase,
+    addAttribute: (name: string, type: string) => void
+  ) {
+    super();
+
+    this.myRef = React.createRef();
+    // TODO: These values need to not be hardcoded.
+    this.zoom = 1.3;
+    this.relationCounter = 0;
+    this.reactFlowInstance = 0;
+    this.visible = true;
+    this.edgeUseCase = edgeUseCase;
+    this.nodeUseCase = nodeUseCase;
+    this.drawOrderUseCase = drawOrderUseCase;
+    this.graphUseCase = graphUseCase;
+    this.placeInQueryBuilder = addAttribute;
+
+    this.nodeQualityPopup = this.emptyNodeQualityPopupNode();
+    this.attributeAnalyticsPopupMenu =
+      this.emptyAttributeAnalyticsPopupMenuNode();
+
+    this.nodeQualityData = {};
+    this.attributeAnalyticsData = {};
+  }
+
+  /**
+   * Containts all function calls to create the graph schema.
+   * Notifies the view about the changes at the end.
+   */
+  public createSchema = (elements: SchemaElements): SchemaElements => {
+    let drawOrder: Node[];
+
+    drawOrder = this.drawOrderUseCase.createDrawOrder(elements);
+    // Create nodes with start position.
+    elements.nodes = this.nodeUseCase.setEntityNodePosition(
+      drawOrder,
+      {
+        x: 0,
+        y: 0,
+      },
+      this.toggleNodeQualityPopup,
+      this.toggleAttributeAnalyticsPopupMenu
+    );
+
+    // Create the relation-nodes.
+    elements.edges.forEach((relation) => {
+      this.createRelationNode(
+        relation.id,
+        relation.data.attributes,
+        relation.data.collection,
+        elements
+      );
+    });
+
+    elements.selfEdges.forEach((relation) => {
+      this.createRelationNode(
+        relation.id,
+        relation.data.attributes,
+        relation.data.collection,
+        elements
+      );
+    });
+
+    // Complement the relation-nodes with extra data that is now accessible.
+    elements.edges = this.edgeUseCase.positionEdges(
+      drawOrder,
+      elements,
+      this.setRelationNodePosition
+    );
+
+    this.visible = false;
+    this.notifyViewAboutChanges();
+    return elements;
+  };
+
+  /**
+   * consumes the schema send from the backend and uses it to create the SchemaElements for the schema
+   * @param jsonObject
+   */
+  public consumeMessageFromBackend(jsonObject: unknown): void {
+    if (isSchemaResult(jsonObject)) {
+      // This is always the first message to receive, so reset the global variables.
+      this.visible = false;
+      this.createSchema({ nodes: [], edges: [], selfEdges: [] });
+
+      this.relationCounter = 0;
+
+      this.nodeQualityPopup.isHidden = true;
+      this.attributeAnalyticsPopupMenu.isHidden = true;
+
+      /* Create the graph-schema and add the popup-menu's for as far as possible. 
+      * Runs underlying useCase trice to fix a bug with lingering selfEdges. 
+      TODO: clean this up.*/
+      this.elements = this.createSchema({
+        nodes: [],
+        edges: [],
+        selfEdges: [],
+      });
+      let schemaElements =
+        this.graphUseCase.createGraphFromInputData(jsonObject);
+      this.elements = this.createSchema(schemaElements);
+      this.notifyViewAboutChanges();
+      //End weird fix
+
+      this.visible = true;
+      schemaElements = this.graphUseCase.createGraphFromInputData(jsonObject);
+      this.elements = this.createSchema(schemaElements);
+      this.notifyViewAboutChanges();
+
+      this.addAttributesToAnalyticsPopupMenus(jsonObject);
+    } else if (isAttributeDataEntity(jsonObject)) {
+      // Add all information from the received message to the popup-menu's.
+      this.addAttributeDataToPopupMenusAndElements(
+        jsonObject,
+        'gsa_node_result',
+        this.elements
+      );
+    } else if (isAttributeDataRelation(jsonObject)) {
+      // Add all information from the received message to the popup-menu's.
+      this.addAttributeDataToPopupMenusAndElements(
+        jsonObject,
+        'gsa_edge_result',
+        this.elements
+      );
+    } else {
+      // TODO: This should be an error screen eventually.
+      console.log('This is no valid input!');
+    }
+    this.notifyViewAboutChanges();
+  }
+
+  /**
+   * Create a reference to the reactflow schema component on load
+   * @param reactFlowInstance reactflow instance
+   */
+  public onLoad = (reactFlowInstance: OnLoadParams<any>): void => {
+    this.reactFlowInstance = reactFlowInstance;
+  };
+
+  /**
+   * Complements the relation-nodes with data that had default values before.
+   * It determines the position of the relation-node and the connected entity-nodes.
+   * @param centerX Used to determine the center of the edge.
+   * @param centerY Used to determine the center of the edge.
+   * @param id The id of the relation node.
+   * @param from The id of the entity where the edge should connect from.
+   * @param to The id of the entity where the edge should connect to.
+   * @param attributes The attributes of the relation node.
+   */
+  public setRelationNodePosition = (
+    centerX: number,
+    centerY: number,
+    id: string,
+    from: string,
+    to: string,
+    attributes: Attribute[]
+  ): void => {
+    let width: number;
+    let overlap: boolean;
+    let y: number;
+
+    // Check if the relation-node is in the list of nodes.
+    let relation = this.elements.nodes.find((node) => node.id == id);
+    if (relation == undefined)
+      throw new Error('Relation ' + id + ' does not exist.');
+
+    // Height of relation/entity + external buttons.
+    const height = 20;
+
+    width =
+      SchemaThemeHolder.relation.width +
+      calcWidthRelationNodeBox(attributes.length, 0);
+    let x = centerX - SchemaThemeHolder.relation.width / 2;
+    y = centerY - height / 2;
+
+    while (this.CheckForOverlap(x, y, width, height)) {
+      y = y + 1;
+    }
+
+    // Replace the default values for the correct values.
+    relation.position = { x: x, y: y };
+    relation.data.from = from;
+    relation.data.to = to;
+
+    this.relationCounter++;
+    if (this.relationCounter == this.elements.edges.length) {
+      this.fitToView();
+    }
+    this.notifyViewAboutChanges();
+  };
+
+  /**
+   * Creates a new relation-node with some default values.
+   * @param id The id of the relation node.
+   * @param attributes The list of attributes that this relation-node has.
+   * @param collection The collection this relation-node is in.
+   */
+  public createRelationNode = (
+    id: string,
+    attributes: Attribute[],
+    collection: string,
+    schemaElements: SchemaElements
+  ): void => {
+    // Height of relation/entity + external buttons.
+    const height = 20;
+    const width =
+      SchemaThemeHolder.relation.width +
+      calcWidthRelationNodeBox(attributes.length, 0);
+
+    schemaElements.nodes.push({
+      type: NodeType.relation,
+      id: id,
+      position: { x: 0, y: 0 },
+      data: {
+        width: width,
+        height: height,
+        collection: collection,
+        attributes: attributes,
+        from: '',
+        to: '',
+        nodeCount: 0,
+        summedNullAmount: 0,
+        fromRatio: 0,
+        toRatio: 0,
+        toggleNodeQualityPopup: this.toggleNodeQualityPopup,
+        toggleAttributeAnalyticsPopupMenu:
+          this.toggleAttributeAnalyticsPopupMenu,
+      },
+    });
+  };
+
+  /**
+   * Calculates the width and height of the graph-schema-panel.
+   * @returns { number; number } The width and the height of the graph-schema-panel.
+   */
+  public getWidthHeight = (): { width: number; height: number } => {
+    const reactFlow = this.myRef.current as HTMLDivElement;
+    const reactFlowBounds = reactFlow.getBoundingClientRect();
+    const width = reactFlowBounds.right - reactFlowBounds.left;
+    const height = reactFlowBounds.bottom - reactFlowBounds.top;
+    return { width, height };
+  };
+
+  /** Placeholder function for fitting the schema into the view. */
+  public fitToView = (): void => {
+    let minX = Infinity;
+    let maxX = 0;
+    let minY = Infinity;
+    let maxY = 0;
+    let xZoom = 0;
+    let yZoom = 0;
+
+    let setY = 0;
+    let setX = 0;
+    let setZoom = 0;
+
+    this.elements.nodes.forEach((node) => {
+      const attributeCount: number = node.data.attributes.length;
+      const nodeCount: number = node.data.nodeCount;
+      const nodeWidth: number =
+        node.type == NodeType.entity
+          ? SchemaThemeHolder.entity.width +
+            calcWidthEntityNodeBox(attributeCount, nodeCount)
+          : SchemaThemeHolder.relation.width +
+            calcWidthRelationNodeBox(attributeCount, nodeCount);
+
+      if (node.position.x < minX) minX = node.position.x;
+      if (node.position.x + nodeWidth > maxX)
+        maxX = node.position.x + nodeWidth;
+      if (node.position.y < minY) minY = node.position.y;
+      if (node.position.y + node.data.height > maxY)
+        maxY = node.position.y + node.data.height;
+    });
+
+    minX -= 10;
+    maxX += 90;
+
+    const { width, height } = this.getWidthHeight();
+
+    // Correct for X and Y position with width and height.
+    let nodeWidth = Math.abs(maxX - minX);
+    let nodeHeight = Math.abs(maxY - minY);
+
+    setX = minX * -1;
+    xZoom = width / nodeWidth;
+    setY = minY * -1;
+    yZoom = height / nodeHeight;
+
+    // TODO: Correct position and zoom for selfEdges.
+
+    if (xZoom >= yZoom) {
+      setZoom = yZoom;
+      setX = setX + width / 2 - nodeWidth / 2;
+    } else {
+      setZoom = xZoom;
+      setY = setY + height / 2 - nodeHeight / 2;
+    }
+
+    try {
+      this.reactFlowInstance.setTransform({ x: setX, y: setY, zoom: setZoom });
+    } catch {
+      console.log('this.reactFlowInstance is undefined!');
+    }
+  };
+
+  /**
+   * this function check for a relation node if it overlaps with any of the other nodes in the schema.
+   * It creates boundingbox for the node and checks with all the other nodes if the boxes overlap.
+   * @param x Top left x of the node.
+   * @param y Top left y of the node.
+   * @param width Width of the node.
+   * @param height Height of the node.
+   * @returns {bool} whether it overlaps.*/
+  public CheckForOverlap = (
+    x: number,
+    y: number,
+    width: number,
+    height: number
+  ): boolean => {
+    const boundingBox = makeBoundingBox(x, y, width, height);
+    let elements = this.elements;
+
+    let boundingTemporary: BoundingBox;
+    for (let i = 0; i < elements.nodes.length; i++) {
+      boundingTemporary = makeBoundingBox(
+        elements.nodes[i].position.x,
+        elements.nodes[i].position.y,
+        elements.nodes[i].data.width,
+        elements.nodes[i].data.height
+      );
+      if (doBoxesOverlap(boundingBox, boundingTemporary)) {
+        return true;
+      }
+    }
+    return false;
+  };
+
+  /** Exports the schema builder to a beautiful png file */
+  public exportToPNG(): void {
+    const { width, height } = this.getWidthHeight();
+    exportComponentAsPNG(this.myRef, {
+      fileName: 'schemaBuilder',
+      pdfOptions: {
+        x: 0,
+        y: 0,
+        w: width,
+        h: height,
+        unit: 'px',
+      } as Partial<PDFOptions>,
+    } as Params);
+  }
+
+  /** Not implemented method for exporting the schema builder visualisation to PDF. */
+  public exportToPDF(): void {
+    console.log('Method not implemented.');
+  }
+
+  /** Attach the listener to the broker. */
+  public subscribeToSchemaResult(): void {
+    Broker.instance().subscribe(this, 'schema_result');
+  }
+
+  /** Detach the listener to the broker. */
+  public unSubscribeFromSchemaResult(): void {
+    Broker.instance().unSubscribe(this, 'schema_result');
+  }
+
+  /** Attach the listeners to the broker. */
+  public subscribeToAnalyticsData(): void {
+    Broker.instance().subscribe(this, 'gsa_node_result');
+    Broker.instance().subscribe(this, 'gsa_edge_result');
+  }
+
+  /** Detach the listeners to the broker. */
+  public unSubscribeFromAnalyticsData(): void {
+    Broker.instance().unSubscribe(this, 'gsa_node_result');
+    Broker.instance().unSubscribe(this, 'gsa_edge_result');
+  }
+
+  /**
+   * This function is used by relation and entity nodes to hide or show the node-quality popup of that node.
+   * @param id of the node for which the new popup is.
+   */
+  public toggleNodeQualityPopup = (id: string): void => {
+    const popup = this.nodeQualityPopup;
+
+    // Hide the popup if the current popup is visible and if the popup belongs to the same node as the given id.
+    if (popup.nodeID == id && !popup.isHidden) popup.isHidden = true;
+    // Else make and show a new popup for the node with the given id.
+    else this.updateNodeQualityPopup(id, this.elements);
+
+    this.notifyViewAboutChanges();
+  };
+
+  /**
+   * This function shows and updates the node-quality popup for the node which has the given id.
+   * @param id of the node for which the new popup is.
+   */
+  private updateNodeQualityPopup(id: string, schemaElements: SchemaElements) {
+    let node = schemaElements.nodes.find((node) => node.id == id);
+    if (node == undefined) {
+      throw new Error('Node does not exist therefore no popup can be shown.');
+    }
+
+    const popup = this.nodeQualityPopup;
+    popup.nodeID = id;
+    popup.isHidden = false;
+    popup.data = this.nodeQualityData[id];
+
+    if (node.type == 'entity') {
+      // Make changes to the popup, to make it a popup for entities.
+      this.updateToNodeQualityEntityPopup(node);
+    } else {
+      // Make changes to the popup, to make it a popup for relations.
+      this.updateToNodeQualityRelationPopup(node);
+    }
+
+    // Hide the attributeAnalyticsPopupMenu so that only one popup is displayed.
+    this.attributeAnalyticsPopupMenu.isHidden = true;
+
+    this.notifyViewAboutChanges();
+    this.relationCounter++;
+    if (this.relationCounter == schemaElements.edges.length) {
+      this.fitToView();
+    }
+  }
+
+  /**
+   * This displays the new node-quality popup for the given entity.
+   * @param node This is the entity of which you want to display the popup.
+   */
+  private updateToNodeQualityEntityPopup(node: Node) {
+    const popup = this.nodeQualityPopup;
+    const offset = this.entityPopupOffsets.nodeQualityOffset;
+
+    popup.position = {
+      x: node.position.x + offset.x,
+      y: node.position.y + offset.y,
+    };
+
+    popup.type = 'nodeQualityEntityPopup';
+  }
+
+  /**
+   * This displays the new node-quality popup for the given relation.
+   * @param node This is the relation of which you want to display the popup.
+   */
+  private updateToNodeQualityRelationPopup(node: Node) {
+    const popup = this.nodeQualityPopup;
+    const offset = this.relationPopupOffsets.nodeQualityOffset;
+
+    popup.position = {
+      x: node.position.x + offset.x,
+      y: node.position.y + offset.y,
+    };
+
+    popup.type = 'nodeQualityRelationPopup';
+  }
+
+  /**
+   * This function is used by relation and entity nodes to hide or show the attribute analyics popup menu of that node.
+   * @param id of the node for which the popup is.
+   */
+  public toggleAttributeAnalyticsPopupMenu = (id: string): void => {
+    const popupMenu = this.attributeAnalyticsPopupMenu;
+
+    // Hide the popup menu if the current popup menu is visible and if the popup menu belongs to the same node as the given id.
+    if (popupMenu.nodeID == id && !popupMenu.isHidden)
+      popupMenu.isHidden = true;
+    // Else make and show a new popup menu for the node with the given id.
+    else this.updateAttributeAnalyticsPopupMenu(id, this.elements);
+
+    this.notifyViewAboutChanges();
+  };
+
+  /**
+   * This displays the attribute-analytics popup menu for the given node (entity or relation).
+   * It removes the other menus from the screen.
+   * @param id This is the id of the node (entity or relation) of which you want to display the menu.
+   */
+  public updateAttributeAnalyticsPopupMenu = (
+    id: string,
+    schemaElements: SchemaElements
+  ): void => {
+    const node = schemaElements.nodes.find((node) => node.id == id);
+
+    if (node == undefined)
+      throw new Error(
+        'Node ' + id + ' does not exist therefore no popup menu can be shown.'
+      );
+
+    const popupMenu = this.attributeAnalyticsPopupMenu;
+    // Make new popup menu for the node.
+    popupMenu.nodeID = id;
+    popupMenu.isHidden = false;
+    popupMenu.data = { ...this.attributeAnalyticsData[id] };
+
+    if (node.type == NodeType.entity) {
+      const offset = this.entityPopupOffsets.attributeQualityOffset;
+      popupMenu.position = {
+        x: node.position.x + offset.x,
+        y: node.position.y + offset.y,
+      };
+    } else {
+      const offset = this.relationPopupOffsets.attributeQualityOffset;
+      popupMenu.position = {
+        x: node.position.x + offset.x,
+        y: node.position.y + offset.y,
+      };
+    }
+
+    // Hide the nodeQualityPopup so that only one popup is displayed.
+    this.nodeQualityPopup.isHidden = true;
+
+    this.notifyViewAboutChanges();
+  };
+
+  /** This removes the node quality popup from the screen. */
+  public hideNodeQualityPopup = (): void => {
+    this.nodeQualityPopup.isHidden = true;
+    this.notifyViewAboutChanges();
+  };
+
+  /** This removes the attribute-analytics popup menu from the screen. */
+  public hideAttributeAnalyticsPopupMenu = (): void => {
+    this.attributeAnalyticsPopupMenu.isHidden = true;
+    this.notifyViewAboutChanges();
+  };
+
+  /**
+   * This sets all the data for the attributesPopupMenu without the attribute data.
+   * @param schemaResult This is the schema result that you get (so no attribute data yet).
+   */
+  addAttributesToAnalyticsPopupMenus = (schemaResult: Schema): void => {
+    this.nodeQualityData = {};
+    this.attributeAnalyticsData = {};
+
+    // Firstly, loop over all entities and add the quality-data (as far as possible).
+    // Then add the attribute-data (as far as possible).
+    schemaResult.nodes.forEach((node) => {
+      this.nodeQualityData[node.name] = {
+        nodeCount: 0,
+        notConnectedNodeCount: 0,
+        attributeNullCount: 0,
+        isAttributeDataIn: false,
+        onClickCloseButton: this.hideNodeQualityPopup,
+      };
+      let attributes: any = [];
+      node.attributes.forEach((attribute) => {
+        attributes.push({
+          attribute: attribute,
+          category: AttributeCategory.undefined,
+          nullAmount: 0,
+        });
+      });
+      this.attributeAnalyticsData[node.name] = {
+        nodeID: node.name,
+        nodeType: NodeType.entity,
+        attributes: attributes,
+        isAttributeDataIn: false,
+        onClickCloseButton: this.hideAttributeAnalyticsPopupMenu,
+        onClickPlaceInQueryBuilderButton: this.placeInQueryBuilder,
+        searchForAttributes: this.searchForAttributes,
+        resetAttributeFilters: this.resetAttributeFilters,
+        applyAttributeFilters: this.applyAttributeFilters,
+      };
+    });
+    // Secondly, loop over all relations and add the quality-data (as far as possible).
+    // Then add the attribute-data (as far as possible).
+    schemaResult.edges.forEach((edge) => {
+      this.nodeQualityData[edge.collection] = {
+        nodeCount: 0,
+        fromRatio: 0,
+        toRatio: 0,
+        attributeNullCount: 0,
+        notConnectedNodeCount: 0,
+        isAttributeDataIn: false,
+        onClickCloseButton: this.hideNodeQualityPopup,
+      };
+      let attributes: any = [];
+      edge.attributes.forEach((attribute) => {
+        attributes.push({
+          attribute: attribute,
+          category: AttributeCategory.undefined,
+          nullAmount: 0,
+        });
+      });
+      this.attributeAnalyticsData[edge.collection] = {
+        nodeID: edge.collection,
+        nodeType: NodeType.relation,
+        attributes: attributes,
+        isAttributeDataIn: false,
+        onClickCloseButton: this.hideAttributeAnalyticsPopupMenu,
+        onClickPlaceInQueryBuilderButton: this.placeInQueryBuilder,
+        searchForAttributes: this.searchForAttributes,
+        resetAttributeFilters: this.resetAttributeFilters,
+        applyAttributeFilters: this.applyAttributeFilters,
+      };
+    });
+  };
+
+  /** Returns an empty quality popup node for react-flow. */
+  public emptyAttributeAnalyticsPopupMenuNode(): AttributeAnalyticsPopupMenuNode {
+    return {
+      id: 'attributeAnalyticsPopupMenu',
+      nodeID: '',
+      position: { x: 0, y: 0 },
+      data: {
+        nodeID: '',
+        nodeType: NodeType.relation,
+        attributes: [],
+        isAttributeDataIn: false,
+        onClickCloseButton: this.hideAttributeAnalyticsPopupMenu,
+        onClickPlaceInQueryBuilderButton: this.placeInQueryBuilder,
+        searchForAttributes: this.searchForAttributes,
+        resetAttributeFilters: this.resetAttributeFilters,
+        applyAttributeFilters: this.applyAttributeFilters,
+      },
+      type: 'attributeAnalyticsPopupMenu',
+      isHidden: true,
+      className: 'attributeAnalyticsPopupMenu',
+    };
+  }
+  /** Returns an empty quality popup node for react-flow. */
+  public emptyNodeQualityPopupNode(): NodeQualityPopupNode {
+    return {
+      id: 'nodeQualityPopup',
+      position: { x: 0, y: 0 },
+      data: {
+        nodeCount: 0,
+        notConnectedNodeCount: 0,
+        attributeNullCount: 0,
+        isAttributeDataIn: false,
+        onClickCloseButton: this.hideNodeQualityPopup,
+      },
+      type: 'nodeQualityEntityPopup',
+      isHidden: true,
+      nodeID: '',
+      className: 'nodeQualityPopup',
+    };
+  }
+
+  /**
+   * This function adjusts the values from the new attribute data into
+   * the attribute-analytics data and the node-quality data.
+   * @param attributeData The data that comes from the backend with (some) data about the attributes.
+   */
+  public addAttributeDataToPopupMenusAndElements = (
+    attributeData: AttributeData,
+    attributeDataType: string,
+    schemaElements: SchemaElements
+  ): void => {
+    // Check if attributeData is a node/entity.
+    if (attributeDataType === 'gsa_node_result') {
+      const entity = attributeData as NodeAttributeData;
+      // If it is a entity then add the data for the corresponding entity.
+      if (entity.id in this.attributeAnalyticsData) {
+        const attributeDataOfEntity = this.attributeAnalyticsData[entity.id];
+        attributeDataOfEntity.isAttributeDataIn = true;
+
+        entity.attributes.forEach((attribute) => {
+          // Check if attribute is in the list with attributes from the correct entity.
+          const attributeFound = attributeDataOfEntity.attributes.find(
+            (attribute_) => attribute_.attribute.name == attribute.name
+          );
+          if (attributeFound !== undefined) {
+            attributeFound.category = attribute.type;
+            attributeFound.nullAmount = attribute.nullAmount;
+          }
+        });
+      } // Not throw new error, because it should not crash, a message is enough and not all data will be shown.
+      else
+        console.log(
+          'entity ' + entity.id + ' is not in attributeAnalyticsData'
+        );
+
+      if (entity.id in this.nodeQualityData) {
+        const qualityDataOfEntity = this.nodeQualityData[
+          entity.id
+        ] as NodeQualityDataForEntities;
+        qualityDataOfEntity.nodeCount = entity.length;
+        qualityDataOfEntity.attributeNullCount = entity.summedNullAmount;
+        qualityDataOfEntity.notConnectedNodeCount = Number(
+          (1 - entity.connectedRatio).toFixed(2)
+        );
+        qualityDataOfEntity.isAttributeDataIn = true;
+      } // Not throw new error, because it should not crash, a message is enough and not all data will be shown.
+      else console.log('entity ' + entity.id + ' is not in nodeQualityData');
+
+      // Check also if the entity exists in the this.elements-list.
+      // If so, add the new data to it.
+      const elementsNode = schemaElements.nodes.find(
+        (node_) => node_.id == entity.id
+      );
+      if (elementsNode !== undefined) {
+        elementsNode.data.nodeCount = entity.length;
+        elementsNode.data.summedNullAmount = entity.summedNullAmount;
+        elementsNode.data.connectedRatio = entity.connectedRatio;
+      }
+    }
+
+    // Check if attributeData is an edge/relation.
+    else if (attributeDataType === 'gsa_edge_result') {
+      const relation = attributeData as EdgeAttributeData;
+      // If it is a relation then add the data for the corresponding relation.
+      if (relation.id in this.attributeAnalyticsData) {
+        const attributeDataOfRelation =
+          this.attributeAnalyticsData[relation.id];
+        attributeDataOfRelation.isAttributeDataIn = true;
+
+        relation.attributes.forEach((attribute) => {
+          // Check if attribute is in the list with attributes from the correct relation.
+          const attributeFound = attributeDataOfRelation.attributes.find(
+            (attribute_) => attribute_.attribute.name == attribute.name
+          );
+          if (attributeFound !== undefined) {
+            attributeFound.category = attribute.type;
+            attributeFound.nullAmount = attribute.nullAmount;
+          }
+        });
+      } // Not throw new error, because it should not crash, a message is enough and not all data will be shown.
+      else
+        console.log(
+          'relation ' + relation.id + ' is not in attributeAnalyticsData'
+        );
+
+      if (relation.id in this.nodeQualityData) {
+        const qualityDataOfRelation = this.nodeQualityData[
+          relation.id
+        ] as NodeQualityDataForRelations;
+        qualityDataOfRelation.nodeCount = relation.length;
+        qualityDataOfRelation.attributeNullCount = relation.summedNullAmount;
+        qualityDataOfRelation.fromRatio = Number(relation.fromRatio.toFixed(2));
+        qualityDataOfRelation.toRatio = Number(relation.toRatio.toFixed(2));
+        qualityDataOfRelation.isAttributeDataIn = true;
+      } // Not throw new error, because it should not crash, a message is enough and not all data will be shown.
+      else
+        console.log('relation ' + relation.id + ' is not in nodeQualityData');
+
+      // Check also if the entity exists in the this.elements-list.
+      // If so, add the new data to it.
+      const elementsNode = schemaElements.nodes.find(
+        (node_) => node_.id == relation.id
+      );
+      if (elementsNode !== undefined) {
+        elementsNode.data.nodeCount = relation.length;
+        elementsNode.data.summedNullAmount = relation.summedNullAmount;
+        elementsNode.data.fromRatio = relation.fromRatio;
+        elementsNode.data.toRatio = relation.toRatio;
+      }
+    } else throw new Error('This data is not valid!');
+  };
+
+  /**
+   * Filter out attributes that do not contain the given searchbar-value.
+   * @param id The id of the node the attributes are from.
+   * @param searchbarValue The value of the searchbar.
+   */
+  public searchForAttributes = (id: string, searchbarValue: string): void => {
+    const data = this.attributeAnalyticsData[id];
+    // Check if there is data available.
+    if (data !== undefined) {
+      let passedAttributes: AttributeWithData[] = [];
+      data.attributes.forEach((attribute) => {
+        if (
+          attribute.attribute.name
+            .toLowerCase()
+            .includes(searchbarValue.toLowerCase())
+        )
+          passedAttributes.push(attribute);
+      });
+      this.attributeAnalyticsPopupMenu.data.attributes = passedAttributes;
+      this.notifyViewAboutChanges();
+    }
+  };
+
+  /**
+   * Reset the current used filters for the attribute-list.
+   * @param id The id of the node the attributes are from.
+   */
+  public resetAttributeFilters = (id: string): void => {
+    const data = this.attributeAnalyticsData[id];
+    // Check if there is data available.
+    if (data !== undefined) {
+      this.attributeAnalyticsPopupMenu.data.attributes = data.attributes;
+      this.notifyViewAboutChanges();
+    }
+  };
+
+  /**
+   * Applies the chosen filters on the list of attributes of the particular node.
+   * @param id The id of the node the attributes are from.
+   * @param dataType The given type of the data you want to filter on (numerical, categorical, other).
+   * @param predicate The given predicate.
+   * @param percentage The given percentage you want to compare the null-values on.
+   */
+  public applyAttributeFilters = (
+    id: string,
+    dataType: AttributeCategory,
+    predicate: string,
+    percentage: number
+  ): void => {
+    const data = this.attributeAnalyticsData[id];
+    // Check if there is data available.
+    if (data !== undefined) {
+      let passedAttributes: AttributeWithData[] = [];
+      data.attributes.forEach((attribute) => {
+        // If the value is undefined it means that this filter is not chosen, so that must not be taken into account for further filtering.
+        if (
+          attribute.category == dataType ||
+          dataType == AttributeCategory.undefined
+        )
+          if (predicate == '' || percentage == -1)
+            // If the string is empty it means that this filter is not chosen, so that must not be taken into account for filtering.
+            passedAttributes.push(attribute);
+          else if (
+            numberPredicates[predicate](attribute.nullAmount, percentage)
+          )
+            passedAttributes.push(attribute);
+      });
+      this.attributeAnalyticsPopupMenu.data.attributes = passedAttributes;
+      this.notifyViewAboutChanges();
+    }
+  };
+}
diff --git a/libs/shared/lib/data-access/socket/query/QueryApi.tsx b/libs/shared/lib/data-access/socket/query/QueryApi.tsx
new file mode 100644
index 000000000..1d4f86112
--- /dev/null
+++ b/libs/shared/lib/data-access/socket/query/QueryApi.tsx
@@ -0,0 +1,30 @@
+import BackendMessengerRepository from '../../../domain/repository-interfaces/BackendMessengerRepository';
+import QueryRepository, { Query } from '../../../domain/repository-interfaces/QueryRepository';
+
+/**
+ * Implementation of the QueryRepository.
+ * Contains function for sending a query to the database and retrieving a cached query.
+ */
+export default class QueryApi implements QueryRepository {
+  backendMessenger: BackendMessengerRepository;
+
+  /** @param backendMessenger {BackendMessengerRepository} A BackendMessengerRepository implementation. */
+  constructor(backendMessenger: BackendMessengerRepository) {
+    this.backendMessenger = backendMessenger;
+  }
+
+  async sendQuery(query: Query): Promise<string> {
+    const body = JSON.stringify(query);
+    return this.backendMessenger.SendMessage(body, 'query/execute/', 'POST').then((response) => {
+      return response.json().then((obj) => obj.queryID);
+    });
+  }
+
+  async retrieveCachedQuery(queryID: string): Promise<Response> {
+    return this.backendMessenger.SendMessage(
+      JSON.stringify({ queryID }),
+      'query/retrieve-cached/',
+      'POST',
+    );
+  }
+}
diff --git a/libs/shared/lib/data-access/store/authSlice.ts b/libs/shared/lib/data-access/store/authSlice.ts
new file mode 100644
index 000000000..273c498e2
--- /dev/null
+++ b/libs/shared/lib/data-access/store/authSlice.ts
@@ -0,0 +1,37 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+import type { RootState } from './store';
+import { useIsAuthorizedState } from '../authorization';
+
+
+
+// Define the initial state using that type
+export const initialState: useIsAuthorizedState = {
+  authorized: false,
+  accessToken: '',
+};
+
+export const authSlice = createSlice({
+  name: 'auth',
+  // `createSlice` will infer the state type from the `initialState` argument
+  initialState,
+  reducers: {
+    updateAccessToken(state, action: PayloadAction<string>) {
+      state.accessToken = action.payload;
+    },
+    authorized(state, action: PayloadAction<useIsAuthorizedState>) {
+      state.authorized = action.payload.authorized;
+      state.accessToken = action.payload.accessToken;
+      state.sessionID = action.payload.sessionID;
+      state.userID = action.payload.userID;
+    }
+  },
+});
+
+export const {
+  updateAccessToken, authorized
+} = authSlice.actions;
+
+// Other code such as selectors can use the imported `RootState` type
+export const authState = (state: RootState) => state.auth;
+
+export default authSlice.reducer;
diff --git a/libs/shared/lib/data-access/store/graphQueryResultSlice.ts b/libs/shared/lib/data-access/store/graphQueryResultSlice.ts
index edbb2b6eb..68ed96218 100644
--- a/libs/shared/lib/data-access/store/graphQueryResultSlice.ts
+++ b/libs/shared/lib/data-access/store/graphQueryResultSlice.ts
@@ -7,7 +7,7 @@ export interface GraphQueryResultFromBackend {
     attributes: { [key: string]: unknown };
   }[];
 
-  links: {
+  edges: {
     attributes: { [key: string]: unknown };
     from: string;
     to: string;
@@ -69,7 +69,7 @@ export const graphQueryResultSlice = createSlice({
 
       // Assign new state
       state.nodes = action.payload.nodes as Node[];
-      state.edges = action.payload.links;
+      state.edges = action.payload.edges;
       state.nodeTypes = nodeTypes;
     },
     resetGraphQueryResults: (state) => {
diff --git a/libs/shared/lib/data-access/store/hooks.ts b/libs/shared/lib/data-access/store/hooks.ts
index e1c5935f4..3e40cdb45 100644
--- a/libs/shared/lib/data-access/store/hooks.ts
+++ b/libs/shared/lib/data-access/store/hooks.ts
@@ -12,6 +12,7 @@ import {
   selectQuerybuilderGraphology,
 } from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice';
 import { sessionCacheState } from './sessionSlice';
+import { authState } from './authSlice';
 
 // Use throughout your app instead of plain `useDispatch` and `useSelector`
 export const useAppDispatch: () => AppDispatch = useDispatch;
@@ -35,3 +36,4 @@ export const useQuerybuilderGraph = () =>
 // Overall Configuration of the app
 export const useConfig = () => useAppSelector(configState);
 export const useSessionCache = () => useAppSelector(sessionCacheState);
+export const useAuthorizationCache = () => useAppSelector(authState);
diff --git a/libs/shared/lib/data-access/store/querybuilderSlice.ts b/libs/shared/lib/data-access/store/querybuilderSlice.ts
index f2e3cd62b..9e1f176c7 100644
--- a/libs/shared/lib/data-access/store/querybuilderSlice.ts
+++ b/libs/shared/lib/data-access/store/querybuilderSlice.ts
@@ -49,6 +49,7 @@ export const querybuilderSlice = createSlice({
     clearQB: (state) => {
       state.graphologySerialized = new QueryMultiGraph().export();
     },
+
     // addQuerybuilderNode: (
     //   state,
     //   action: PayloadAction<{ id: string; attributes: Attributes }>
diff --git a/libs/shared/lib/data-access/store/schemaSlice.ts b/libs/shared/lib/data-access/store/schemaSlice.ts
index bd3d0b4b7..f2a64c896 100644
--- a/libs/shared/lib/data-access/store/schemaSlice.ts
+++ b/libs/shared/lib/data-access/store/schemaSlice.ts
@@ -5,6 +5,7 @@ import { SchemaFromBackend } from '@graphpolaris/shared/lib/model/backend';
 import type { RootState } from './store';
 import { AllLayoutAlgorithms } from '@graphpolaris/shared/lib/graph-layout';
 import { QueryMultiGraph } from '@graphpolaris/shared/lib/querybuilder/graph/graphology/utils';
+import { SchemaUtils } from '../../schema/schema-utils';
 
 /**************************************************************** */
 
@@ -34,10 +35,8 @@ export const schemaSlice = createSlice({
       state,
       action: PayloadAction<SchemaFromBackend>
     ) => {
-      const { nodes, edges } = action.payload;
-      // Instantiate a directed graph that allows self loops and parallel edges
-      const schema = new MultiGraph({ allowSelfLoops: true });
-      console.log('Updating schema');
+      state.graphologySerialized = SchemaUtils.schemaBackend2Graphology(action.payload).export();
+      console.log('Updated schema from backend');
       // The graph schema needs a node for each node AND edge. These need then be connected
 
       // nodes.forEach((node) => {
@@ -69,7 +68,7 @@ export const schemaSlice = createSlice({
       //   schema.addDirectedEdgeWithKey(edgeID + 't', edgeID, edge.to);
       // });
 
-      state.graphologySerialized = schema.export();
+      // state.graphologySerialized = schema.export();
     },
   },
 });
diff --git a/libs/shared/lib/data-access/store/sessionSlice.ts b/libs/shared/lib/data-access/store/sessionSlice.ts
index 979f7f028..f246f7e44 100644
--- a/libs/shared/lib/data-access/store/sessionSlice.ts
+++ b/libs/shared/lib/data-access/store/sessionSlice.ts
@@ -1,13 +1,45 @@
 import { createSlice, PayloadAction } from '@reduxjs/toolkit';
 import type { RootState } from './store';
+import { BackendQueryFormat } from '../../querybuilder';
 
-// Define the initial state using that type
-export const initialState: {
-  currentDatabase: string;
+/** Message format of the error message from the backend */
+export type ErrorMessage = {
+  errorStatus: any;
+  queryID: any;
+};
+
+/** Cache type */
+export type SessionCacheI = {
+  currentDatabase?: string;
   databases: string[];
-} = {
-  currentDatabase: '',
+  error?: ErrorMessage;
+
+  // queryListOpen: boolean;
+  // queryStatusList: {
+  //   queries: Record<string, QueryStatusListItem>;
+  //   queryIDsOrder: string[];
+  // };
+  // functionsMenuOpen: boolean;
+  // currentDatabaseKey: string;
+  // currentColours: { key: string; index: number };
+  // elementsperDatabaseObject: Record<string, (AnyNode | Edge<any>)[]>;
+  // autoSendQueries: boolean;
+  // panelWidthHeights: {
+  //   windowinnerHeight: number;
+  //   windowinnerWidth: number;
+  //   navBarHeight: number;
+  //   schemaDrawerHeight: number;
+  //   queryDrawerHeight: number;
+  //   schemaDrawerWidth: number;
+  //   queryDrawerWidth: number;
+  // };
+};
+
+// Define the initial state using that type
+export const initialState: SessionCacheI = {
+  currentDatabase: undefined,
   databases: [],
+  error: undefined,
 };
 
 export const sessionSlice = createSlice({
@@ -19,14 +51,35 @@ export const sessionSlice = createSlice({
       state.currentDatabase = action.payload;
     },
     updateDatabaseList(state, action: PayloadAction<string[]>) {
+      console.log('Updating database list', action);
       state.databases = action.payload;
+      if (state.databases.length > 0) {
+        if (!state.currentDatabase || !(state.databases.includes(state.currentDatabase)))
+          state.currentDatabase = state.databases[0]
+        else state.currentDatabase = undefined;
+      }
+    },
+    displayError(state, action: PayloadAction<ErrorMessage>): void {
+      // use a switch, in case certain errors will have side effects in the future
+      switch (action.payload.errorStatus) {
+        case 'Bad request':
+        case 'Bad credentials':
+        case 'Translation error':
+        case 'Database error':
+        case 'ML bad request':
+          state.error = action.payload
+          break;
+      }
+    },
+    closeError(state): void {
+      state.error = undefined;
     }
   },
 });
 
 export const {
-  updateCurrentDatabase,
-  updateDatabaseList
+  updateCurrentDatabase, updateDatabaseList, displayError, closeError
+
 } = sessionSlice.actions;
 
 // Other code such as selectors can use the imported `RootState` type
diff --git a/libs/shared/lib/data-access/store/store.ts b/libs/shared/lib/data-access/store/store.ts
index ef639a920..f5dd2dab6 100644
--- a/libs/shared/lib/data-access/store/store.ts
+++ b/libs/shared/lib/data-access/store/store.ts
@@ -5,6 +5,7 @@ import querybuilderSlice from './querybuilderSlice';
 import schemaSlice from './schemaSlice';
 import configSlice from './configSlice';
 import sessionSlice from './sessionSlice';
+import authSlice from './authSlice';
 
 export const store = configureStore({
   reducer: {
@@ -13,6 +14,7 @@ export const store = configureStore({
     colorPaletteConfig: colorPaletteConfigSlice,
     querybuilder: querybuilderSlice,
     sessionCache: sessionSlice,
+    auth: authSlice,
     config: configSlice,
   },
   middleware: (getDefaultMiddleware) =>
diff --git a/libs/shared/lib/querybuilder/graph/graphology/utils.ts b/libs/shared/lib/querybuilder/graph/graphology/utils.ts
index b55a4597d..4128f61e9 100644
--- a/libs/shared/lib/querybuilder/graph/graphology/utils.ts
+++ b/libs/shared/lib/querybuilder/graph/graphology/utils.ts
@@ -115,7 +115,7 @@ export function calcWidthHeightOfPill(attributes: Attributes): {
 }
 
 /** Interface for x and y position of node */
-export interface NodePosition extends XYPosition {}
+export interface NodePosition extends XYPosition { }
 
 /** Returns from-position of relation node */
 export function RelationPosToFromEntityPos(position: XYPosition): NodePosition {
diff --git a/libs/shared/lib/querybuilder/index.ts b/libs/shared/lib/querybuilder/index.ts
index e9ce65c3e..35906bcea 100644
--- a/libs/shared/lib/querybuilder/index.ts
+++ b/libs/shared/lib/querybuilder/index.ts
@@ -1,2 +1,3 @@
 export * from './panel';
-export * from './pills';
\ No newline at end of file
+export * from './pills';
+export * from './query-utils';
\ No newline at end of file
diff --git a/libs/shared/lib/querybuilder/panel/querybuilder.module.scss b/libs/shared/lib/querybuilder/panel/querybuilder.module.scss
index e1a5d48d0..40d1908c9 100644
--- a/libs/shared/lib/querybuilder/panel/querybuilder.module.scss
+++ b/libs/shared/lib/querybuilder/panel/querybuilder.module.scss
@@ -1,13 +1,12 @@
 .reactflow {
   width: 100%;
-  height: 500px;
+  // height: 500px;
 
   // :global(.react-flow__edges) {
   //   z-index: 4 !important;
   // }
 }
 
-
 //controls
 .controls {
   left: auto !important;
@@ -23,10 +22,10 @@
   right: 20px;
   & svg {
     transform: scale(1.4);
-  };
+  }
 }
 
 .menuText {
   font-size: small;
   font-family: Poppins, sans-serif;
-}
\ No newline at end of file
+}
diff --git a/libs/shared/lib/querybuilder/panel/querybuilder.tsx b/libs/shared/lib/querybuilder/panel/querybuilder.tsx
index aca3c482a..7891eed18 100644
--- a/libs/shared/lib/querybuilder/panel/querybuilder.tsx
+++ b/libs/shared/lib/querybuilder/panel/querybuilder.tsx
@@ -208,8 +208,10 @@ export const QueryBuilder: React.FC = () => {
           x: position.x,
           y: position.y,
           depth: { min: 0, max: 1 },
-          name: dragData.name,
+          // name: dragData.name,
+          name: dragData.collection, // TODO leave collection or use name?
           fadeIn: dragData.fadeIn,
+          collection: dragData.collection,
         });
         const leftEntityId = graphologyGraph.addPill2Graphology({
           type: QueryElementTypes.Entity,
@@ -282,6 +284,7 @@ export const QueryBuilder: React.FC = () => {
           onNodesChange={onNodesChange}
           deleteKeyCode="Backspace"
           className={styles.reactflow}
+          attributionPosition="top-right"
         >
           <Background gap={10} size={0.7} />
           <Controls
diff --git a/libs/shared/lib/querybuilder/query-utils/BackendQueryFormat.tsx b/libs/shared/lib/querybuilder/query-utils/BackendQueryFormat.tsx
new file mode 100644
index 000000000..4866d7412
--- /dev/null
+++ b/libs/shared/lib/querybuilder/query-utils/BackendQueryFormat.tsx
@@ -0,0 +1,112 @@
+/**
+ * This program has been developed by students from the bachelor Computer Science at
+ * Utrecht University within the Software Project course.
+ * © Copyright Utrecht University (Department of Information and Computing Sciences)
+ */
+
+/** JSON query format used to send a query to the backend. */
+export interface BackendQueryResultFormat {
+  databaseName: string;
+  return: {
+    entities: number[];
+    relations: number[];
+    groupBys: number[];
+  };
+  entities: Entity[];
+  relations: Relation[];
+  groupBys: GroupBy[];
+  machineLearning: MachineLearning[];
+  limit: number;
+  // modifiers: ModifierStruct[];
+  // prefix: string;
+}
+
+/** JSON query format used to send a query to the backend. */
+export interface BackendQueryFormat {
+  databaseName: string;
+  return: {
+    entities: number[];
+    relations: number[];
+    groupBys: number[];
+  };
+  entities: Entity[];
+  relations: Relation[];
+  groupBys: GroupBy[];
+  machineLearning: MachineLearning[];
+  limit: number;
+  // modifiers: ModifierStruct[];
+  // prefix: string;
+}
+
+/** Interface for an entity in the JSON for the query. */
+export interface Entity {
+  name: string;
+  ID: number;
+  constraints: Constraint[];
+}
+
+/** Interface for an relation in the JSON for the query. */
+export interface Relation {
+  name: string;
+  ID: number;
+  fromType: string;
+  fromID: number;
+  toType: string;
+  toID: number;
+  depth: { min: number; max: number };
+  constraints: Constraint[];
+}
+
+/**
+ * Constraint datatypes created from the attributes of a relation or entity.
+ *
+ * string MatchTypes: exact/contains/startswith/endswith.
+ * int    MatchTypes: GT/LT/EQ.
+ * bool   MatchTypes: EQ/NEQ.
+ */
+export interface Constraint {
+  attribute: string;
+  dataType: string;
+
+  matchType: string;
+  value: string;
+}
+
+/** Interface for a function in the JSON for the query. */
+export interface GroupBy {
+  ID: number;
+
+  groupType: string;
+  groupID: number[];
+  groupAttribute: string;
+
+  byType: string;
+  byID: number[];
+  byAttribute: string;
+
+  appliedModifier: string;
+  relationID: number;
+  constraints: Constraint[];
+}
+
+/** Interface for Machine Learning algorithm */
+export interface MachineLearning {
+  ID?: number;
+  queuename: string;
+  parameters: string[];
+}
+/** Interface for what the JSON needs for link predicition */
+export interface LinkPrediction {
+  queuename: string;
+  parameters: {
+    key: string;
+    value: string;
+  }[];
+}
+
+export interface ModifierStruct {
+  type: string;
+  selectedType: string;
+  selectedTypeID: number;
+  attributeIndex: number;
+}
diff --git a/libs/shared/lib/querybuilder/query-utils/index.ts b/libs/shared/lib/querybuilder/query-utils/index.ts
new file mode 100644
index 000000000..2a8aba6d7
--- /dev/null
+++ b/libs/shared/lib/querybuilder/query-utils/index.ts
@@ -0,0 +1,2 @@
+export * from './query-utils'
+export * from './BackendQueryFormat'
\ No newline at end of file
diff --git a/libs/shared/lib/querybuilder/query-utils/query-utils.ts b/libs/shared/lib/querybuilder/query-utils/query-utils.ts
new file mode 100644
index 000000000..b081210b4
--- /dev/null
+++ b/libs/shared/lib/querybuilder/query-utils/query-utils.ts
@@ -0,0 +1,78 @@
+import { EntityNodeAttributes, RelationNodeAttributes } from "../graph/graphology/model";
+import { QueryMultiGraphExport } from "../graph/graphology/utils";
+import { Handles } from "../graph/reactflow/handles";
+import { EntityNode, FunctionNode, QueryElementTypes, RelationData, RelationNode, possibleTypes } from "../graph/reactflow/model";
+import { BackendQueryFormat } from "./BackendQueryFormat";
+
+/**
+   * Converts the ReactFlow query to a json data structure to send the query to the backend.
+   * @returns {BackendQueryFormat} A JSON object in the `JSONFormat`.
+   */
+export function Query2BackendQuery(databaseName: string, graph: QueryMultiGraphExport): BackendQueryFormat {
+    // dict of nodes per type
+    const entities = graph.nodes.filter(n => n.attributes?.type === QueryElementTypes.Entity)
+        .map((n) => ({ name: n.attributes!.name, ID: Number(n.key), constraints: [] }));
+
+    const relations = graph.nodes.filter(n => n.attributes?.type === QueryElementTypes.Relation)
+        .map((n) => {
+            const attributes = n.attributes as RelationNodeAttributes;
+            const leftEdge = graph.edges.filter(e => (e.target === n.key && e.attributes!.targetHandle === Handles.RelationLeft))?.[0];
+            const rightEdge = graph.edges.filter(e => (e.target === n.key && e.attributes!.targetHandle === Handles.RelationRight))?.[0];
+            if (!leftEdge || !rightEdge) throw Error('Malformed Graph! One or more edges of a relation node do not exist');
+
+            const leftType = graph.nodes.filter(n => n.key === leftEdge.source)?.[0]?.attributes?.type;
+            const rightType = graph.nodes.filter(n => n.key === rightEdge.source)?.[0]?.attributes?.type;
+            if (!rightType || !leftType) throw Error('Malformed Graph! edges must have a type');
+
+            return {
+                name: attributes!.collection, ID: Number(n.key), depth: attributes!.depth,
+                fromType: leftType, fromID: Number(leftEdge.source), // need to treat function==groupby
+                toType: rightType, toID: Number(rightEdge.source), // need to treat function==groupby
+                constraints: [],
+            }
+        });
+
+    // TODO: Groubby not implemented (constraints)
+    const result: BackendQueryFormat = {
+        databaseName: databaseName,
+        return: { entities: entities.map(e => e.ID), relations: relations.map(r => r.ID), groupBys: [] },
+        entities: entities,
+        relations: relations,
+        groupBys: [], // TODO
+        machineLearning: [], // TODO
+        limit: 5000, // TODO
+    };
+    console.log(result, graph);
+
+
+    //note that attribute nodes are not processed here, but are detected if they are connected to any entity/relation/function node
+
+    //add nodes to JSON query
+    // entityNodes.forEach((node) => {
+    //     this.AddEntityToJsonObject(result, node);
+    // });
+    // relationNodes.forEach((node) => {
+    //     this.AddRelationToJsonObject(result, node);
+    // });
+
+    // functionNodes.forEach((functionNode: FunctionNode) => {
+    //   switch (functionNode.data?.functionType) {
+    //     case FunctionTypes.GroupBy:
+    //       this.AddGroupByToJsonObject(result, functionNode);
+    //       break;
+    //     case FunctionTypes.link:
+    //       this.AddLinkPredictionToJsonObject(result, functionNode);
+    //       break;
+    //     case FunctionTypes.communityDetection:
+    //       this.AddCommunityDetectionToJsonObject(result, functionNode);
+    //       break;
+    //     case FunctionTypes.centrality:
+    //       this.AddCentralityToJsonObject(result, functionNode);
+    //       break;
+    //     case FunctionTypes.shortestPath:
+    //       this.addShortestPathToJsonOBject(result, functionNode);
+    //       break;
+    //   }
+    // });
+    return result;
+}
\ No newline at end of file
diff --git a/libs/shared/lib/schema/panel/schema.module.scss b/libs/shared/lib/schema/panel/schema.module.scss
index f4d3e74fb..aeedac18d 100644
--- a/libs/shared/lib/schema/panel/schema.module.scss
+++ b/libs/shared/lib/schema/panel/schema.module.scss
@@ -14,9 +14,10 @@
 //  import { SchemaThemeHolder } from '../../../domain/entity/css-themes/themeHolder';
 
 .schemaPanel {
-  display: 'flex';
-  flex-direction: 'column';
-  justify-content: 'center';
+  width: 100%;
+  // display: 'flex';
+  // flex-direction: 'column';
+  // justify-content: 'center';
   //   '& div': {
   //     '& svg' {
   //       zindex: 2;
@@ -25,11 +26,11 @@
 }
 
 .controls {
-  left: 'auto !important';
-  bottom: 'auto !important';
-  top: '10px';
-  right: '20px';
-  width: 'auto !important';
+  left: auto !important;
+  bottom: auto !important;
+  top: 10px;
+  right: 20px;
+  width: auto !important;
 }
 
 //  export const useStyles = (theme: Theme) =>
diff --git a/libs/shared/lib/schema/panel/schema.tsx b/libs/shared/lib/schema/panel/schema.tsx
index 68245392c..cfe8a15d1 100644
--- a/libs/shared/lib/schema/panel/schema.tsx
+++ b/libs/shared/lib/schema/panel/schema.tsx
@@ -24,6 +24,7 @@ import ReactFlow, {
   useNodesState,
   useEdgesState,
   ReactFlowInstance,
+  Background,
 } from 'reactflow';
 
 import 'reactflow/dist/style.css';
@@ -104,7 +105,7 @@ export const Schema = (props: Props) => {
     if (schemaGraphology == undefined || schemaGraphology.order == 0) {
       return;
     }
-    console.log(schemaGraphology.export());
+    // console.log(schemaGraphology.export());
 
     const expandedSchema = schemaExpandRelation(schemaGraphology);
     layout.current?.layout(expandedSchema);
@@ -170,7 +171,7 @@ export const Schema = (props: Props) => {
             showFitView={true}
             className={styles.controls}
           >
-            <ControlButton
+            {/* <ControlButton
               className={styles.exportButton}
               title={'Export graph schema'}
               onClick={(event) => {
@@ -181,8 +182,8 @@ export const Schema = (props: Props) => {
                 // });
               }}
             >
-              {/* <img src={exportIcon} width={21}></img> */}
-            </ControlButton>
+              <img src={exportIcon} width={21}></img>
+            </ControlButton> */}
           </Controls>
         </ReactFlow>
       </ReactFlowProvider>
diff --git a/libs/shared/lib/schema/pills/nodes/entity/entity-node.tsx b/libs/shared/lib/schema/pills/nodes/entity/entity-node.tsx
index a33dcaceb..4c54a857b 100644
--- a/libs/shared/lib/schema/pills/nodes/entity/entity-node.tsx
+++ b/libs/shared/lib/schema/pills/nodes/entity/entity-node.tsx
@@ -40,6 +40,7 @@ export const EntityNode = React.memo(
      * @param event React Mouse drag event
      */
     const onDragStart = (event: React.DragEvent<HTMLDivElement>) => {
+      // console.log(id, data);
       event.dataTransfer.setData(
         'application/reactflow',
         JSON.stringify({ type: 'entity', name: id })
diff --git a/libs/shared/lib/schema/pills/nodes/entity/entity.scss b/libs/shared/lib/schema/pills/nodes/entity/entity.scss
deleted file mode 100644
index b4ca11567..000000000
--- a/libs/shared/lib/schema/pills/nodes/entity/entity.scss
+++ /dev/null
@@ -1,180 +0,0 @@
-
-  /**
-   * When changing the height of the nodes, or the height of the buttons on the nodes, note that there is a hardcoded value 'height'
-   * in schemaViewModelImpl (in the method 'setRelationNodePosition') that is based on these heights.
-   */
-.entityNode {
-    background: SchemaThemeHolder.entity.baseColor;
-    display: 'flex';
-    borderRadius: '1px';
-    fontFamily: SchemaThemeHolder.fontFamily;
-    fontWeight: 'bold';
-    fontSize: `${SchemaThemeHolder.fontSize}px`;
-    width: `${SchemaThemeHolder.entity.width}px`;
-    lineHeight: `${SchemaThemeHolder.entity.height}px`;
-}
-
-  nodeWrapper: {
-    display: 'inherit',
-    color: SchemaThemeHolder.entity.textColor,
-    textAlign: 'center',
-    justifyContent: 'space-between',
-    alignItems: 'center',
-    width: 'inherit',
-  },
-
-  nodeSpan: {
-    margin: '0 0.5em 0 1em',
-    float: 'right',
-  },
-
-  nodeData: {
-    flexGrow: 2,
-  },
-
-  entityNodeAttributesBox: {
-    height: 20,
-    position: 'absolute',
-    left: '115px',
-    top: '-33%',
-    transform: 'translateX(50%)',
-    borderRadius: '1px',
-    boxShadow: '-1px 2px 5px #888888',
-    textAlign: 'right',
-  },
-
-  entityNodeNodesBox: {
-    height: 20,
-    position: 'absolute',
-    left: '115px',
-    top: '54%',
-    transform: 'translateX(50%)',
-    borderRadius: '1px',
-    boxShadow: '-1px 2px 5px #888888',
-    textAlign: 'right',
-  },
-
-  /**
-   * Code for the shape of the relationNodes
-   */
-  relationNode: {
-    height: 36,
-    display: 'flex',
-    fontFamily: SchemaThemeHolder.fontFamily,
-    fontWeight: 'bold',
-    fontSize: `${SchemaThemeHolder.fontSize}px`,
-    width: `${SchemaThemeHolder.relation.width}px`,
-    lineHeight: `${SchemaThemeHolder.relation.height}px`,
-  },
-
-  relationNodeTriangleGeneral: {
-    position: 'absolute',
-    width: 0,
-    height: 0,
-    margin: 'auto 0',
-    borderStyle: 'solid',
-    borderColor: 'transparent',
-  },
-
-  relationNodeTriangleLeft: {
-    transform: 'translateX(-100%)',
-    top: '0px',
-    borderWidth: '18px 24px 18px 0',
-  },
-
-  relationNodeSmallTriangleLeft: {
-    transform: 'translateX(-100%)',
-    top: '30px',
-    borderWidth: '0 8px 6px 0',
-  },
-
-  relationNodeTriangleRight: {
-    transform: `translateX(${SchemaThemeHolder.relation.width}px)`,
-    top: '0px',
-    borderWidth: '18px 0 18px 24px',
-  },
-
-  relationNodeSmallTriangleRight: {
-    transform: `translateX(${SchemaThemeHolder.relation.width}px)`,
-    top: '30px',
-    borderWidth: '0 0 6px 8px',
-  },
-
-  relationNodeAttributesBox: {
-    position: 'absolute',
-    top: '-4px',
-    transform: `translateX(${SchemaThemeHolder.relation.width + 4}px)`,
-    clipPath: 'polygon(0 0, 100% 0, 100% 100%, 26.5px 100%)',
-    height: 20,
-    textAlign: 'right',
-  },
-
-  relationNodeNodesBox: {
-    position: 'absolute',
-    top: '20px',
-    transform: `translateX(${SchemaThemeHolder.relation.width + 4}px)`,
-    clipPath: 'polygon(26.5px 0, 100% 0, 100% 100%, 0 100%)',
-    height: 20,
-    textAlign: 'right',
-  },
-
-  arrowup: {
-    width: 0,
-    height: 0,
-    position: 'absolute',
-    left: '50%',
-    top: '-20%',
-    transform: 'translateX(-50%)',
-    margin: '0 auto',
-    backgroundColor: 'transparent',
-    borderStyle: 'solid',
-    borderTopWidth: 0,
-    borderRightWidth: 8,
-    borderBottomWidth: 5,
-    borderLeftWidth: 8,
-    borderTopColor: 'transparent',
-    borderRightColor: 'transparent',
-    borderLeftColor: 'transparent',
-  },
-
-  arrowdown: {
-    width: 0,
-    height: 0,
-    position: 'absolute',
-    left: '50%',
-    bottom: '-20%',
-    transform: 'translateX(-50%)',
-    margin: '0 auto',
-    backgroundColor: 'transparent',
-    borderStyle: 'solid',
-    borderTopWidth: 5,
-    borderRightWidth: 8,
-    borderBottomWidth: 0,
-    borderLeftWidth: 8,
-    borderRightColor: 'transparent',
-    borderBottomColor: 'transparent',
-    borderLeftColor: 'transparent',
-  },
-
-  controls: {
-    left: 'auto !important',
-    bottom: 'auto !important',
-    top: '10px',
-    right: '20px',
-    width: 'auto !important',
-  },
-
-  exportButton: {
-    left: 'auto !important',
-    bottom: 'auto !important',
-    top: '10px',
-    right: '20px',
-    '& svg': {
-      transform: 'scale(1.4)',
-    },
-  },
-
-  menuText: {
-    fontSize: 'small',
-    fontFamily: 'Poppins, sans-serif',
-  },
\ No newline at end of file
diff --git a/libs/shared/lib/schema/pills/nodes/relation/relation-node.tsx b/libs/shared/lib/schema/pills/nodes/relation/relation-node.tsx
index aa5e1c849..ca4f7f16d 100644
--- a/libs/shared/lib/schema/pills/nodes/relation/relation-node.tsx
+++ b/libs/shared/lib/schema/pills/nodes/relation/relation-node.tsx
@@ -40,11 +40,12 @@ export const RelationNode = React.memo(
      * @param event React Mouse drag event.
      */
     const onDragStart = (event: React.DragEvent<HTMLDivElement>) => {
+      // console.log(id, data);
       event.dataTransfer.setData(
         'application/reactflow',
         JSON.stringify({
           type: 'relation',
-          name: id,
+          name: id, //TODO id?
           from: data.from,
           to: data.to,
           collection: data.collection,
diff --git a/libs/shared/lib/schema/schema-utils/Types.tsx b/libs/shared/lib/schema/schema-utils/Types.tsx
index 2861ca084..c3fde663a 100644
--- a/libs/shared/lib/schema/schema-utils/Types.tsx
+++ b/libs/shared/lib/schema/schema-utils/Types.tsx
@@ -32,6 +32,7 @@ export enum AttributeCategory {
 // };
 
 export interface SchemaGraphData {
+  name: string;
   attributes: Attributes[];
   nodeCount: number;
   summedNullAmount: number;
diff --git a/libs/shared/lib/vis/nodelink/ResultNodeLinkParserUseCase.tsx b/libs/shared/lib/vis/nodelink/ResultNodeLinkParserUseCase.tsx
index e1e0c9fee..1a185ac4b 100644
--- a/libs/shared/lib/vis/nodelink/ResultNodeLinkParserUseCase.tsx
+++ b/libs/shared/lib/vis/nodelink/ResultNodeLinkParserUseCase.tsx
@@ -3,8 +3,8 @@
  * Utrecht University within the Software Project course.
  * © Copyright Utrecht University (Department of Information and Computing Sciences)
  */
-import { GraphType, LinkType, NodeType } from "./Types";
-import { Edge, GraphQueryResult } from "../../data-access/store";
+import { GraphType, LinkType, NodeType } from './Types';
+import { Edge, GraphQueryResult } from '../../data-access/store';
 /** ResultNodeLinkParserUseCase implements methods to parse and translate websocket messages from the backend into a GraphType. */
 
 /**
@@ -37,7 +37,7 @@ export interface Link extends AxisType {
 
 /** Gets the group to which the node/edge belongs */
 export function getGroupName(axisType: AxisType): string {
-  return axisType.id.split("/")[0];
+  return axisType.id.split('/')[0];
 }
 
 /** Returns true if the given id belongs to the target group. */
@@ -56,19 +56,19 @@ export function isNodeLinkResult(
   jsonObject: any
 ): jsonObject is NodeLinkResultType {
   if (
-    typeof jsonObject === "object" &&
+    typeof jsonObject === 'object' &&
     jsonObject !== null &&
-    "nodes" in jsonObject &&
-    "edges" in jsonObject
+    'nodes' in jsonObject &&
+    'edges' in jsonObject
   ) {
     if (!Array.isArray(jsonObject.nodes) || !Array.isArray(jsonObject.edges))
       return false;
 
     const validNodes = jsonObject.nodes.every(
-      (node: any) => "id" in node && "attributes" in node
+      (node: any) => 'id' in node && 'attributes' in node
     );
     const validEdges = jsonObject.edges.every(
-      (edge: any) => "from" in edge && "to" in edge
+      (edge: any) => 'from' in edge && 'to' in edge
     );
 
     return validNodes && validEdges;
@@ -119,13 +119,13 @@ export class ParseToUniqueEdges {
       if (!isLinkPredictionData) {
         for (let j = 0; j < queryResultEdges.length; j++) {
           const newLink =
-            queryResultEdges[j].from + ":" + queryResultEdges[j].to;
+            queryResultEdges[j].from + ':' + queryResultEdges[j].to;
           edgesMap.set(newLink, (edgesMap.get(newLink) || 0) + 1);
           attriMap.set(newLink, queryResultEdges[j].attributes);
         }
 
         edgesMap.forEach((count, key) => {
-          const fromTo = key.split(":");
+          const fromTo = key.split(':');
           edges.push({
             from: fromTo[0],
             to: fromTo[1],
@@ -168,8 +168,8 @@ export default class ResultNodeLinkParserUseCase {
 
     for (let i = 0; i < queryResult.nodes.length; i++) {
       // Assigns a group to every entity type for color coding
-      const nodeId = queryResult.nodes[i].id + "/";
-      const entityType = nodeId.split("/")[0];
+      const nodeId = queryResult.nodes[i].id + '/';
+      const entityType = nodeId.split('/')[0];
 
       // The preferred text to be shown on top of the node
       let preferredText = nodeId;
@@ -201,7 +201,7 @@ export default class ResultNodeLinkParserUseCase {
       let mlExtra = {};
       if (
         queryResult.nodes[i].mldata &&
-        typeof queryResult.nodes[i].mldata != "number"
+        typeof queryResult.nodes[i].mldata != 'number'
       ) {
         mlExtra = {
           shortestPathData: queryResult.nodes[i].mldata as Record<
@@ -210,7 +210,7 @@ export default class ResultNodeLinkParserUseCase {
           >,
         };
         shortestPathInResult = true;
-      } else if (typeof queryResult.nodes[i].mldata == "number") {
+      } else if (typeof queryResult.nodes[i].mldata == 'number') {
         // mldata + 1 so you dont get 0, which is interpreted as 'undefined'
         const numberOfCluster = (queryResult.nodes[i].mldata as number) + 1;
         mlExtra = {
diff --git a/libs/shared/lib/vis/nodelink/nodelinkvis.stories.tsx b/libs/shared/lib/vis/nodelink/nodelinkvis.stories.tsx
index f34405056..988444d37 100644
--- a/libs/shared/lib/vis/nodelink/nodelinkvis.stories.tsx
+++ b/libs/shared/lib/vis/nodelink/nodelinkvis.stories.tsx
@@ -1,6 +1,6 @@
-import React from "react";
-import { ComponentStory, Meta } from "@storybook/react";
-import { NodeLinkVis } from "./nodelinkvis";
+import React from 'react';
+import { ComponentStory, Meta } from '@storybook/react';
+import { NodeLinkVis } from './nodelinkvis';
 
 import {
   assignNewGraphQueryResult,
@@ -8,21 +8,21 @@ import {
   graphQueryResultSlice,
   resetGraphQueryResults,
   store,
-} from "../../data-access/store";
-import { configureStore } from "@reduxjs/toolkit";
-import { Provider } from "react-redux";
-import { GraphPolarisThemeProvider } from "../../data-access/theme";
+} from '../../data-access/store';
+import { configureStore } from '@reduxjs/toolkit';
+import { Provider } from 'react-redux';
+import { GraphPolarisThemeProvider } from '../../data-access/theme';
 import {
   big2ndChamberQueryResult,
   smallFlightsQueryResults,
-} from "../../mock-data";
+} from '../../mock-data';
 
 const Component: Meta<typeof NodeLinkVis> = {
   /* 👇 The title prop is optional.
    * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading
    * to learn how to generate automatic titles
    */
-  title: "Components/Visualizations/NodeLinkVis",
+  title: 'Components/Visualizations/NodeLinkVis',
   component: NodeLinkVis,
   decorators: [
     (story) => (
@@ -30,8 +30,8 @@ const Component: Meta<typeof NodeLinkVis> = {
         <GraphPolarisThemeProvider>
           <div
             style={{
-              width: "100%",
-              height: "100vh",
+              width: '100%',
+              height: '100vh',
             }}
           >
             {story()}
@@ -62,11 +62,11 @@ TestWithData.play = async () => {
   dispatch(
     assignNewGraphQueryResult({
       nodes: [
-        { id: "agent/007", attributes: { name: "Daniel Craig" } },
-        { id: "villain", attributes: { name: "Le Chiffre" } },
+        { id: 'agent/007', attributes: { name: 'Daniel Craig' } },
+        { id: 'villain', attributes: { name: 'Le Chiffre' } },
       ],
-      links: [
-        { from: "agent/007", to: "villain", attributes: { name: "Escape" } },
+      edges: [
+        { from: 'agent/007', to: 'villain', attributes: { name: 'Escape' } },
       ],
     })
   );
@@ -81,7 +81,7 @@ TestWithNoData.play = async () => {
   dispatch(
     assignNewGraphQueryResult({
       nodes: [],
-      links: [],
+      edges: [],
     })
   );
 };
diff --git a/libs/shared/lib/vis/nodelink/nodelinkvis.tsx b/libs/shared/lib/vis/nodelink/nodelinkvis.tsx
index 30b71e803..a1805f7b5 100644
--- a/libs/shared/lib/vis/nodelink/nodelinkvis.tsx
+++ b/libs/shared/lib/vis/nodelink/nodelinkvis.tsx
@@ -15,7 +15,7 @@ import ResultNodeLinkParserUseCase from './ResultNodeLinkParserUseCase';
 
 interface Props {
   loading?: boolean;
-  currentColours: any;
+  // currentColours: any;
 }
 
 const Div = styled.div`
diff --git a/libs/shared/lib/vis/rawjsonvis/rawjsonvis.stories.tsx b/libs/shared/lib/vis/rawjsonvis/rawjsonvis.stories.tsx
index 688a89dab..e66fde6f4 100644
--- a/libs/shared/lib/vis/rawjsonvis/rawjsonvis.stories.tsx
+++ b/libs/shared/lib/vis/rawjsonvis/rawjsonvis.stories.tsx
@@ -1,6 +1,6 @@
-import React from "react";
-import { ComponentStory, Meta } from "@storybook/react";
-import { RawJSONVis } from "./rawjsonvis";
+import React from 'react';
+import { ComponentStory, Meta } from '@storybook/react';
+import { RawJSONVis } from './rawjsonvis';
 
 import {
   assignNewGraphQueryResult,
@@ -8,17 +8,17 @@ import {
   graphQueryResultSlice,
   resetGraphQueryResults,
   store,
-} from "../../data-access/store";
-import { configureStore } from "@reduxjs/toolkit";
-import { Provider } from "react-redux";
-import { GraphPolarisThemeProvider } from "../../data-access/theme";
+} from '../../data-access/store';
+import { configureStore } from '@reduxjs/toolkit';
+import { Provider } from 'react-redux';
+import { GraphPolarisThemeProvider } from '../../data-access/theme';
 
 const Component: Meta<typeof RawJSONVis> = {
   /* 👇 The title prop is optional.
    * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading
    * to learn how to generate automatic titles
    */
-  title: "Components/Visualizations/RawJSONVIS",
+  title: 'Components/Visualizations/RawJSONVIS',
   component: RawJSONVis,
   decorators: [
     (story) => (
@@ -49,10 +49,10 @@ TestWithData.play = async () => {
   dispatch(
     assignNewGraphQueryResult({
       nodes: [
-        { id: "agent/007", attributes: { name: "Daniel Craig" } },
-        { id: "villain", attributes: { name: "Le Chiffre" } },
+        { id: 'agent/007', attributes: { name: 'Daniel Craig' } },
+        { id: 'villain', attributes: { name: 'Le Chiffre' } },
       ],
-      links: [],
+      edges: [],
     })
   );
 };
diff --git a/libs/shared/lib/vis/rawjsonvis/rawjsonvis.tsx b/libs/shared/lib/vis/rawjsonvis/rawjsonvis.tsx
index 66d63b79e..61ebc2364 100644
--- a/libs/shared/lib/vis/rawjsonvis/rawjsonvis.tsx
+++ b/libs/shared/lib/vis/rawjsonvis/rawjsonvis.tsx
@@ -33,7 +33,7 @@ export const RawJSONVis = React.memo((props: RawJSONVisProps) => {
   const loading = props.loading;
 
   return (
-    <>
+    <div className="overflow-scroll">
       <input
         onChange={(v) =>
           dispatch(changePrimary({ main: v.currentTarget.value }))
@@ -76,7 +76,7 @@ export const RawJSONVis = React.memo((props: RawJSONVisProps) => {
           </div>
         </div>
       )}
-    </>
+    </div>
   );
 });
 
diff --git a/libs/shared/tsconfig.json b/libs/shared/tsconfig.json
index faa063642..855b96076 100644
--- a/libs/shared/tsconfig.json
+++ b/libs/shared/tsconfig.json
@@ -29,5 +29,6 @@
   },
   "exclude": ["dist", "build", "node_modules"],
   "include": ["src", "lib"],
+  "files": ["./node.d.ts"],
   "references": [{ "path": "./tsconfig.node.json" }]
 }
diff --git a/libs/shared/tsconfig.node.json b/libs/shared/tsconfig.node.json
index e5cd6295c..223fa3b61 100644
--- a/libs/shared/tsconfig.node.json
+++ b/libs/shared/tsconfig.node.json
@@ -3,7 +3,8 @@
     "composite": true,
     "module": "ESNext",
     "moduleResolution": "node",
-    "allowSyntheticDefaultImports": true
+    "allowSyntheticDefaultImports": true,
+    "types": ["node", "vite/client"]
   },
   "include": ["vite.config.ts"]
 }
diff --git a/libs/storybook/.env b/libs/storybook/.env
new file mode 100644
index 000000000..fd336427c
--- /dev/null
+++ b/libs/storybook/.env
@@ -0,0 +1,2 @@
+VITE_BACKEND_URL=null
+VITE_STAGING=sb
\ No newline at end of file
diff --git a/package.json b/package.json
index 2404d0c90..b88691351 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,5 @@
 {
-  "name": "my-turborepo",
+  "name": "graphpolaris-monorepo",
   "version": "0.0.0",
   "private": true,
   "workspaces": [
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 14b92d621..8c7b9194b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -26,6 +26,43 @@ importers:
         specifier: latest
         version: 1.9.3
 
+  apps/docs:
+    dependencies:
+      next:
+        specifier: ^13.1.1
+        version: 13.1.1(@babel/core@7.21.3)(react-dom@18.2.0)(react@18.2.0)
+      react:
+        specifier: 18.2.0
+        version: 18.2.0
+      react-dom:
+        specifier: 18.2.0
+        version: 18.2.0(react@18.2.0)
+    devDependencies:
+      '@babel/core':
+        specifier: ^7.0.0
+        version: 7.21.3
+      '@types/node':
+        specifier: ^17.0.12
+        version: 17.0.12
+      '@types/react':
+        specifier: ^18.0.22
+        version: 18.0.28
+      '@types/react-dom':
+        specifier: ^18.0.7
+        version: 18.0.11
+      eslint:
+        specifier: 7.32.0
+        version: 7.32.0
+      eslint-config-custom:
+        specifier: workspace:*
+        version: link:../../libs/workspace/eslint-config-custom
+      tsconfig:
+        specifier: workspace:*
+        version: link:../../libs/workspace/tsconfig
+      typescript:
+        specifier: ^4.5.3
+        version: 4.9.5
+
   apps/web:
     dependencies:
       '@graphpolaris/shared':
@@ -92,12 +129,21 @@ importers:
       '@vitejs/plugin-react-swc':
         specifier: ^3.0.0
         version: 3.2.0(vite@4.2.1)
+      autoprefixer:
+        specifier: ^10.4.14
+        version: 10.4.14(postcss@8.4.21)
       graphology-types:
         specifier: ^0.24.7
         version: 0.24.7
+      postcss:
+        specifier: ^8.4.21
+        version: 8.4.21
       react-is:
         specifier: ^18.2.0
         version: 18.2.0
+      tailwindcss:
+        specifier: ^3.3.1
+        version: 3.3.1(postcss@8.4.21)(ts-node@10.9.1)
       typescript:
         specifier: ^4.9.3
         version: 4.9.5
@@ -106,7 +152,7 @@ importers:
         version: 4.2.1(@types/node@18.13.0)(less@4.1.3)(sass@1.59.3)
       vite-plugin-dts:
         specifier: ^2.1.0
-        version: 2.1.0(@types/node@18.13.0)(vite@4.2.1)
+        version: 2.1.0(@types/node@17.0.12)(vite@4.2.1)
       vitest:
         specifier: ^0.29.2
         version: 0.29.4(happy-dom@8.9.0)(jsdom@21.1.1)(sass@1.59.3)
@@ -502,7 +548,6 @@ packages:
     dependencies:
       '@jridgewell/gen-mapping': 0.1.1
       '@jridgewell/trace-mapping': 0.3.17
-    dev: true
 
   /@aw-web-design/x-default-browser@1.4.88:
     resolution: {integrity: sha512-AkEmF0wcwYC2QkhK703Y83fxWARttIWXDmQN8+cof8FmFZ5BRhnNXGymeb1S73bOCLfWjYELxtujL56idCN/XA==}
@@ -525,7 +570,6 @@ packages:
   /@babel/compat-data@7.21.4:
     resolution: {integrity: sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g==}
     engines: {node: '>=6.9.0'}
-    dev: true
 
   /@babel/core@7.21.3:
     resolution: {integrity: sha512-qIJONzoa/qiHghnm0l1n4i/6IIziDpzqc36FBs4pzMhDUraHqponwJLiAKm1hGLP3OSB/TVNz6rMwVGpwxxySw==}
@@ -548,7 +592,6 @@ packages:
       semver: 6.3.0
     transitivePeerDependencies:
       - supports-color
-    dev: true
 
   /@babel/generator@7.21.3:
     resolution: {integrity: sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==}
@@ -585,7 +628,6 @@ packages:
       browserslist: 4.21.5
       lru-cache: 5.1.1
       semver: 6.3.0
-    dev: true
 
   /@babel/helper-create-class-features-plugin@7.21.0(@babel/core@7.21.3):
     resolution: {integrity: sha512-Q8wNiMIdwsv5la5SPxNYzzkPnjgC0Sy0i7jLkVOCdllu/xcVNkr3TeZzbHBJrj+XXRqzX5uCyCoV9eu6xUG7KQ==}
@@ -684,7 +726,6 @@ packages:
       '@babel/types': 7.21.4
     transitivePeerDependencies:
       - supports-color
-    dev: true
 
   /@babel/helper-optimise-call-expression@7.18.6:
     resolution: {integrity: sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==}
@@ -732,7 +773,6 @@ packages:
     engines: {node: '>=6.9.0'}
     dependencies:
       '@babel/types': 7.21.4
-    dev: true
 
   /@babel/helper-skip-transparent-expression-wrappers@7.20.0:
     resolution: {integrity: sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==}
@@ -758,7 +798,6 @@ packages:
   /@babel/helper-validator-option@7.21.0:
     resolution: {integrity: sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==}
     engines: {node: '>=6.9.0'}
-    dev: true
 
   /@babel/helper-wrap-function@7.20.5:
     resolution: {integrity: sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==}
@@ -781,7 +820,6 @@ packages:
       '@babel/types': 7.21.4
     transitivePeerDependencies:
       - supports-color
-    dev: true
 
   /@babel/highlight@7.18.6:
     resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==}
@@ -1889,15 +1927,15 @@ packages:
       '@commitlint/execute-rule': 17.4.0
       '@commitlint/resolve-extends': 17.4.4
       '@commitlint/types': 17.4.4
-      '@types/node': 18.13.0
+      '@types/node': 17.0.12
       chalk: 4.1.2
       cosmiconfig: 8.1.3
-      cosmiconfig-typescript-loader: 4.3.0(@types/node@18.13.0)(cosmiconfig@8.1.3)(ts-node@10.9.1)(typescript@4.9.5)
+      cosmiconfig-typescript-loader: 4.3.0(@types/node@17.0.12)(cosmiconfig@8.1.3)(ts-node@10.9.1)(typescript@4.9.5)
       lodash.isplainobject: 4.0.6
       lodash.merge: 4.6.2
       lodash.uniq: 4.5.0
       resolve-from: 5.0.0
-      ts-node: 10.9.1(@types/node@18.13.0)(typescript@4.9.5)
+      ts-node: 10.9.1(@types/node@17.0.12)(typescript@4.9.5)
       typescript: 4.9.5
     transitivePeerDependencies:
       - '@swc/core'
@@ -2511,6 +2549,21 @@ packages:
     dev: true
     optional: true
 
+  /@eslint-community/eslint-utils@4.4.0(eslint@8.40.0):
+    resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+    dependencies:
+      eslint: 8.40.0
+      eslint-visitor-keys: 3.4.1
+    dev: true
+
+  /@eslint-community/regexpp@4.5.1:
+    resolution: {integrity: sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==}
+    engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
+    dev: true
+
   /@eslint/eslintrc@0.4.3:
     resolution: {integrity: sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==}
     engines: {node: ^10.12.0 || >=12.0.0}
@@ -2527,10 +2580,43 @@ packages:
     transitivePeerDependencies:
       - supports-color
 
+  /@eslint/eslintrc@2.0.3:
+    resolution: {integrity: sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    dependencies:
+      ajv: 6.12.6
+      debug: 4.3.4(supports-color@5.5.0)
+      espree: 9.5.2
+      globals: 13.20.0
+      ignore: 5.2.4
+      import-fresh: 3.3.0
+      js-yaml: 4.1.0
+      minimatch: 3.1.2
+      strip-json-comments: 3.1.1
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@eslint/js@8.40.0:
+    resolution: {integrity: sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    dev: true
+
   /@fal-works/esbuild-plugin-global-externals@2.1.2:
     resolution: {integrity: sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==}
     dev: true
 
+  /@humanwhocodes/config-array@0.11.8:
+    resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==}
+    engines: {node: '>=10.10.0'}
+    dependencies:
+      '@humanwhocodes/object-schema': 1.2.1
+      debug: 4.3.4(supports-color@5.5.0)
+      minimatch: 3.1.2
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /@humanwhocodes/config-array@0.5.0:
     resolution: {integrity: sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==}
     engines: {node: '>=10.10.0'}
@@ -2541,6 +2627,11 @@ packages:
     transitivePeerDependencies:
       - supports-color
 
+  /@humanwhocodes/module-importer@1.0.1:
+    resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
+    engines: {node: '>=12.22'}
+    dev: true
+
   /@humanwhocodes/object-schema@1.2.1:
     resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==}
 
@@ -2596,7 +2687,7 @@ packages:
     dependencies:
       '@types/istanbul-lib-coverage': 2.0.4
       '@types/istanbul-reports': 3.0.1
-      '@types/node': 18.13.0
+      '@types/node': 17.0.12
       '@types/yargs': 16.0.5
       chalk: 4.1.2
     dev: true
@@ -2608,7 +2699,7 @@ packages:
       '@jest/schemas': 29.4.3
       '@types/istanbul-lib-coverage': 2.0.4
       '@types/istanbul-reports': 3.0.1
-      '@types/node': 18.13.0
+      '@types/node': 17.0.12
       '@types/yargs': 17.0.23
       chalk: 4.1.2
     dev: true
@@ -2636,7 +2727,6 @@ packages:
     dependencies:
       '@jridgewell/set-array': 1.1.2
       '@jridgewell/sourcemap-codec': 1.4.14
-    dev: true
 
   /@jridgewell/gen-mapping@0.3.2:
     resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==}
@@ -2709,6 +2799,16 @@ packages:
       react: 18.2.0
     dev: true
 
+  /@microsoft/api-extractor-model@7.26.4(@types/node@17.0.12):
+    resolution: {integrity: sha512-PDCgCzXDo+SLY5bsfl4bS7hxaeEtnXj7XtuzEE+BtALp7B5mK/NrS2kHWU69pohgsRmEALycQdaQPXoyT2i5MQ==}
+    dependencies:
+      '@microsoft/tsdoc': 0.14.2
+      '@microsoft/tsdoc-config': 0.16.2
+      '@rushstack/node-core-library': 3.55.2(@types/node@17.0.12)
+    transitivePeerDependencies:
+      - '@types/node'
+    dev: true
+
   /@microsoft/api-extractor-model@7.26.4(@types/node@18.13.0):
     resolution: {integrity: sha512-PDCgCzXDo+SLY5bsfl4bS7hxaeEtnXj7XtuzEE+BtALp7B5mK/NrS2kHWU69pohgsRmEALycQdaQPXoyT2i5MQ==}
     dependencies:
@@ -2719,6 +2819,26 @@ packages:
       - '@types/node'
     dev: true
 
+  /@microsoft/api-extractor@7.34.4(@types/node@17.0.12):
+    resolution: {integrity: sha512-HOdcci2nT40ejhwPC3Xja9G+WSJmWhCUKKryRfQYsmE9cD+pxmBaKBKCbuS9jUcl6bLLb4Gz+h7xEN5r0QiXnQ==}
+    hasBin: true
+    dependencies:
+      '@microsoft/api-extractor-model': 7.26.4(@types/node@17.0.12)
+      '@microsoft/tsdoc': 0.14.2
+      '@microsoft/tsdoc-config': 0.16.2
+      '@rushstack/node-core-library': 3.55.2(@types/node@17.0.12)
+      '@rushstack/rig-package': 0.3.18
+      '@rushstack/ts-command-line': 4.13.2
+      colors: 1.2.5
+      lodash: 4.17.21
+      resolve: 1.22.1
+      semver: 7.3.8
+      source-map: 0.6.1
+      typescript: 4.8.4
+    transitivePeerDependencies:
+      - '@types/node'
+    dev: true
+
   /@microsoft/api-extractor@7.34.4(@types/node@18.13.0):
     resolution: {integrity: sha512-HOdcci2nT40ejhwPC3Xja9G+WSJmWhCUKKryRfQYsmE9cD+pxmBaKBKCbuS9jUcl6bLLb4Gz+h7xEN5r0QiXnQ==}
     hasBin: true
@@ -2975,11 +3095,132 @@ packages:
       tar-fs: 2.1.1
     dev: true
 
+  /@next/env@13.1.1:
+    resolution: {integrity: sha512-vFMyXtPjSAiOXOywMojxfKIqE3VWN5RCAx+tT3AS3pcKjMLFTCJFUWsKv8hC+87Z1F4W3r68qTwDFZIFmd5Xkw==}
+    dev: false
+
   /@next/eslint-plugin-next@13.0.0:
     resolution: {integrity: sha512-z+gnX4Zizatqatc6f4CQrcC9oN8Us3Vrq/OLyc98h7K/eWctrnV91zFZodmJHUjx0cITY8uYM7LXD7IdYkg3kg==}
     dependencies:
       glob: 7.1.7
 
+  /@next/swc-android-arm-eabi@13.1.1:
+    resolution: {integrity: sha512-qnFCx1kT3JTWhWve4VkeWuZiyjG0b5T6J2iWuin74lORCupdrNukxkq9Pm+Z7PsatxuwVJMhjUoYz7H4cWzx2A==}
+    engines: {node: '>= 10'}
+    cpu: [arm]
+    os: [android]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@next/swc-android-arm64@13.1.1:
+    resolution: {integrity: sha512-eCiZhTzjySubNqUnNkQCjU3Fh+ep3C6b5DCM5FKzsTH/3Gr/4Y7EiaPZKILbvnXmhWtKPIdcY6Zjx51t4VeTfA==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [android]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@next/swc-darwin-arm64@13.1.1:
+    resolution: {integrity: sha512-9zRJSSIwER5tu9ADDkPw5rIZ+Np44HTXpYMr0rkM656IvssowPxmhK0rTreC1gpUCYwFsRbxarUJnJsTWiutPg==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [darwin]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@next/swc-darwin-x64@13.1.1:
+    resolution: {integrity: sha512-qWr9qEn5nrnlhB0rtjSdR00RRZEtxg4EGvicIipqZWEyayPxhUu6NwKiG8wZiYZCLfJ5KWr66PGSNeDMGlNaiA==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [darwin]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@next/swc-freebsd-x64@13.1.1:
+    resolution: {integrity: sha512-UwP4w/NcQ7V/VJEj3tGVszgb4pyUCt3lzJfUhjDMUmQbzG9LDvgiZgAGMYH6L21MoyAATJQPDGiAMWAPKsmumA==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [freebsd]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@next/swc-linux-arm-gnueabihf@13.1.1:
+    resolution: {integrity: sha512-CnsxmKHco9sosBs1XcvCXP845Db+Wx1G0qouV5+Gr+HT/ZlDYEWKoHVDgnJXLVEQzq4FmHddBNGbXvgqM1Gfkg==}
+    engines: {node: '>= 10'}
+    cpu: [arm]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@next/swc-linux-arm64-gnu@13.1.1:
+    resolution: {integrity: sha512-JfDq1eri5Dif+VDpTkONRd083780nsMCOKoFG87wA0sa4xL8LGcXIBAkUGIC1uVy9SMsr2scA9CySLD/i+Oqiw==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@next/swc-linux-arm64-musl@13.1.1:
+    resolution: {integrity: sha512-GA67ZbDq2AW0CY07zzGt07M5b5Yaq5qUpFIoW3UFfjOPgb0Sqf3DAW7GtFMK1sF4ROHsRDMGQ9rnT0VM2dVfKA==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@next/swc-linux-x64-gnu@13.1.1:
+    resolution: {integrity: sha512-nnjuBrbzvqaOJaV+XgT8/+lmXrSCOt1YYZn/irbDb2fR2QprL6Q7WJNgwsZNxiLSfLdv+2RJGGegBx9sLBEzGA==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@next/swc-linux-x64-musl@13.1.1:
+    resolution: {integrity: sha512-CM9xnAQNIZ8zf/igbIT/i3xWbQZYaF397H+JroF5VMOCUleElaMdQLL5riJml8wUfPoN3dtfn2s4peSr3azz/g==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@next/swc-win32-arm64-msvc@13.1.1:
+    resolution: {integrity: sha512-pzUHOGrbgfGgPlOMx9xk3QdPJoRPU+om84hqVoe6u+E0RdwOG0Ho/2UxCgDqmvpUrMab1Deltlt6RqcXFpnigQ==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [win32]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@next/swc-win32-ia32-msvc@13.1.1:
+    resolution: {integrity: sha512-WeX8kVS46aobM9a7Xr/kEPcrTyiwJqQv/tbw6nhJ4fH9xNZ+cEcyPoQkwPo570dCOLz3Zo9S2q0E6lJ/EAUOBg==}
+    engines: {node: '>= 10'}
+    cpu: [ia32]
+    os: [win32]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@next/swc-win32-x64-msvc@13.1.1:
+    resolution: {integrity: sha512-mVF0/3/5QAc5EGVnb8ll31nNvf3BWpPY4pBb84tk+BfQglWLqc5AC9q1Ht/YMWiEgs8ALNKEQ3GQnbY0bJF2Gg==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [win32]
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /@nodelib/fs.scandir@2.1.5:
     resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
     engines: {node: '>= 8'}
@@ -3577,6 +3818,24 @@ packages:
   /@rushstack/eslint-patch@1.2.0:
     resolution: {integrity: sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==}
 
+  /@rushstack/node-core-library@3.55.2(@types/node@17.0.12):
+    resolution: {integrity: sha512-SaLe/x/Q/uBVdNFK5V1xXvsVps0y7h1sN7aSJllQyFbugyOaxhNRF25bwEDnicARNEjJw0pk0lYnJQ9Kr6ev0A==}
+    peerDependencies:
+      '@types/node': '*'
+    peerDependenciesMeta:
+      '@types/node':
+        optional: true
+    dependencies:
+      '@types/node': 17.0.12
+      colors: 1.2.5
+      fs-extra: 7.0.1
+      import-lazy: 4.0.0
+      jju: 1.4.0
+      resolve: 1.22.1
+      semver: 7.3.8
+      z-schema: 5.0.5
+    dev: true
+
   /@rushstack/node-core-library@3.55.2(@types/node@18.13.0):
     resolution: {integrity: sha512-SaLe/x/Q/uBVdNFK5V1xXvsVps0y7h1sN7aSJllQyFbugyOaxhNRF25bwEDnicARNEjJw0pk0lYnJQ9Kr6ev0A==}
     peerDependencies:
@@ -5189,6 +5448,12 @@ packages:
       '@swc/core-win32-x64-msvc': 1.3.42
     dev: true
 
+  /@swc/helpers@0.4.14:
+    resolution: {integrity: sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==}
+    dependencies:
+      tslib: 2.5.0
+    dev: false
+
   /@testing-library/dom@8.20.0:
     resolution: {integrity: sha512-d9ULIT+a4EXLX3UU8FBjauG9NnsZHkHztXoIcTsOKoOw030fyjheN9svkTULjJxtYag9DZz5Jz5qkWZDPxTFwA==}
     engines: {node: '>=12'}
@@ -5336,7 +5601,7 @@ packages:
     resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==}
     dependencies:
       '@types/connect': 3.4.35
-      '@types/node': 18.13.0
+      '@types/node': 17.0.12
     dev: true
 
   /@types/chai-subset@1.3.3:
@@ -5368,7 +5633,7 @@ packages:
   /@types/connect@3.4.35:
     resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==}
     dependencies:
-      '@types/node': 18.13.0
+      '@types/node': 17.0.12
     dev: true
 
   /@types/cookie@0.3.3:
@@ -5574,7 +5839,7 @@ packages:
   /@types/express-serve-static-core@4.17.33:
     resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==}
     dependencies:
-      '@types/node': 18.13.0
+      '@types/node': 17.0.12
       '@types/qs': 6.9.7
       '@types/range-parser': 1.2.4
     dev: true
@@ -5599,20 +5864,20 @@ packages:
     resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==}
     dependencies:
       '@types/minimatch': 5.1.2
-      '@types/node': 18.13.0
+      '@types/node': 17.0.12
     dev: true
 
   /@types/glob@8.1.0:
     resolution: {integrity: sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==}
     dependencies:
       '@types/minimatch': 5.1.2
-      '@types/node': 18.13.0
+      '@types/node': 17.0.12
     dev: true
 
   /@types/graceful-fs@4.1.6:
     resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==}
     dependencies:
-      '@types/node': 18.13.0
+      '@types/node': 17.0.12
     dev: true
 
   /@types/hoist-non-react-statics@3.3.1:
@@ -5675,7 +5940,7 @@ packages:
   /@types/node-fetch@2.6.2:
     resolution: {integrity: sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==}
     dependencies:
-      '@types/node': 18.13.0
+      '@types/node': 17.0.12
       form-data: 3.0.1
     dev: true
 
@@ -5683,6 +5948,10 @@ packages:
     resolution: {integrity: sha512-ZOzvDRWp8dCVBmgnkIqYCArgdFOO9YzocZp8Ra25N/RStKiWvMOXHMz+GjSeVNe5TstaTmTWPucGJkDw0XXJWA==}
     dev: true
 
+  /@types/node@17.0.12:
+    resolution: {integrity: sha512-4YpbAsnJXWYK/fpTVFlMIcUIho2AYCi4wg5aNPrG1ng7fn/1/RZfCIpRCiBX+12RVa34RluilnvCqD+g3KiSiA==}
+    dev: true
+
   /@types/node@18.13.0:
     resolution: {integrity: sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==}
     dev: true
@@ -5771,7 +6040,7 @@ packages:
     resolution: {integrity: sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==}
     dependencies:
       '@types/mime': 3.0.1
-      '@types/node': 18.13.0
+      '@types/node': 17.0.12
     dev: true
 
   /@types/styled-components@5.1.26:
@@ -5836,54 +6105,122 @@ packages:
       - supports-color
     dev: true
 
-  /@typescript-eslint/parser@5.52.0(eslint@7.32.0)(typescript@4.9.5):
-    resolution: {integrity: sha512-e2KiLQOZRo4Y0D/b+3y08i3jsekoSkOYStROYmPUnGMEoA0h+k2qOH5H6tcjIc68WDvGwH+PaOrP1XRzLJ6QlA==}
+  /@typescript-eslint/eslint-plugin@5.52.0(@typescript-eslint/parser@5.52.0)(eslint@8.40.0)(typescript@4.9.5):
+    resolution: {integrity: sha512-lHazYdvYVsBokwCdKOppvYJKaJ4S41CgKBcPvyd0xjZNbvQdhn/pnJlGtQksQ/NhInzdaeaSarlBjDXHuclEbg==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     peerDependencies:
+      '@typescript-eslint/parser': ^5.0.0
       eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
       typescript: '*'
     peerDependenciesMeta:
       typescript:
         optional: true
     dependencies:
+      '@typescript-eslint/parser': 5.52.0(eslint@8.40.0)(typescript@4.9.5)
       '@typescript-eslint/scope-manager': 5.52.0
-      '@typescript-eslint/types': 5.52.0
-      '@typescript-eslint/typescript-estree': 5.52.0(typescript@4.9.5)
+      '@typescript-eslint/type-utils': 5.52.0(eslint@8.40.0)(typescript@4.9.5)
+      '@typescript-eslint/utils': 5.52.0(eslint@8.40.0)(typescript@4.9.5)
       debug: 4.3.4(supports-color@5.5.0)
-      eslint: 7.32.0
+      eslint: 8.40.0
+      grapheme-splitter: 1.0.4
+      ignore: 5.2.4
+      natural-compare-lite: 1.4.0
+      regexpp: 3.2.0
+      semver: 7.3.8
+      tsutils: 3.21.0(typescript@4.9.5)
       typescript: 4.9.5
     transitivePeerDependencies:
       - supports-color
+    dev: true
 
-  /@typescript-eslint/scope-manager@5.52.0:
-    resolution: {integrity: sha512-AR7sxxfBKiNV0FWBSARxM8DmNxrwgnYMPwmpkC1Pl1n+eT8/I2NAUPuwDy/FmDcC6F8pBfmOcaxcxRHspgOBMw==}
-    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
-    dependencies:
-      '@typescript-eslint/types': 5.52.0
-      '@typescript-eslint/visitor-keys': 5.52.0
-
-  /@typescript-eslint/type-utils@5.52.0(eslint@7.32.0)(typescript@4.9.5):
-    resolution: {integrity: sha512-tEKuUHfDOv852QGlpPtB3lHOoig5pyFQN/cUiZtpw99D93nEBjexRLre5sQZlkMoHry/lZr8qDAt2oAHLKA6Jw==}
+  /@typescript-eslint/parser@5.52.0(eslint@7.32.0)(typescript@4.9.5):
+    resolution: {integrity: sha512-e2KiLQOZRo4Y0D/b+3y08i3jsekoSkOYStROYmPUnGMEoA0h+k2qOH5H6tcjIc68WDvGwH+PaOrP1XRzLJ6QlA==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     peerDependencies:
-      eslint: '*'
+      eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
       typescript: '*'
     peerDependenciesMeta:
       typescript:
         optional: true
     dependencies:
+      '@typescript-eslint/scope-manager': 5.52.0
+      '@typescript-eslint/types': 5.52.0
       '@typescript-eslint/typescript-estree': 5.52.0(typescript@4.9.5)
-      '@typescript-eslint/utils': 5.52.0(eslint@7.32.0)(typescript@4.9.5)
       debug: 4.3.4(supports-color@5.5.0)
       eslint: 7.32.0
-      tsutils: 3.21.0(typescript@4.9.5)
       typescript: 4.9.5
     transitivePeerDependencies:
       - supports-color
-    dev: true
 
-  /@typescript-eslint/types@5.52.0:
-    resolution: {integrity: sha512-oV7XU4CHYfBhk78fS7tkum+/Dpgsfi91IIDy7fjCyq2k6KB63M6gMC0YIvy+iABzmXThCRI6xpCEyVObBdWSDQ==}
+  /@typescript-eslint/parser@5.52.0(eslint@8.40.0)(typescript@4.9.5):
+    resolution: {integrity: sha512-e2KiLQOZRo4Y0D/b+3y08i3jsekoSkOYStROYmPUnGMEoA0h+k2qOH5H6tcjIc68WDvGwH+PaOrP1XRzLJ6QlA==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      '@typescript-eslint/scope-manager': 5.52.0
+      '@typescript-eslint/types': 5.52.0
+      '@typescript-eslint/typescript-estree': 5.52.0(typescript@4.9.5)
+      debug: 4.3.4(supports-color@5.5.0)
+      eslint: 8.40.0
+      typescript: 4.9.5
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@typescript-eslint/scope-manager@5.52.0:
+    resolution: {integrity: sha512-AR7sxxfBKiNV0FWBSARxM8DmNxrwgnYMPwmpkC1Pl1n+eT8/I2NAUPuwDy/FmDcC6F8pBfmOcaxcxRHspgOBMw==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    dependencies:
+      '@typescript-eslint/types': 5.52.0
+      '@typescript-eslint/visitor-keys': 5.52.0
+
+  /@typescript-eslint/type-utils@5.52.0(eslint@7.32.0)(typescript@4.9.5):
+    resolution: {integrity: sha512-tEKuUHfDOv852QGlpPtB3lHOoig5pyFQN/cUiZtpw99D93nEBjexRLre5sQZlkMoHry/lZr8qDAt2oAHLKA6Jw==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      eslint: '*'
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      '@typescript-eslint/typescript-estree': 5.52.0(typescript@4.9.5)
+      '@typescript-eslint/utils': 5.52.0(eslint@7.32.0)(typescript@4.9.5)
+      debug: 4.3.4(supports-color@5.5.0)
+      eslint: 7.32.0
+      tsutils: 3.21.0(typescript@4.9.5)
+      typescript: 4.9.5
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@typescript-eslint/type-utils@5.52.0(eslint@8.40.0)(typescript@4.9.5):
+    resolution: {integrity: sha512-tEKuUHfDOv852QGlpPtB3lHOoig5pyFQN/cUiZtpw99D93nEBjexRLre5sQZlkMoHry/lZr8qDAt2oAHLKA6Jw==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      eslint: '*'
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      '@typescript-eslint/typescript-estree': 5.52.0(typescript@4.9.5)
+      '@typescript-eslint/utils': 5.52.0(eslint@8.40.0)(typescript@4.9.5)
+      debug: 4.3.4(supports-color@5.5.0)
+      eslint: 8.40.0
+      tsutils: 3.21.0(typescript@4.9.5)
+      typescript: 4.9.5
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@typescript-eslint/types@5.52.0:
+    resolution: {integrity: sha512-oV7XU4CHYfBhk78fS7tkum+/Dpgsfi91IIDy7fjCyq2k6KB63M6gMC0YIvy+iABzmXThCRI6xpCEyVObBdWSDQ==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
 
   /@typescript-eslint/typescript-estree@5.52.0(typescript@4.9.5):
@@ -5926,12 +6263,32 @@ packages:
       - typescript
     dev: true
 
+  /@typescript-eslint/utils@5.52.0(eslint@8.40.0)(typescript@4.9.5):
+    resolution: {integrity: sha512-As3lChhrbwWQLNk2HC8Ree96hldKIqk98EYvypd3It8Q1f8d5zWyIoaZEp2va5667M4ZyE7X8UUR+azXrFl+NA==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
+    dependencies:
+      '@types/json-schema': 7.0.11
+      '@types/semver': 7.3.13
+      '@typescript-eslint/scope-manager': 5.52.0
+      '@typescript-eslint/types': 5.52.0
+      '@typescript-eslint/typescript-estree': 5.52.0(typescript@4.9.5)
+      eslint: 8.40.0
+      eslint-scope: 5.1.1
+      eslint-utils: 3.0.0(eslint@8.40.0)
+      semver: 7.3.8
+    transitivePeerDependencies:
+      - supports-color
+      - typescript
+    dev: true
+
   /@typescript-eslint/visitor-keys@5.52.0:
     resolution: {integrity: sha512-qMwpw6SU5VHCPr99y274xhbm+PRViK/NATY6qzt+Et7+mThGuFSl/ompj2/hrBlRP/kq+BFdgagnOSgw9TB0eA==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dependencies:
       '@typescript-eslint/types': 5.52.0
-      eslint-visitor-keys: 3.3.0
+      eslint-visitor-keys: 3.4.1
 
   /@vitejs/plugin-basic-ssl@1.0.1(vite@4.2.1):
     resolution: {integrity: sha512-pcub+YbFtFhaGRTo1832FQHQSHvMrlb43974e2eS8EKleR3p1cDdkJFPci1UhwkEf1J9Bz+wKBSzqpKp7nNj2A==}
@@ -6172,6 +6529,14 @@ packages:
     dependencies:
       acorn: 7.4.1
 
+  /acorn-jsx@5.3.2(acorn@8.8.2):
+    resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
+    peerDependencies:
+      acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+    dependencies:
+      acorn: 8.8.2
+    dev: true
+
   /acorn-walk@7.2.0:
     resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==}
     engines: {node: '>=0.4.0'}
@@ -6293,6 +6658,10 @@ packages:
     engines: {node: '>=12'}
     dev: true
 
+  /any-promise@1.3.0:
+    resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
+    dev: true
+
   /anymatch@3.1.3:
     resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
     engines: {node: '>= 8'}
@@ -6320,6 +6689,10 @@ packages:
     resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
     dev: true
 
+  /arg@5.0.2:
+    resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
+    dev: true
+
   /argparse@1.0.10:
     resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
     dependencies:
@@ -6448,6 +6821,22 @@ packages:
     hasBin: true
     dev: false
 
+  /autoprefixer@10.4.14(postcss@8.4.21):
+    resolution: {integrity: sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==}
+    engines: {node: ^10 || ^12 || >=14}
+    hasBin: true
+    peerDependencies:
+      postcss: ^8.1.0
+    dependencies:
+      browserslist: 4.21.5
+      caniuse-lite: 1.0.30001466
+      fraction.js: 4.2.0
+      normalize-range: 0.1.2
+      picocolors: 1.0.0
+      postcss: 8.4.21
+      postcss-value-parser: 4.2.0
+    dev: true
+
   /available-typed-arrays@1.0.5:
     resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==}
     engines: {node: '>= 0.4'}
@@ -6673,7 +7062,6 @@ packages:
       electron-to-chromium: 1.4.330
       node-releases: 2.0.10
       update-browserslist-db: 1.0.10(browserslist@4.21.5)
-    dev: true
 
   /bser@2.1.1:
     resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==}
@@ -6702,6 +7090,12 @@ packages:
       ieee754: 1.2.1
     dev: true
 
+  /builtins@5.0.1:
+    resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==}
+    dependencies:
+      semver: 7.3.8
+    dev: true
+
   /bytes@3.0.0:
     resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==}
     engines: {node: '>= 0.8'}
@@ -6776,7 +7170,6 @@ packages:
 
   /caniuse-lite@1.0.30001466:
     resolution: {integrity: sha512-ewtFBSfWjEmxUgNBSZItFSmVtvk9zkwkl1OfRZlKA8slltRN+/C/tuGVrF9styXkN36Yu3+SeJ1qkXxDEyNZ5w==}
-    dev: true
 
   /canvas@2.11.0:
     resolution: {integrity: sha512-bdTjFexjKJEwtIo0oRx8eD4G2yWoUOXP9lj279jmQ2zMnTQhT8C3512OKz3s+ZOaQlLbE7TuVvRDYDB3Llyy5g==}
@@ -6907,6 +7300,10 @@ packages:
       string-width: 5.1.2
     dev: true
 
+  /client-only@0.0.1:
+    resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
+    dev: false
+
   /cliui@7.0.4:
     resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
     dependencies:
@@ -7001,6 +7398,11 @@ packages:
   /commander@2.20.3:
     resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
 
+  /commander@4.1.1:
+    resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
+    engines: {node: '>= 6'}
+    dev: true
+
   /commander@6.2.1:
     resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==}
     engines: {node: '>= 6'}
@@ -7158,7 +7560,7 @@ packages:
       layout-base: 2.0.1
     dev: true
 
-  /cosmiconfig-typescript-loader@4.3.0(@types/node@18.13.0)(cosmiconfig@8.1.3)(ts-node@10.9.1)(typescript@4.9.5):
+  /cosmiconfig-typescript-loader@4.3.0(@types/node@17.0.12)(cosmiconfig@8.1.3)(ts-node@10.9.1)(typescript@4.9.5):
     resolution: {integrity: sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==}
     engines: {node: '>=12', npm: '>=6'}
     peerDependencies:
@@ -7167,9 +7569,9 @@ packages:
       ts-node: '>=10'
       typescript: '>=3'
     dependencies:
-      '@types/node': 18.13.0
+      '@types/node': 17.0.12
       cosmiconfig: 8.1.3
-      ts-node: 10.9.1(@types/node@18.13.0)(typescript@4.9.5)
+      ts-node: 10.9.1(@types/node@17.0.12)(typescript@4.9.5)
       typescript: 4.9.5
     dev: true
 
@@ -7825,6 +8227,10 @@ packages:
       - supports-color
     dev: true
 
+  /didyoumean@1.2.2:
+    resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
+    dev: true
+
   /diff@4.0.2:
     resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
     engines: {node: '>=0.3.1'}
@@ -7841,6 +8247,10 @@ packages:
     dependencies:
       path-type: 4.0.0
 
+  /dlv@1.1.3:
+    resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
+    dev: true
+
   /doctrine@2.1.0:
     resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
     engines: {node: '>=0.10.0'}
@@ -7928,7 +8338,6 @@ packages:
 
   /electron-to-chromium@1.4.330:
     resolution: {integrity: sha512-PqyefhybrVdjAJ45HaPLtuVaehiSw7C3ya0aad+rvmV53IVyXmYRk3pwIOb2TxTDTnmgQdn46NjMMaysx79/6Q==}
-    dev: true
 
   /elkjs@0.8.2:
     resolution: {integrity: sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==}
@@ -8165,7 +8574,6 @@ packages:
   /escalade@3.1.1:
     resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
     engines: {node: '>=6'}
-    dev: true
 
   /escape-html@1.0.3:
     resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
@@ -8224,6 +8632,52 @@ packages:
     dependencies:
       eslint: 7.32.0
 
+  /eslint-config-standard-jsx@11.0.0(eslint-plugin-react@7.31.8)(eslint@8.40.0):
+    resolution: {integrity: sha512-+1EV/R0JxEK1L0NGolAr8Iktm3Rgotx3BKwgaX+eAuSX8D952LULKtjgZD3F+e6SvibONnhLwoTi9DPxN5LvvQ==}
+    peerDependencies:
+      eslint: ^8.8.0
+      eslint-plugin-react: ^7.28.0
+    dependencies:
+      eslint: 8.40.0
+      eslint-plugin-react: 7.31.8(eslint@8.40.0)
+    dev: true
+
+  /eslint-config-standard-with-typescript@23.0.0(@typescript-eslint/eslint-plugin@5.52.0)(eslint-plugin-import@2.27.5)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.40.0)(typescript@4.9.5):
+    resolution: {integrity: sha512-iaaWifImn37Z1OXbNW1es7KI+S7D408F9ys0bpaQf2temeBWlvb0Nc5qHkOgYaRb5QxTZT32GGeN1gtswASOXA==}
+    peerDependencies:
+      '@typescript-eslint/eslint-plugin': ^5.0.0
+      eslint: ^8.0.1
+      eslint-plugin-import: ^2.25.2
+      eslint-plugin-n: ^15.0.0
+      eslint-plugin-promise: ^6.0.0
+      typescript: '*'
+    dependencies:
+      '@typescript-eslint/eslint-plugin': 5.52.0(@typescript-eslint/parser@5.52.0)(eslint@8.40.0)(typescript@4.9.5)
+      '@typescript-eslint/parser': 5.52.0(eslint@8.40.0)(typescript@4.9.5)
+      eslint: 8.40.0
+      eslint-config-standard: 17.0.0(eslint-plugin-import@2.27.5)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.40.0)
+      eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.52.0)(eslint-import-resolver-typescript@2.7.1)(eslint@7.32.0)
+      eslint-plugin-n: 15.7.0(eslint@8.40.0)
+      eslint-plugin-promise: 6.1.1(eslint@8.40.0)
+      typescript: 4.9.5
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /eslint-config-standard@17.0.0(eslint-plugin-import@2.27.5)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.40.0):
+    resolution: {integrity: sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==}
+    peerDependencies:
+      eslint: ^8.0.1
+      eslint-plugin-import: ^2.25.2
+      eslint-plugin-n: ^15.0.0
+      eslint-plugin-promise: ^6.0.0
+    dependencies:
+      eslint: 8.40.0
+      eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.52.0)(eslint-import-resolver-typescript@2.7.1)(eslint@7.32.0)
+      eslint-plugin-n: 15.7.0(eslint@8.40.0)
+      eslint-plugin-promise: 6.1.1(eslint@8.40.0)
+    dev: true
+
   /eslint-config-turbo@1.8.8(eslint@7.32.0):
     resolution: {integrity: sha512-+yT22sHOT5iC1sbBXfLIdXfbZuiv9bAyOXsxTxFCWelTeFFnANqmuKB3x274CFvf7WRuZ/vYP/VMjzU9xnFnxA==}
     peerDependencies:
@@ -8287,6 +8741,17 @@ packages:
     transitivePeerDependencies:
       - supports-color
 
+  /eslint-plugin-es@4.1.0(eslint@8.40.0):
+    resolution: {integrity: sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==}
+    engines: {node: '>=8.10.0'}
+    peerDependencies:
+      eslint: '>=4.19.1'
+    dependencies:
+      eslint: 8.40.0
+      eslint-utils: 2.1.0
+      regexpp: 3.2.0
+    dev: true
+
   /eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.52.0)(eslint-import-resolver-typescript@2.7.1)(eslint@7.32.0):
     resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==}
     engines: {node: '>=4'}
@@ -8343,6 +8808,32 @@ packages:
       object.fromentries: 2.0.6
       semver: 6.3.0
 
+  /eslint-plugin-n@15.7.0(eslint@8.40.0):
+    resolution: {integrity: sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==}
+    engines: {node: '>=12.22.0'}
+    peerDependencies:
+      eslint: '>=7.0.0'
+    dependencies:
+      builtins: 5.0.1
+      eslint: 8.40.0
+      eslint-plugin-es: 4.1.0(eslint@8.40.0)
+      eslint-utils: 3.0.0(eslint@8.40.0)
+      ignore: 5.2.4
+      is-core-module: 2.11.0
+      minimatch: 3.1.2
+      resolve: 1.22.1
+      semver: 7.3.8
+    dev: true
+
+  /eslint-plugin-promise@6.1.1(eslint@8.40.0):
+    resolution: {integrity: sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      eslint: ^7.0.0 || ^8.0.0
+    dependencies:
+      eslint: 8.40.0
+    dev: true
+
   /eslint-plugin-react-hooks@4.6.0(eslint@7.32.0):
     resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==}
     engines: {node: '>=10'}
@@ -8373,6 +8864,29 @@ packages:
       semver: 6.3.0
       string.prototype.matchall: 4.0.8
 
+  /eslint-plugin-react@7.31.8(eslint@8.40.0):
+    resolution: {integrity: sha512-5lBTZmgQmARLLSYiwI71tiGVTLUuqXantZM6vlSY39OaDSV0M7+32K5DnLkmFrwTe+Ksz0ffuLUC91RUviVZfw==}
+    engines: {node: '>=4'}
+    peerDependencies:
+      eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8
+    dependencies:
+      array-includes: 3.1.6
+      array.prototype.flatmap: 1.3.1
+      doctrine: 2.1.0
+      eslint: 8.40.0
+      estraverse: 5.3.0
+      jsx-ast-utils: 3.3.3
+      minimatch: 3.1.2
+      object.entries: 1.1.6
+      object.fromentries: 2.0.6
+      object.hasown: 1.1.2
+      object.values: 1.1.6
+      prop-types: 15.8.1
+      resolve: 2.0.0-next.4
+      semver: 6.3.0
+      string.prototype.matchall: 4.0.8
+    dev: true
+
   /eslint-plugin-turbo@1.8.8(eslint@7.32.0):
     resolution: {integrity: sha512-zqyTIvveOY4YU5jviDWw9GXHd4RiKmfEgwsjBrV/a965w0PpDwJgEUoSMB/C/dU310Sv9mF3DSdEjxjJLaw6rA==}
     peerDependencies:
@@ -8387,6 +8901,14 @@ packages:
       esrecurse: 4.3.0
       estraverse: 4.3.0
 
+  /eslint-scope@7.2.0:
+    resolution: {integrity: sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    dependencies:
+      esrecurse: 4.3.0
+      estraverse: 5.3.0
+    dev: true
+
   /eslint-utils@2.1.0:
     resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==}
     engines: {node: '>=6'}
@@ -8403,6 +8925,16 @@ packages:
       eslint-visitor-keys: 2.1.0
     dev: true
 
+  /eslint-utils@3.0.0(eslint@8.40.0):
+    resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==}
+    engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0}
+    peerDependencies:
+      eslint: '>=5'
+    dependencies:
+      eslint: 8.40.0
+      eslint-visitor-keys: 2.1.0
+    dev: true
+
   /eslint-visitor-keys@1.3.0:
     resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==}
     engines: {node: '>=4'}
@@ -8411,8 +8943,8 @@ packages:
     resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==}
     engines: {node: '>=10'}
 
-  /eslint-visitor-keys@3.3.0:
-    resolution: {integrity: sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==}
+  /eslint-visitor-keys@3.4.1:
+    resolution: {integrity: sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
 
   /eslint@7.32.0:
@@ -8463,6 +8995,55 @@ packages:
     transitivePeerDependencies:
       - supports-color
 
+  /eslint@8.40.0:
+    resolution: {integrity: sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    hasBin: true
+    dependencies:
+      '@eslint-community/eslint-utils': 4.4.0(eslint@8.40.0)
+      '@eslint-community/regexpp': 4.5.1
+      '@eslint/eslintrc': 2.0.3
+      '@eslint/js': 8.40.0
+      '@humanwhocodes/config-array': 0.11.8
+      '@humanwhocodes/module-importer': 1.0.1
+      '@nodelib/fs.walk': 1.2.8
+      ajv: 6.12.6
+      chalk: 4.1.2
+      cross-spawn: 7.0.3
+      debug: 4.3.4(supports-color@5.5.0)
+      doctrine: 3.0.0
+      escape-string-regexp: 4.0.0
+      eslint-scope: 7.2.0
+      eslint-visitor-keys: 3.4.1
+      espree: 9.5.2
+      esquery: 1.5.0
+      esutils: 2.0.3
+      fast-deep-equal: 3.1.3
+      file-entry-cache: 6.0.1
+      find-up: 5.0.0
+      glob-parent: 6.0.2
+      globals: 13.20.0
+      grapheme-splitter: 1.0.4
+      ignore: 5.2.4
+      import-fresh: 3.3.0
+      imurmurhash: 0.1.4
+      is-glob: 4.0.3
+      is-path-inside: 3.0.3
+      js-sdsl: 4.4.0
+      js-yaml: 4.1.0
+      json-stable-stringify-without-jsonify: 1.0.1
+      levn: 0.4.1
+      lodash.merge: 4.6.2
+      minimatch: 3.1.2
+      natural-compare: 1.4.0
+      optionator: 0.9.1
+      strip-ansi: 6.0.1
+      strip-json-comments: 3.1.1
+      text-table: 0.2.0
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /espree@7.3.1:
     resolution: {integrity: sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==}
     engines: {node: ^10.12.0 || >=12.0.0}
@@ -8471,6 +9052,15 @@ packages:
       acorn-jsx: 5.3.2(acorn@7.4.1)
       eslint-visitor-keys: 1.3.0
 
+  /espree@9.5.2:
+    resolution: {integrity: sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    dependencies:
+      acorn: 8.8.2
+      acorn-jsx: 5.3.2(acorn@8.8.2)
+      eslint-visitor-keys: 3.4.1
+    dev: true
+
   /esprima@4.0.1:
     resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
     engines: {node: '>=4'}
@@ -8752,6 +9342,14 @@ packages:
       path-exists: 4.0.0
     dev: true
 
+  /find-up@6.3.0:
+    resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dependencies:
+      locate-path: 7.2.0
+      path-exists: 5.0.0
+    dev: true
+
   /flat-cache@3.0.4:
     resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==}
     engines: {node: ^10.12.0 || >=12.0.0}
@@ -8815,6 +9413,10 @@ packages:
     engines: {node: '>= 0.6'}
     dev: true
 
+  /fraction.js@4.2.0:
+    resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==}
+    dev: true
+
   /fresh@0.5.2:
     resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
     engines: {node: '>= 0.6'}
@@ -8904,7 +9506,6 @@ packages:
   /gensync@1.0.0-beta.2:
     resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
     engines: {node: '>=6.9.0'}
-    dev: true
 
   /get-caller-file@2.0.5:
     resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
@@ -8937,6 +9538,11 @@ packages:
     engines: {node: '>=8'}
     dev: true
 
+  /get-stdin@8.0.0:
+    resolution: {integrity: sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==}
+    engines: {node: '>=10'}
+    dev: true
+
   /get-stream@6.0.1:
     resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
     engines: {node: '>=10'}
@@ -8986,6 +9592,13 @@ packages:
     dependencies:
       is-glob: 4.0.3
 
+  /glob-parent@6.0.2:
+    resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+    engines: {node: '>=10.13.0'}
+    dependencies:
+      is-glob: 4.0.3
+    dev: true
+
   /glob-promise@4.2.2(glob@7.2.3):
     resolution: {integrity: sha512-xcUzJ8NWN5bktoTIX7eOclO1Npxd/dyVqUJxlLIDasT4C7KZyqlPIwkdJ0Ypiy3p2ZKahTjK4M9uC3sNSfNMzw==}
     engines: {node: '>=12'}
@@ -9010,6 +9623,17 @@ packages:
     resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
     dev: true
 
+  /glob@7.1.6:
+    resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==}
+    dependencies:
+      fs.realpath: 1.0.0
+      inflight: 1.0.6
+      inherits: 2.0.4
+      minimatch: 3.1.2
+      once: 1.4.0
+      path-is-absolute: 1.0.1
+    dev: true
+
   /glob@7.1.7:
     resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==}
     dependencies:
@@ -9815,7 +10439,7 @@ packages:
     dependencies:
       '@jest/types': 29.5.0
       '@types/graceful-fs': 4.1.6
-      '@types/node': 18.13.0
+      '@types/node': 17.0.12
       anymatch: 3.1.3
       fb-watchman: 2.0.2
       graceful-fs: 4.2.11
@@ -9833,7 +10457,7 @@ packages:
     engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
     dependencies:
       '@jest/types': 27.5.1
-      '@types/node': 18.13.0
+      '@types/node': 17.0.12
     dev: true
 
   /jest-regex-util@29.4.3:
@@ -9846,7 +10470,7 @@ packages:
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
       '@jest/types': 29.5.0
-      '@types/node': 18.13.0
+      '@types/node': 17.0.12
       chalk: 4.1.2
       ci-info: 3.8.0
       graceful-fs: 4.2.11
@@ -9857,7 +10481,7 @@ packages:
     resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
     engines: {node: '>= 10.13.0'}
     dependencies:
-      '@types/node': 18.13.0
+      '@types/node': 17.0.12
       merge-stream: 2.0.0
       supports-color: 8.1.1
     dev: true
@@ -9866,7 +10490,7 @@ packages:
     resolution: {integrity: sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
-      '@types/node': 18.13.0
+      '@types/node': 17.0.12
       jest-util: 29.5.0
       merge-stream: 2.0.0
       supports-color: 8.1.1
@@ -9881,6 +10505,10 @@ packages:
     resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==}
     dev: true
 
+  /js-sdsl@4.4.0:
+    resolution: {integrity: sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==}
+    dev: true
+
   /js-tokens@4.0.0:
     resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
 
@@ -9980,6 +10608,10 @@ packages:
     engines: {node: '>=4'}
     hasBin: true
 
+  /json-parse-better-errors@1.0.2:
+    resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==}
+    dev: true
+
   /json-parse-even-better-errors@2.3.1:
     resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
 
@@ -10002,7 +10634,6 @@ packages:
     resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
     engines: {node: '>=6'}
     hasBin: true
-    dev: true
 
   /jsonc-parser@3.2.0:
     resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==}
@@ -10156,6 +10787,22 @@ packages:
   /lines-and-columns@1.2.4:
     resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
 
+  /load-json-file@5.3.0:
+    resolution: {integrity: sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==}
+    engines: {node: '>=6'}
+    dependencies:
+      graceful-fs: 4.2.11
+      parse-json: 4.0.0
+      pify: 4.0.1
+      strip-bom: 3.0.0
+      type-fest: 0.3.1
+    dev: true
+
+  /load-json-file@7.0.1:
+    resolution: {integrity: sha512-Gnxj3ev3mB5TkVBGad0JM6dmLiQL+o0t23JPBZ9sd+yvSLk05mFoqKBw5N8gbbkU4TNXyqCgIrl/VM17OgUIgQ==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dev: true
+
   /loader-runner@4.3.0:
     resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==}
     engines: {node: '>=6.11.5'}
@@ -10197,6 +10844,13 @@ packages:
       p-locate: 5.0.0
     dev: true
 
+  /locate-path@7.2.0:
+    resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dependencies:
+      p-locate: 6.0.0
+    dev: true
+
   /lodash.camelcase@4.3.0:
     resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
     dev: true
@@ -10277,7 +10931,6 @@ packages:
     resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
     dependencies:
       yallist: 3.1.1
-    dev: true
 
   /lru-cache@6.0.0:
     resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
@@ -10553,6 +11206,14 @@ packages:
   /ms@2.1.3:
     resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
 
+  /mz@2.7.0:
+    resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
+    dependencies:
+      any-promise: 1.3.0
+      object-assign: 4.1.1
+      thenify-all: 1.6.0
+    dev: true
+
   /nan@2.17.0:
     resolution: {integrity: sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==}
     requiresBuild: true
@@ -10593,6 +11254,50 @@ packages:
     resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
     dev: true
 
+  /next@13.1.1(@babel/core@7.21.3)(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-R5eBAaIa3X7LJeYvv1bMdGnAVF4fVToEjim7MkflceFPuANY3YyvFxXee/A+acrSYwYPvOvf7f6v/BM/48ea5w==}
+    engines: {node: '>=14.6.0'}
+    hasBin: true
+    peerDependencies:
+      fibers: '>= 3.1.0'
+      node-sass: ^6.0.0 || ^7.0.0
+      react: ^18.2.0
+      react-dom: ^18.2.0
+      sass: ^1.3.0
+    peerDependenciesMeta:
+      fibers:
+        optional: true
+      node-sass:
+        optional: true
+      sass:
+        optional: true
+    dependencies:
+      '@next/env': 13.1.1
+      '@swc/helpers': 0.4.14
+      caniuse-lite: 1.0.30001466
+      postcss: 8.4.14
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
+      styled-jsx: 5.1.1(@babel/core@7.21.3)(react@18.2.0)
+    optionalDependencies:
+      '@next/swc-android-arm-eabi': 13.1.1
+      '@next/swc-android-arm64': 13.1.1
+      '@next/swc-darwin-arm64': 13.1.1
+      '@next/swc-darwin-x64': 13.1.1
+      '@next/swc-freebsd-x64': 13.1.1
+      '@next/swc-linux-arm-gnueabihf': 13.1.1
+      '@next/swc-linux-arm64-gnu': 13.1.1
+      '@next/swc-linux-arm64-musl': 13.1.1
+      '@next/swc-linux-x64-gnu': 13.1.1
+      '@next/swc-linux-x64-musl': 13.1.1
+      '@next/swc-win32-arm64-msvc': 13.1.1
+      '@next/swc-win32-ia32-msvc': 13.1.1
+      '@next/swc-win32-x64-msvc': 13.1.1
+    transitivePeerDependencies:
+      - '@babel/core'
+      - babel-plugin-macros
+    dev: false
+
   /node-dir@0.1.17:
     resolution: {integrity: sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==}
     engines: {node: '>= 0.10.5'}
@@ -10634,7 +11339,6 @@ packages:
 
   /node-releases@2.0.10:
     resolution: {integrity: sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==}
-    dev: true
 
   /nopt@5.0.0:
     resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}
@@ -10667,6 +11371,11 @@ packages:
     resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
     engines: {node: '>=0.10.0'}
 
+  /normalize-range@0.1.2:
+    resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
   /npm-run-path@4.0.1:
     resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
     engines: {node: '>=8'}
@@ -10691,6 +11400,11 @@ packages:
     resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
     engines: {node: '>=0.10.0'}
 
+  /object-hash@3.0.0:
+    resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
+    engines: {node: '>= 6'}
+    dev: true
+
   /object-inspect@1.12.3:
     resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==}
 
@@ -10858,6 +11572,13 @@ packages:
       p-limit: 3.1.0
     dev: true
 
+  /p-locate@6.0.0:
+    resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dependencies:
+      p-limit: 4.0.0
+    dev: true
+
   /p-map@4.0.0:
     resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==}
     engines: {node: '>=10'}
@@ -10886,6 +11607,14 @@ packages:
     dependencies:
       callsites: 3.1.0
 
+  /parse-json@4.0.0:
+    resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==}
+    engines: {node: '>=4'}
+    dependencies:
+      error-ex: 1.3.2
+      json-parse-better-errors: 1.0.2
+    dev: true
+
   /parse-json@5.2.0:
     resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
     engines: {node: '>=8'}
@@ -10925,6 +11654,11 @@ packages:
     engines: {node: '>=8'}
     dev: true
 
+  /path-exists@5.0.0:
+    resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dev: true
+
   /path-is-absolute@1.0.1:
     resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
     engines: {node: '>=0.10.0'}
@@ -10976,6 +11710,11 @@ packages:
     resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
     engines: {node: '>=8.6'}
 
+  /pify@2.3.0:
+    resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
   /pify@4.0.1:
     resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
     engines: {node: '>=6'}
@@ -11022,6 +11761,22 @@ packages:
     transitivePeerDependencies:
       - '@pixi/utils'
 
+  /pkg-conf@3.1.0:
+    resolution: {integrity: sha512-m0OTbR/5VPNPqO1ph6Fqbj7Hv6QU7gR/tQW40ZqrL1rjgCU85W6C1bJn0BItuJqnR98PWzw7Z8hHeChD1WrgdQ==}
+    engines: {node: '>=6'}
+    dependencies:
+      find-up: 3.0.0
+      load-json-file: 5.3.0
+    dev: true
+
+  /pkg-conf@4.0.0:
+    resolution: {integrity: sha512-7dmgi4UY4qk+4mj5Cd8v/GExPo0K+SlY+hulOSdfZ/T6jVH6//y7NtzZo5WrfhDBxuQ0jCa7fLZmNaNh7EWL/w==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dependencies:
+      find-up: 6.3.0
+      load-json-file: 7.0.1
+    dev: true
+
   /pkg-dir@3.0.0:
     resolution: {integrity: sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==}
     engines: {node: '>=6'}
@@ -11058,6 +11813,18 @@ packages:
       '@babel/runtime': 7.21.0
     dev: true
 
+  /postcss-import@14.1.0(postcss@8.4.21):
+    resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==}
+    engines: {node: '>=10.0.0'}
+    peerDependencies:
+      postcss: ^8.0.0
+    dependencies:
+      postcss: 8.4.21
+      postcss-value-parser: 4.2.0
+      read-cache: 1.0.0
+      resolve: 1.22.1
+    dev: true
+
   /postcss-js@4.0.1(postcss@8.4.21):
     resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==}
     engines: {node: ^12 || ^14 || >= 16}
@@ -11068,6 +11835,24 @@ packages:
       postcss: 8.4.21
     dev: true
 
+  /postcss-load-config@3.1.4(postcss@8.4.21)(ts-node@10.9.1):
+    resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
+    engines: {node: '>= 10'}
+    peerDependencies:
+      postcss: '>=8.0.9'
+      ts-node: '>=9.0.0'
+    peerDependenciesMeta:
+      postcss:
+        optional: true
+      ts-node:
+        optional: true
+    dependencies:
+      lilconfig: 2.1.0
+      postcss: 8.4.21
+      ts-node: 10.9.1(@types/node@17.0.12)(typescript@4.9.5)
+      yaml: 1.10.2
+    dev: true
+
   /postcss-load-config@4.0.1(postcss@8.4.21)(ts-node@10.9.1):
     resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==}
     engines: {node: '>= 14'}
@@ -11142,6 +11927,16 @@ packages:
       postcss: 8.4.21
     dev: true
 
+  /postcss-nested@6.0.0(postcss@8.4.21):
+    resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==}
+    engines: {node: '>=12.0'}
+    peerDependencies:
+      postcss: ^8.2.14
+    dependencies:
+      postcss: 8.4.21
+      postcss-selector-parser: 6.0.11
+    dev: true
+
   /postcss-nesting@11.2.2(postcss@8.4.21):
     resolution: {integrity: sha512-aOTiUniAB1bcPE6GGiynWRa6PZFPhOTAm5q3q5cem6QeSijIHHkWr6gs65ukCZMXeak8yXeZVbBJET3VM+HlhA==}
     engines: {node: ^14 || ^16 || >=18}
@@ -11187,6 +11982,15 @@ packages:
       supports-color: 5.5.0
     dev: true
 
+  /postcss@8.4.14:
+    resolution: {integrity: sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==}
+    engines: {node: ^10 || ^12 || >=14}
+    dependencies:
+      nanoid: 3.3.4
+      picocolors: 1.0.0
+      source-map-js: 1.0.2
+    dev: false
+
   /postcss@8.4.21:
     resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==}
     engines: {node: ^10 || ^12 || >=14}
@@ -11374,6 +12178,11 @@ packages:
     engines: {node: '>=8'}
     dev: true
 
+  /quick-lru@5.1.1:
+    resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
+    engines: {node: '>=10'}
+    dev: true
+
   /raf@3.4.1:
     resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==}
     dependencies:
@@ -11728,6 +12537,12 @@ packages:
       - immer
     dev: false
 
+  /read-cache@1.0.0:
+    resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
+    dependencies:
+      pify: 2.3.0
+    dev: true
+
   /read-pkg-up@7.0.1:
     resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==}
     engines: {node: '>=8'}
@@ -12347,6 +13162,16 @@ packages:
     dev: false
     optional: true
 
+  /standard-engine@15.0.0:
+    resolution: {integrity: sha512-4xwUhJNo1g/L2cleysUqUv7/btn7GEbYJvmgKrQ2vd/8pkTmN8cpqAZg+BT8Z1hNeEH787iWUdOpL8fmApLtxA==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    dependencies:
+      get-stdin: 8.0.0
+      minimist: 1.2.8
+      pkg-conf: 3.1.0
+      xdg-basedir: 4.0.0
+    dev: true
+
   /statuses@2.0.1:
     resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
     engines: {node: '>= 0.8'}
@@ -12521,10 +13346,42 @@ packages:
       supports-color: 5.5.0
     dev: false
 
+  /styled-jsx@5.1.1(@babel/core@7.21.3)(react@18.2.0):
+    resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==}
+    engines: {node: '>= 12.0.0'}
+    peerDependencies:
+      '@babel/core': '*'
+      babel-plugin-macros: '*'
+      react: '>= 16.8.0 || 17.x.x || ^18.0.0-0'
+    peerDependenciesMeta:
+      '@babel/core':
+        optional: true
+      babel-plugin-macros:
+        optional: true
+    dependencies:
+      '@babel/core': 7.21.3
+      client-only: 0.0.1
+      react: 18.2.0
+    dev: false
+
   /stylis@4.1.3:
     resolution: {integrity: sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==}
     dev: false
 
+  /sucrase@3.32.0:
+    resolution: {integrity: sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==}
+    engines: {node: '>=8'}
+    hasBin: true
+    dependencies:
+      '@jridgewell/gen-mapping': 0.3.2
+      commander: 4.1.1
+      glob: 7.1.6
+      lines-and-columns: 1.2.4
+      mz: 2.7.0
+      pirates: 4.0.5
+      ts-interface-checker: 0.1.13
+    dev: true
+
   /supports-color@5.5.0:
     resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
     engines: {node: '>=4'}
@@ -12572,6 +13429,41 @@ packages:
       string-width: 4.2.3
       strip-ansi: 6.0.1
 
+  /tailwindcss@3.3.1(postcss@8.4.21)(ts-node@10.9.1):
+    resolution: {integrity: sha512-Vkiouc41d4CEq0ujXl6oiGFQ7bA3WEhUZdTgXAhtKxSy49OmKs8rEfQmupsfF0IGW8fv2iQkp1EVUuapCFrZ9g==}
+    engines: {node: '>=12.13.0'}
+    hasBin: true
+    peerDependencies:
+      postcss: ^8.0.9
+    dependencies:
+      arg: 5.0.2
+      chokidar: 3.5.3
+      color-name: 1.1.4
+      didyoumean: 1.2.2
+      dlv: 1.1.3
+      fast-glob: 3.2.12
+      glob-parent: 6.0.2
+      is-glob: 4.0.3
+      jiti: 1.18.2
+      lilconfig: 2.1.0
+      micromatch: 4.0.5
+      normalize-path: 3.0.0
+      object-hash: 3.0.0
+      picocolors: 1.0.0
+      postcss: 8.4.21
+      postcss-import: 14.1.0(postcss@8.4.21)
+      postcss-js: 4.0.1(postcss@8.4.21)
+      postcss-load-config: 3.1.4(postcss@8.4.21)(ts-node@10.9.1)
+      postcss-nested: 6.0.0(postcss@8.4.21)
+      postcss-selector-parser: 6.0.11
+      postcss-value-parser: 4.2.0
+      quick-lru: 5.1.1
+      resolve: 1.22.1
+      sucrase: 3.32.0
+    transitivePeerDependencies:
+      - ts-node
+    dev: true
+
   /tapable@2.2.1:
     resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
     engines: {node: '>=6'}
@@ -12736,6 +13628,19 @@ packages:
   /text-table@0.2.0:
     resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
 
+  /thenify-all@1.6.0:
+    resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
+    engines: {node: '>=0.8'}
+    dependencies:
+      thenify: 3.3.1
+    dev: true
+
+  /thenify@3.3.1:
+    resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
+    dependencies:
+      any-promise: 1.3.0
+    dev: true
+
   /through2@2.0.5:
     resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==}
     dependencies:
@@ -12816,6 +13721,10 @@ packages:
     engines: {node: '>=6.10'}
     dev: true
 
+  /ts-interface-checker@0.1.13:
+    resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
+    dev: true
+
   /ts-morph@17.0.1:
     resolution: {integrity: sha512-10PkHyXmrtsTvZSL+cqtJLTgFXkU43Gd0JCc0Rw6GchWbqKe0Rwgt1v3ouobTZwQzF1mGhDeAlWYBMGRV7y+3g==}
     dependencies:
@@ -12823,6 +13732,37 @@ packages:
       code-block-writer: 11.0.3
     dev: true
 
+  /ts-node@10.9.1(@types/node@17.0.12)(typescript@4.9.5):
+    resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
+    hasBin: true
+    peerDependencies:
+      '@swc/core': '>=1.2.50'
+      '@swc/wasm': '>=1.2.50'
+      '@types/node': '*'
+      typescript: '>=2.7'
+    peerDependenciesMeta:
+      '@swc/core':
+        optional: true
+      '@swc/wasm':
+        optional: true
+    dependencies:
+      '@cspotcode/source-map-support': 0.8.1
+      '@tsconfig/node10': 1.0.9
+      '@tsconfig/node12': 1.0.11
+      '@tsconfig/node14': 1.0.3
+      '@tsconfig/node16': 1.0.3
+      '@types/node': 17.0.12
+      acorn: 8.8.2
+      acorn-walk: 8.2.0
+      arg: 4.1.3
+      create-require: 1.1.1
+      diff: 4.0.2
+      make-error: 1.3.6
+      typescript: 4.9.5
+      v8-compile-cache-lib: 3.0.1
+      yn: 3.1.1
+    dev: true
+
   /ts-node@10.9.1(@types/node@18.13.0)(typescript@4.9.5):
     resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
     hasBin: true
@@ -12854,6 +13794,32 @@ packages:
       yn: 3.1.1
     dev: true
 
+  /ts-standard@12.0.2(typescript@4.9.5):
+    resolution: {integrity: sha512-XX2wrB9fKKTfBj4yD3ABm9iShzZcS2iWcPK8XzlBvuL20+wMiLgiz/k5tXgZwTaYq5wRhbks1Y9PelhujF/9ag==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    hasBin: true
+    peerDependencies:
+      typescript: '*'
+    dependencies:
+      '@typescript-eslint/eslint-plugin': 5.52.0(@typescript-eslint/parser@5.52.0)(eslint@8.40.0)(typescript@4.9.5)
+      '@typescript-eslint/parser': 5.52.0(eslint@8.40.0)(typescript@4.9.5)
+      eslint: 8.40.0
+      eslint-config-standard-jsx: 11.0.0(eslint-plugin-react@7.31.8)(eslint@8.40.0)
+      eslint-config-standard-with-typescript: 23.0.0(@typescript-eslint/eslint-plugin@5.52.0)(eslint-plugin-import@2.27.5)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.40.0)(typescript@4.9.5)
+      eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.52.0)(eslint-import-resolver-typescript@2.7.1)(eslint@7.32.0)
+      eslint-plugin-n: 15.7.0(eslint@8.40.0)
+      eslint-plugin-promise: 6.1.1(eslint@8.40.0)
+      eslint-plugin-react: 7.31.8(eslint@8.40.0)
+      minimist: 1.2.8
+      pkg-conf: 4.0.0
+      standard-engine: 15.0.0
+      typescript: 4.9.5
+    transitivePeerDependencies:
+      - eslint-import-resolver-typescript
+      - eslint-import-resolver-webpack
+      - supports-color
+    dev: true
+
   /tsconfck@2.1.0(typescript@4.9.5):
     resolution: {integrity: sha512-lztI9ohwclQHISVWrM/hlcgsRpphsii94DV9AQtAw2XJSVNiv+3ppdEsrL5J+xc5oTeHXe1qDqlOAGw8VSa9+Q==}
     engines: {node: ^14.13.1 || ^16 || >=18}
@@ -12983,6 +13949,11 @@ packages:
     resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
     engines: {node: '>=10'}
 
+  /type-fest@0.3.1:
+    resolution: {integrity: sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==}
+    engines: {node: '>=6'}
+    dev: true
+
   /type-fest@0.6.0:
     resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==}
     engines: {node: '>=8'}
@@ -13155,7 +14126,6 @@ packages:
       browserslist: 4.21.5
       escalade: 3.1.1
       picocolors: 1.0.0
-    dev: true
 
   /uri-js@4.4.1:
     resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
@@ -13307,7 +14277,7 @@ packages:
     engines: {node: '>= 0.8'}
     dev: true
 
-  /vite-node@0.29.4(@types/node@18.13.0)(sass@1.59.3):
+  /vite-node@0.29.4(@types/node@17.0.12)(sass@1.59.3):
     resolution: {integrity: sha512-sPhnCzGm3rCI1BMgOUHiGJN4MObLUOzdCjrNU5A2miNTat/7k96hmvVLxKXPLb0wjX160oG1ZhLVYBzF80UJlQ==}
     engines: {node: '>=v14.16.0'}
     hasBin: true
@@ -13328,6 +14298,29 @@ packages:
       - terser
     dev: true
 
+  /vite-plugin-dts@2.1.0(@types/node@17.0.12)(vite@4.2.1):
+    resolution: {integrity: sha512-Vw0FdCuM3VLR4hTFHh0yMEzfwI7NyFvPIMFwvE+Q0t4qtoHIfYOP/JXs7nTnHuQk87FSjlhGeIJ1fLBcktgPgA==}
+    engines: {node: ^14.18.0 || >=16.0.0}
+    peerDependencies:
+      vite: '>=2.9.0'
+    dependencies:
+      '@babel/parser': 7.21.3
+      '@microsoft/api-extractor': 7.34.4(@types/node@17.0.12)
+      '@rollup/pluginutils': 5.0.2
+      '@rushstack/node-core-library': 3.55.2(@types/node@17.0.12)
+      debug: 4.3.4(supports-color@5.5.0)
+      fast-glob: 3.2.12
+      fs-extra: 10.1.0
+      kolorist: 1.7.0
+      magic-string: 0.29.0
+      ts-morph: 17.0.1
+      vite: 4.2.1(@types/node@18.13.0)(less@4.1.3)(sass@1.59.3)
+    transitivePeerDependencies:
+      - '@types/node'
+      - rollup
+      - supports-color
+    dev: true
+
   /vite-plugin-dts@2.1.0(@types/node@18.13.0)(vite@4.2.1):
     resolution: {integrity: sha512-Vw0FdCuM3VLR4hTFHh0yMEzfwI7NyFvPIMFwvE+Q0t4qtoHIfYOP/JXs7nTnHuQk87FSjlhGeIJ1fLBcktgPgA==}
     engines: {node: ^14.18.0 || >=16.0.0}
@@ -13466,7 +14459,7 @@ packages:
     dependencies:
       '@types/chai': 4.3.4
       '@types/chai-subset': 1.3.3
-      '@types/node': 18.13.0
+      '@types/node': 17.0.12
       '@vitest/expect': 0.29.4
       '@vitest/runner': 0.29.4
       '@vitest/spy': 0.29.4
@@ -13488,7 +14481,7 @@ packages:
       tinypool: 0.4.0
       tinyspy: 1.1.1
       vite: 4.2.1(@types/node@18.13.0)(less@4.1.3)(sass@1.59.3)
-      vite-node: 0.29.4(@types/node@18.13.0)(sass@1.59.3)
+      vite-node: 0.29.4(@types/node@17.0.12)(sass@1.59.3)
       why-is-node-running: 2.2.2
     transitivePeerDependencies:
       - less
@@ -13767,6 +14760,11 @@ packages:
         optional: true
     dev: true
 
+  /xdg-basedir@4.0.0:
+    resolution: {integrity: sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==}
+    engines: {node: '>=8'}
+    dev: true
+
   /xml-name-validator@4.0.0:
     resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
     engines: {node: '>=12'}
@@ -13788,7 +14786,6 @@ packages:
 
   /yallist@3.1.1:
     resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
-    dev: true
 
   /yallist@4.0.0:
     resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
@@ -13796,7 +14793,6 @@ packages:
   /yaml@1.10.2:
     resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
     engines: {node: '>= 6'}
-    dev: false
 
   /yaml@2.2.1:
     resolution: {integrity: sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==}
-- 
GitLab