diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 36e1e34e21c4674efff2897edef252a49f90f3c1..83b89b18cb31b1c87548dd99ef87d6468fbdd9c8 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -6,16 +6,55 @@ variables:
 cache:
   paths:
     - .cache/pip
-    - venv/
+    - venv/ # makes the virtual env available for jobs
 
-before_script:
-  - python -V
+stages:
+  - venv update # Keep the cached virtual env up to date with dependencies
+  - tests and linting # Run test/linting for frontend and backend
 
-test:
+update:
+  before_script:
+    - python -V
+  image: python:3.9-buster
+  stage: venv update
   script:
     - make install
+
+backend lint:
+  before_script:
+    - python -V
+  image: python:3.9-buster
+  stage: tests and linting
+  script:
     - make lint
+
+backend test:
+  before_script:
+    - python -V
+  image: python:3.9-buster
+  stage: tests and linting
+  script:
     - make test
   artifacts:
     reports:
       cobertura: coverage.xml
+
+frontend test:
+  image: node:14-alpine
+  stage: tests and linting
+  script:
+    - cd frontend
+    - npm install -g npm@latest
+    - npm ci
+    - npm run coverage:ci
+  coverage:
+    '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
+
+frontend lint:
+  image: node:14-alpine
+  stage: tests and linting
+  script:
+    - cd frontend
+    - npm install -g npm@latest
+    - npm ci
+    - npm run lint
diff --git a/frontend/.env b/frontend/.env
index 60a2d2a264d628fd76716f27d74f22778ae43b99..bd56a6e4c209ad07a85513349a27e2f5161ef0f4 100644
--- a/frontend/.env
+++ b/frontend/.env
@@ -1,2 +1,11 @@
 REACT_APP_VERSION=$npm_package_version
 REACT_APP_NAME=$npm_package_name
+
+REACT_APP_SUPPORT_MAIL=test@example.org
+REACT_APP_INST=uio
+REACT_APP_SUPPORT_URL=https://example.org
+
+REACT_APP_RESPONSIBLE_ORGANIZATION=N/A
+REACT_APP_RESPONSIBLE_ORGANIZATION_LINK=https://example.org
+
+REACT_APP_THEME=default
diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json
new file mode 100644
index 0000000000000000000000000000000000000000..ef74cbdbf973cfbfe0a0c596ae4e97961d923f76
--- /dev/null
+++ b/frontend/.eslintrc.json
@@ -0,0 +1,27 @@
+{
+  "env": {
+    "browser": true,
+    "es2021": true,
+    "node": true
+  },
+  "extends": [
+    "plugin:react/recommended",
+    "airbnb",
+    "airbnb-typescript",
+    "prettier"
+  ],
+  "parser": "@typescript-eslint/parser",
+  "parserOptions": {
+    "project": "./tsconfig.json",
+    "ecmaFeatures": {
+      "jsx": true
+    },
+    "ecmaVersion": 12,
+    "sourceType": "module"
+  },
+  "plugins": ["react", "@typescript-eslint"],
+  "rules": {
+    "semi": "off",
+    "@typescript-eslint/semi": ["error", "never"]
+  }
+}
diff --git a/frontend/.gitignore b/frontend/.gitignore
index 4d29575de80483b005c29bfcac5061cd2f45313e..54ce1aaf18b4e6696a0f88d0980589f740901a31 100644
--- a/frontend/.gitignore
+++ b/frontend/.gitignore
@@ -11,6 +11,9 @@
 # production
 /build
 
+# frontend configuration
+/public/env.js
+
 # misc
 .DS_Store
 .env.local
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 9b9b5bbf781ad15a7df48a4e2a1c2d3a3467e857..164bd6ca3ec90bd4ef618b306dc6aac2a40ecb27 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -23,6 +23,7 @@
         "react-datepicker": "^4.2.1",
         "react-dom": "^17.0.2",
         "react-helmet": "^6.1.0",
+        "react-hook-form": "^7.15.3",
         "react-i18next": "^11.11.4",
         "react-router-dom": "^5.3.0",
         "react-scripts": "4.0.3",
@@ -33,7 +34,18 @@
       "devDependencies": {
         "@types/react-datepicker": "^4.1.7",
         "@types/react-helmet": "^6.1.2",
-        "@types/react-router-dom": "^5.1.8"
+        "@types/react-router-dom": "^5.1.8",
+        "@typescript-eslint/eslint-plugin": "^4.31.1",
+        "@typescript-eslint/parser": "^4.31.1",
+        "eslint": "^7.32.0",
+        "eslint-config-airbnb": "^18.2.1",
+        "eslint-config-airbnb-typescript": "^14.0.0",
+        "eslint-config-prettier": "^8.3.0",
+        "eslint-plugin-import": "^2.24.2",
+        "eslint-plugin-jsx-a11y": "^6.4.1",
+        "eslint-plugin-react": "^7.25.1",
+        "eslint-plugin-react-hooks": "^4.2.0",
+        "jest-junit": "^12.2.0"
       }
     },
     "node_modules/@babel/code-frame": {
@@ -3657,12 +3669,12 @@
       "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw=="
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "4.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.31.0.tgz",
-      "integrity": "sha512-iPKZTZNavAlOhfF4gymiSuUkgLne/nh5Oz2/mdiUmuZVD42m9PapnCnzjxuDsnpnbH3wT5s2D8bw6S39TC6GNw==",
+      "version": "4.31.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.31.1.tgz",
+      "integrity": "sha512-UDqhWmd5i0TvPLmbK5xY3UZB0zEGseF+DHPghZ37Sb83Qd3p8ujhvAtkU4OF46Ka5Pm5kWvFIx0cCTBFKo0alA==",
       "dependencies": {
-        "@typescript-eslint/experimental-utils": "4.31.0",
-        "@typescript-eslint/scope-manager": "4.31.0",
+        "@typescript-eslint/experimental-utils": "4.31.1",
+        "@typescript-eslint/scope-manager": "4.31.1",
         "debug": "^4.3.1",
         "functional-red-black-tree": "^1.0.1",
         "regexpp": "^3.1.0",
@@ -3701,14 +3713,14 @@
       }
     },
     "node_modules/@typescript-eslint/experimental-utils": {
-      "version": "4.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.31.0.tgz",
-      "integrity": "sha512-Hld+EQiKLMppgKKkdUsLeVIeEOrwKc2G983NmznY/r5/ZtZCDvIOXnXtwqJIgYz/ymsy7n7RGvMyrzf1WaSQrw==",
+      "version": "4.31.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.31.1.tgz",
+      "integrity": "sha512-NtoPsqmcSsWty0mcL5nTZXMf7Ei0Xr2MT8jWjXMVgRK0/1qeQ2jZzLFUh4QtyJ4+/lPUyMw5cSfeeME+Zrtp9Q==",
       "dependencies": {
         "@types/json-schema": "^7.0.7",
-        "@typescript-eslint/scope-manager": "4.31.0",
-        "@typescript-eslint/types": "4.31.0",
-        "@typescript-eslint/typescript-estree": "4.31.0",
+        "@typescript-eslint/scope-manager": "4.31.1",
+        "@typescript-eslint/types": "4.31.1",
+        "@typescript-eslint/typescript-estree": "4.31.1",
         "eslint-scope": "^5.1.1",
         "eslint-utils": "^3.0.0"
       },
@@ -3724,13 +3736,13 @@
       }
     },
     "node_modules/@typescript-eslint/parser": {
-      "version": "4.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.31.0.tgz",
-      "integrity": "sha512-oWbzvPh5amMuTmKaf1wp0ySxPt2ZXHnFQBN2Szu1O//7LmOvgaKTCIDNLK2NvzpmVd5A2M/1j/rujBqO37hj3w==",
+      "version": "4.31.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.31.1.tgz",
+      "integrity": "sha512-dnVZDB6FhpIby6yVbHkwTKkn2ypjVIfAR9nh+kYsA/ZL0JlTsd22BiDjouotisY3Irmd3OW1qlk9EI5R8GrvRQ==",
       "dependencies": {
-        "@typescript-eslint/scope-manager": "4.31.0",
-        "@typescript-eslint/types": "4.31.0",
-        "@typescript-eslint/typescript-estree": "4.31.0",
+        "@typescript-eslint/scope-manager": "4.31.1",
+        "@typescript-eslint/types": "4.31.1",
+        "@typescript-eslint/typescript-estree": "4.31.1",
         "debug": "^4.3.1"
       },
       "engines": {
@@ -3750,12 +3762,12 @@
       }
     },
     "node_modules/@typescript-eslint/scope-manager": {
-      "version": "4.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.31.0.tgz",
-      "integrity": "sha512-LJ+xtl34W76JMRLjbaQorhR0hfRAlp3Lscdiz9NeI/8i+q0hdBZ7BsiYieLoYWqy+AnRigaD3hUwPFugSzdocg==",
+      "version": "4.31.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.31.1.tgz",
+      "integrity": "sha512-N1Uhn6SqNtU2XpFSkD4oA+F0PfKdWHyr4bTX0xTj8NRx1314gBDRL1LUuZd5+L3oP+wo6hCbZpaa1in6SwMcVQ==",
       "dependencies": {
-        "@typescript-eslint/types": "4.31.0",
-        "@typescript-eslint/visitor-keys": "4.31.0"
+        "@typescript-eslint/types": "4.31.1",
+        "@typescript-eslint/visitor-keys": "4.31.1"
       },
       "engines": {
         "node": "^8.10.0 || ^10.13.0 || >=11.10.1"
@@ -3766,9 +3778,9 @@
       }
     },
     "node_modules/@typescript-eslint/types": {
-      "version": "4.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.31.0.tgz",
-      "integrity": "sha512-9XR5q9mk7DCXgXLS7REIVs+BaAswfdHhx91XqlJklmqWpTALGjygWVIb/UnLh4NWhfwhR5wNe1yTyCInxVhLqQ==",
+      "version": "4.31.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.31.1.tgz",
+      "integrity": "sha512-kixltt51ZJGKENNW88IY5MYqTBA8FR0Md8QdGbJD2pKZ+D5IvxjTYDNtJPDxFBiXmka2aJsITdB1BtO1fsgmsQ==",
       "engines": {
         "node": "^8.10.0 || ^10.13.0 || >=11.10.1"
       },
@@ -3778,12 +3790,12 @@
       }
     },
     "node_modules/@typescript-eslint/typescript-estree": {
-      "version": "4.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.0.tgz",
-      "integrity": "sha512-QHl2014t3ptg+xpmOSSPn5hm4mY8D4s97ftzyk9BZ8RxYQ3j73XcwuijnJ9cMa6DO4aLXeo8XS3z1omT9LA/Eg==",
+      "version": "4.31.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.1.tgz",
+      "integrity": "sha512-EGHkbsUvjFrvRnusk6yFGqrqMBTue5E5ROnS5puj3laGQPasVUgwhrxfcgkdHNFECHAewpvELE1Gjv0XO3mdWg==",
       "dependencies": {
-        "@typescript-eslint/types": "4.31.0",
-        "@typescript-eslint/visitor-keys": "4.31.0",
+        "@typescript-eslint/types": "4.31.1",
+        "@typescript-eslint/visitor-keys": "4.31.1",
         "debug": "^4.3.1",
         "globby": "^11.0.3",
         "is-glob": "^4.0.1",
@@ -3818,11 +3830,11 @@
       }
     },
     "node_modules/@typescript-eslint/visitor-keys": {
-      "version": "4.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.0.tgz",
-      "integrity": "sha512-HUcRp2a9I+P21+O21yu3ezv3GEPGjyGiXoEUQwZXjR8UxRApGeLyWH4ZIIUSalE28aG4YsV6GjtaAVB3QKOu0w==",
+      "version": "4.31.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.1.tgz",
+      "integrity": "sha512-PCncP8hEqKw6SOJY+3St4LVtoZpPPn+Zlpm7KW5xnviMhdqcsBty4Lsg4J/VECpJjw1CkROaZhH4B8M1OfnXTQ==",
       "dependencies": {
-        "@typescript-eslint/types": "4.31.0",
+        "@typescript-eslint/types": "4.31.1",
         "eslint-visitor-keys": "^2.0.0"
       },
       "engines": {
@@ -5192,15 +5204,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/bindings": {
-      "version": "1.5.0",
-      "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
-      "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
-      "optional": true,
-      "dependencies": {
-        "file-uri-to-path": "1.0.0"
-      }
-    },
     "node_modules/bluebird": {
       "version": "3.7.2",
       "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
@@ -7705,6 +7708,67 @@
         "url": "https://opencollective.com/eslint"
       }
     },
+    "node_modules/eslint-config-airbnb": {
+      "version": "18.2.1",
+      "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-18.2.1.tgz",
+      "integrity": "sha512-glZNDEZ36VdlZWoxn/bUR1r/sdFKPd1mHPbqUtkctgNG4yT2DLLtJ3D+yCV+jzZCc2V1nBVkmdknOJBZ5Hc0fg==",
+      "dev": true,
+      "dependencies": {
+        "eslint-config-airbnb-base": "^14.2.1",
+        "object.assign": "^4.1.2",
+        "object.entries": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 6"
+      },
+      "peerDependencies": {
+        "eslint": "^5.16.0 || ^6.8.0 || ^7.2.0",
+        "eslint-plugin-import": "^2.22.1",
+        "eslint-plugin-jsx-a11y": "^6.4.1",
+        "eslint-plugin-react": "^7.21.5",
+        "eslint-plugin-react-hooks": "^4 || ^3 || ^2.3.0 || ^1.7.0"
+      }
+    },
+    "node_modules/eslint-config-airbnb-base": {
+      "version": "14.2.1",
+      "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.1.tgz",
+      "integrity": "sha512-GOrQyDtVEc1Xy20U7vsB2yAoB4nBlfH5HZJeatRXHleO+OS5Ot+MWij4Dpltw4/DyIkqUfqz1epfhVR5XWWQPA==",
+      "dev": true,
+      "dependencies": {
+        "confusing-browser-globals": "^1.0.10",
+        "object.assign": "^4.1.2",
+        "object.entries": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 6"
+      },
+      "peerDependencies": {
+        "eslint": "^5.16.0 || ^6.8.0 || ^7.2.0",
+        "eslint-plugin-import": "^2.22.1"
+      }
+    },
+    "node_modules/eslint-config-airbnb-typescript": {
+      "version": "14.0.0",
+      "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-14.0.0.tgz",
+      "integrity": "sha512-d2Nit2ByZARGRYK6tgSNl3nnmGZPyvsgbsKFcmm+nAhvT8VjVpifG5jI4tzObUUPb0sWw0E1oO/0pSpBD/pIuQ==",
+      "dev": true,
+      "peerDependencies": {
+        "@typescript-eslint/eslint-plugin": "^4.29.3",
+        "@typescript-eslint/parser": "^4.29.3"
+      }
+    },
+    "node_modules/eslint-config-prettier": {
+      "version": "8.3.0",
+      "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz",
+      "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==",
+      "dev": true,
+      "bin": {
+        "eslint-config-prettier": "bin/cli.js"
+      },
+      "peerDependencies": {
+        "eslint": ">=7.0.0"
+      }
+    },
     "node_modules/eslint-config-react-app": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-6.0.0.tgz",
@@ -8922,12 +8986,6 @@
         "url": "https://opencollective.com/webpack"
       }
     },
-    "node_modules/file-uri-to-path": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
-      "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
-      "optional": true
-    },
     "node_modules/filesize": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz",
@@ -9351,19 +9409,6 @@
       "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
       "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
     },
-    "node_modules/fsevents": {
-      "version": "2.3.2",
-      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
-      "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
-      "hasInstallScript": true,
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
-      }
-    },
     "node_modules/function-bind": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
@@ -11571,6 +11616,54 @@
         "node": ">=8"
       }
     },
+    "node_modules/jest-junit": {
+      "version": "12.2.0",
+      "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-12.2.0.tgz",
+      "integrity": "sha512-ecGzF3KEQwLbMP5xMO7wqmgmyZlY/5yWDvgE/vFa+/uIT0KsU5nluf0D2fjIlOKB+tb6DiuSSpZuGpsmwbf7Fw==",
+      "dev": true,
+      "dependencies": {
+        "mkdirp": "^1.0.4",
+        "strip-ansi": "^5.2.0",
+        "uuid": "^8.3.2",
+        "xml": "^1.0.1"
+      },
+      "engines": {
+        "node": ">=10.12.0"
+      }
+    },
+    "node_modules/jest-junit/node_modules/ansi-regex": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+      "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/jest-junit/node_modules/mkdirp": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+      "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+      "dev": true,
+      "bin": {
+        "mkdirp": "bin/cmd.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/jest-junit/node_modules/strip-ansi": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+      "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+      "dev": true,
+      "dependencies": {
+        "ansi-regex": "^4.1.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/jest-leak-detector": {
       "version": "26.6.2",
       "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz",
@@ -13608,12 +13701,6 @@
       "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz",
       "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE="
     },
-    "node_modules/nan": {
-      "version": "2.15.0",
-      "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz",
-      "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==",
-      "optional": true
-    },
     "node_modules/nanoid": {
       "version": "3.1.25",
       "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz",
@@ -16451,6 +16538,18 @@
         "react": ">=16.3.0"
       }
     },
+    "node_modules/react-hook-form": {
+      "version": "7.15.4",
+      "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.15.4.tgz",
+      "integrity": "sha512-jEtsDBPfpkz1uuJVlTLDOg+jO3cG9pFHT3g5uayVvlNT551IetXE1iwrSaxUR/QPWyJA2FLx4Q/VjO2viZNfLg==",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/react-hook-form"
+      },
+      "peerDependencies": {
+        "react": "^16.8.0 || ^17"
+      }
+    },
     "node_modules/react-i18next": {
       "version": "11.11.4",
       "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.11.4.tgz",
@@ -19981,7 +20080,7 @@
       "version": "8.3.2",
       "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
       "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
-      "optional": true,
+      "devOptional": true,
       "bin": {
         "uuid": "dist/bin/uuid"
       }
@@ -20227,24 +20326,6 @@
         "node": ">=0.10.0"
       }
     },
-    "node_modules/watchpack-chokidar2/node_modules/fsevents": {
-      "version": "1.2.13",
-      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
-      "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
-      "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.",
-      "hasInstallScript": true,
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "dependencies": {
-        "bindings": "^1.5.0",
-        "nan": "^2.12.1"
-      },
-      "engines": {
-        "node": ">= 4.0"
-      }
-    },
     "node_modules/watchpack-chokidar2/node_modules/glob-parent": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
@@ -20682,24 +20763,6 @@
         "node": ">=6"
       }
     },
-    "node_modules/webpack-dev-server/node_modules/fsevents": {
-      "version": "1.2.13",
-      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
-      "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
-      "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.",
-      "hasInstallScript": true,
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "dependencies": {
-        "bindings": "^1.5.0",
-        "nan": "^2.12.1"
-      },
-      "engines": {
-        "node": ">= 4.0"
-      }
-    },
     "node_modules/webpack-dev-server/node_modules/glob-parent": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
@@ -21844,6 +21907,12 @@
         }
       }
     },
+    "node_modules/xml": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
+      "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=",
+      "dev": true
+    },
     "node_modules/xml-name-validator": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz",
@@ -24555,12 +24624,12 @@
       "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw=="
     },
     "@typescript-eslint/eslint-plugin": {
-      "version": "4.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.31.0.tgz",
-      "integrity": "sha512-iPKZTZNavAlOhfF4gymiSuUkgLne/nh5Oz2/mdiUmuZVD42m9PapnCnzjxuDsnpnbH3wT5s2D8bw6S39TC6GNw==",
+      "version": "4.31.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.31.1.tgz",
+      "integrity": "sha512-UDqhWmd5i0TvPLmbK5xY3UZB0zEGseF+DHPghZ37Sb83Qd3p8ujhvAtkU4OF46Ka5Pm5kWvFIx0cCTBFKo0alA==",
       "requires": {
-        "@typescript-eslint/experimental-utils": "4.31.0",
-        "@typescript-eslint/scope-manager": "4.31.0",
+        "@typescript-eslint/experimental-utils": "4.31.1",
+        "@typescript-eslint/scope-manager": "4.31.1",
         "debug": "^4.3.1",
         "functional-red-black-tree": "^1.0.1",
         "regexpp": "^3.1.0",
@@ -24579,50 +24648,50 @@
       }
     },
     "@typescript-eslint/experimental-utils": {
-      "version": "4.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.31.0.tgz",
-      "integrity": "sha512-Hld+EQiKLMppgKKkdUsLeVIeEOrwKc2G983NmznY/r5/ZtZCDvIOXnXtwqJIgYz/ymsy7n7RGvMyrzf1WaSQrw==",
+      "version": "4.31.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.31.1.tgz",
+      "integrity": "sha512-NtoPsqmcSsWty0mcL5nTZXMf7Ei0Xr2MT8jWjXMVgRK0/1qeQ2jZzLFUh4QtyJ4+/lPUyMw5cSfeeME+Zrtp9Q==",
       "requires": {
         "@types/json-schema": "^7.0.7",
-        "@typescript-eslint/scope-manager": "4.31.0",
-        "@typescript-eslint/types": "4.31.0",
-        "@typescript-eslint/typescript-estree": "4.31.0",
+        "@typescript-eslint/scope-manager": "4.31.1",
+        "@typescript-eslint/types": "4.31.1",
+        "@typescript-eslint/typescript-estree": "4.31.1",
         "eslint-scope": "^5.1.1",
         "eslint-utils": "^3.0.0"
       }
     },
     "@typescript-eslint/parser": {
-      "version": "4.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.31.0.tgz",
-      "integrity": "sha512-oWbzvPh5amMuTmKaf1wp0ySxPt2ZXHnFQBN2Szu1O//7LmOvgaKTCIDNLK2NvzpmVd5A2M/1j/rujBqO37hj3w==",
+      "version": "4.31.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.31.1.tgz",
+      "integrity": "sha512-dnVZDB6FhpIby6yVbHkwTKkn2ypjVIfAR9nh+kYsA/ZL0JlTsd22BiDjouotisY3Irmd3OW1qlk9EI5R8GrvRQ==",
       "requires": {
-        "@typescript-eslint/scope-manager": "4.31.0",
-        "@typescript-eslint/types": "4.31.0",
-        "@typescript-eslint/typescript-estree": "4.31.0",
+        "@typescript-eslint/scope-manager": "4.31.1",
+        "@typescript-eslint/types": "4.31.1",
+        "@typescript-eslint/typescript-estree": "4.31.1",
         "debug": "^4.3.1"
       }
     },
     "@typescript-eslint/scope-manager": {
-      "version": "4.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.31.0.tgz",
-      "integrity": "sha512-LJ+xtl34W76JMRLjbaQorhR0hfRAlp3Lscdiz9NeI/8i+q0hdBZ7BsiYieLoYWqy+AnRigaD3hUwPFugSzdocg==",
+      "version": "4.31.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.31.1.tgz",
+      "integrity": "sha512-N1Uhn6SqNtU2XpFSkD4oA+F0PfKdWHyr4bTX0xTj8NRx1314gBDRL1LUuZd5+L3oP+wo6hCbZpaa1in6SwMcVQ==",
       "requires": {
-        "@typescript-eslint/types": "4.31.0",
-        "@typescript-eslint/visitor-keys": "4.31.0"
+        "@typescript-eslint/types": "4.31.1",
+        "@typescript-eslint/visitor-keys": "4.31.1"
       }
     },
     "@typescript-eslint/types": {
-      "version": "4.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.31.0.tgz",
-      "integrity": "sha512-9XR5q9mk7DCXgXLS7REIVs+BaAswfdHhx91XqlJklmqWpTALGjygWVIb/UnLh4NWhfwhR5wNe1yTyCInxVhLqQ=="
+      "version": "4.31.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.31.1.tgz",
+      "integrity": "sha512-kixltt51ZJGKENNW88IY5MYqTBA8FR0Md8QdGbJD2pKZ+D5IvxjTYDNtJPDxFBiXmka2aJsITdB1BtO1fsgmsQ=="
     },
     "@typescript-eslint/typescript-estree": {
-      "version": "4.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.0.tgz",
-      "integrity": "sha512-QHl2014t3ptg+xpmOSSPn5hm4mY8D4s97ftzyk9BZ8RxYQ3j73XcwuijnJ9cMa6DO4aLXeo8XS3z1omT9LA/Eg==",
+      "version": "4.31.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.1.tgz",
+      "integrity": "sha512-EGHkbsUvjFrvRnusk6yFGqrqMBTue5E5ROnS5puj3laGQPasVUgwhrxfcgkdHNFECHAewpvELE1Gjv0XO3mdWg==",
       "requires": {
-        "@typescript-eslint/types": "4.31.0",
-        "@typescript-eslint/visitor-keys": "4.31.0",
+        "@typescript-eslint/types": "4.31.1",
+        "@typescript-eslint/visitor-keys": "4.31.1",
         "debug": "^4.3.1",
         "globby": "^11.0.3",
         "is-glob": "^4.0.1",
@@ -24641,11 +24710,11 @@
       }
     },
     "@typescript-eslint/visitor-keys": {
-      "version": "4.31.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.0.tgz",
-      "integrity": "sha512-HUcRp2a9I+P21+O21yu3ezv3GEPGjyGiXoEUQwZXjR8UxRApGeLyWH4ZIIUSalE28aG4YsV6GjtaAVB3QKOu0w==",
+      "version": "4.31.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.1.tgz",
+      "integrity": "sha512-PCncP8hEqKw6SOJY+3St4LVtoZpPPn+Zlpm7KW5xnviMhdqcsBty4Lsg4J/VECpJjw1CkROaZhH4B8M1OfnXTQ==",
       "requires": {
-        "@typescript-eslint/types": "4.31.0",
+        "@typescript-eslint/types": "4.31.1",
         "eslint-visitor-keys": "^2.0.0"
       }
     },
@@ -25740,15 +25809,6 @@
       "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
       "optional": true
     },
-    "bindings": {
-      "version": "1.5.0",
-      "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
-      "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
-      "optional": true,
-      "requires": {
-        "file-uri-to-path": "1.0.0"
-      }
-    },
     "bluebird": {
       "version": "3.7.2",
       "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
@@ -27839,6 +27899,42 @@
         }
       }
     },
+    "eslint-config-airbnb": {
+      "version": "18.2.1",
+      "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-18.2.1.tgz",
+      "integrity": "sha512-glZNDEZ36VdlZWoxn/bUR1r/sdFKPd1mHPbqUtkctgNG4yT2DLLtJ3D+yCV+jzZCc2V1nBVkmdknOJBZ5Hc0fg==",
+      "dev": true,
+      "requires": {
+        "eslint-config-airbnb-base": "^14.2.1",
+        "object.assign": "^4.1.2",
+        "object.entries": "^1.1.2"
+      }
+    },
+    "eslint-config-airbnb-base": {
+      "version": "14.2.1",
+      "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.1.tgz",
+      "integrity": "sha512-GOrQyDtVEc1Xy20U7vsB2yAoB4nBlfH5HZJeatRXHleO+OS5Ot+MWij4Dpltw4/DyIkqUfqz1epfhVR5XWWQPA==",
+      "dev": true,
+      "requires": {
+        "confusing-browser-globals": "^1.0.10",
+        "object.assign": "^4.1.2",
+        "object.entries": "^1.1.2"
+      }
+    },
+    "eslint-config-airbnb-typescript": {
+      "version": "14.0.0",
+      "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-14.0.0.tgz",
+      "integrity": "sha512-d2Nit2ByZARGRYK6tgSNl3nnmGZPyvsgbsKFcmm+nAhvT8VjVpifG5jI4tzObUUPb0sWw0E1oO/0pSpBD/pIuQ==",
+      "dev": true,
+      "requires": {}
+    },
+    "eslint-config-prettier": {
+      "version": "8.3.0",
+      "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz",
+      "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==",
+      "dev": true,
+      "requires": {}
+    },
     "eslint-config-react-app": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-6.0.0.tgz",
@@ -28640,12 +28736,6 @@
         }
       }
     },
-    "file-uri-to-path": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
-      "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
-      "optional": true
-    },
     "filesize": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz",
@@ -28978,12 +29068,6 @@
       "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
       "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
     },
-    "fsevents": {
-      "version": "2.3.2",
-      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
-      "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
-      "optional": true
-    },
     "function-bind": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
@@ -30610,6 +30694,41 @@
         }
       }
     },
+    "jest-junit": {
+      "version": "12.2.0",
+      "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-12.2.0.tgz",
+      "integrity": "sha512-ecGzF3KEQwLbMP5xMO7wqmgmyZlY/5yWDvgE/vFa+/uIT0KsU5nluf0D2fjIlOKB+tb6DiuSSpZuGpsmwbf7Fw==",
+      "dev": true,
+      "requires": {
+        "mkdirp": "^1.0.4",
+        "strip-ansi": "^5.2.0",
+        "uuid": "^8.3.2",
+        "xml": "^1.0.1"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+          "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+          "dev": true
+        },
+        "mkdirp": {
+          "version": "1.0.4",
+          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+          "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+          "dev": true
+        },
+        "strip-ansi": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+          "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^4.1.0"
+          }
+        }
+      }
+    },
     "jest-leak-detector": {
       "version": "26.6.2",
       "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz",
@@ -32150,12 +32269,6 @@
       "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz",
       "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE="
     },
-    "nan": {
-      "version": "2.15.0",
-      "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz",
-      "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==",
-      "optional": true
-    },
     "nanoid": {
       "version": "3.1.25",
       "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz",
@@ -34408,6 +34521,12 @@
         "react-side-effect": "^2.1.0"
       }
     },
+    "react-hook-form": {
+      "version": "7.15.4",
+      "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.15.4.tgz",
+      "integrity": "sha512-jEtsDBPfpkz1uuJVlTLDOg+jO3cG9pFHT3g5uayVvlNT551IetXE1iwrSaxUR/QPWyJA2FLx4Q/VjO2viZNfLg==",
+      "requires": {}
+    },
     "react-i18next": {
       "version": "11.11.4",
       "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.11.4.tgz",
@@ -37201,7 +37320,7 @@
       "version": "8.3.2",
       "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
       "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
-      "optional": true
+      "devOptional": true
     },
     "v8-compile-cache": {
       "version": "2.3.0",
@@ -37410,16 +37529,6 @@
             }
           }
         },
-        "fsevents": {
-          "version": "1.2.13",
-          "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
-          "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
-          "optional": true,
-          "requires": {
-            "bindings": "^1.5.0",
-            "nan": "^2.12.1"
-          }
-        },
         "glob-parent": {
           "version": "3.1.0",
           "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
@@ -38005,16 +38114,6 @@
             "locate-path": "^3.0.0"
           }
         },
-        "fsevents": {
-          "version": "1.2.13",
-          "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
-          "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
-          "optional": true,
-          "requires": {
-            "bindings": "^1.5.0",
-            "nan": "^2.12.1"
-          }
-        },
         "glob-parent": {
           "version": "3.1.0",
           "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
@@ -38708,6 +38807,12 @@
       "integrity": "sha512-zP9z6GXm6zC27YtspwH99T3qTG7bBFv2VIkeHstMLrLlDJuzA7tQ5ls3OJ1hOGGCzTQPniNJoHXIAOS0Jljohg==",
       "requires": {}
     },
+    "xml": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
+      "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=",
+      "dev": true
+    },
     "xml-name-validator": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index e31e8c92ddc6cccfc7c1b6a594968fcf2fc683d0..2ee7d0571d10ac8a3745a0440eb246582827d87d 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -18,6 +18,7 @@
     "react-datepicker": "^4.2.1",
     "react-dom": "^17.0.2",
     "react-helmet": "^6.1.0",
+    "react-hook-form": "^7.15.3",
     "react-i18next": "^11.11.4",
     "react-router-dom": "^5.3.0",
     "react-scripts": "4.0.3",
@@ -26,11 +27,16 @@
     "web-vitals": "^1.1.2"
   },
   "scripts": {
-    "start": "NODE_ENV=development HOST=0.0.0.0 npm run build:env && react-scripts start",
-    "build": "npm run build:env && react-scripts build",
+    "start": "NODE_ENV=development HOST=0.0.0.0 npm run build:env && DISABLE_ESLINT_PLUGIN='true' react-scripts start",
+    "build": "npm run build:env && DISABLE_ESLINT_PLUGIN='true' react-scripts build",
     "build:env": "node scripts/build-env.js",
+    "test": "react-scripts test --env=jsdom",
+    "test:ci": "react-scripts test --env=jsdom --ci --reporters=default --reporters=jest-junit --coverageReporters=cobertura --coverage --coverageDirectory=coverage",
+    "coverage:ci": "react-scripts test --env=jsdom --ci --coverage --color --watchAll=false",
+    "coverage": "react-scripts test --env=jsdom --coverage --color",
+    "lint": "eslint --ext js,jsx,ts,tsx src",
+    "lint-report": "eslint --ext js,jsx,ts,tsx -f checkstyle -o checkstyle-result.xml src",
     "clean": "rm -rf node_modules",
-    "test": "react-scripts test",
     "eject": "react-scripts eject"
   },
   "eslintConfig": {
@@ -54,6 +60,17 @@
   "devDependencies": {
     "@types/react-datepicker": "^4.1.7",
     "@types/react-helmet": "^6.1.2",
-    "@types/react-router-dom": "^5.1.8"
+    "@types/react-router-dom": "^5.1.8",
+    "@typescript-eslint/eslint-plugin": "^4.31.1",
+    "@typescript-eslint/parser": "^4.31.1",
+    "eslint": "^7.32.0",
+    "eslint-config-airbnb": "^18.2.1",
+    "eslint-config-airbnb-typescript": "^14.0.0",
+    "eslint-config-prettier": "^8.3.0",
+    "eslint-plugin-import": "^2.24.2",
+    "eslint-plugin-jsx-a11y": "^6.4.1",
+    "eslint-plugin-react": "^7.25.1",
+    "eslint-plugin-react-hooks": "^4.2.0",
+    "jest-junit": "^12.2.0"
   }
 }
diff --git a/frontend/public/env.js b/frontend/public/env.js
deleted file mode 100644
index c8a0f84e3e93334b727664616bffe2bf3fc3a5c8..0000000000000000000000000000000000000000
--- a/frontend/public/env.js
+++ /dev/null
@@ -1,7 +0,0 @@
-window.ENV = {
-  NODE_ENV: 'development',
-  PUBLIC_URL: '',
-  FAST_REFRESH: true,
-  REACT_APP_VERSION: '0.1.0',
-  REACT_APP_NAME: 'greg',
-}
diff --git a/frontend/public/locales/en/common.json b/frontend/public/locales/en/common.json
index fdf892b074b390c1ed1116c9a9e6c5e153d8791b..3e446301e09b80fec0560fad31d727e4f0e7d106 100644
--- a/frontend/public/locales/en/common.json
+++ b/frontend/public/locales/en/common.json
@@ -6,5 +6,12 @@
   "language": {
     "change": "Change language to {{lang}}"
   },
-  "loading": "Loading..."
+  "header": {
+    "applicationTitleShort": "GREG",
+    "applicationTitleLong": "Guest Registration",
+    "selectLanguage": "Select language"
+  },
+  "loading": "Loading...",
+  "termsHeader": "Terms",
+  "staging": "Staging"
 }
diff --git a/frontend/public/locales/en/footer.json b/frontend/public/locales/en/footer.json
index 205afe095d14684e1f7f96bac62f68e5aed1292c..d052c9b6ffc6413c632c721e8e4f57d2470e9f33 100644
--- a/frontend/public/locales/en/footer.json
+++ b/frontend/public/locales/en/footer.json
@@ -1,3 +1,7 @@
 {
-  "footerInfo": "This is a footer namespace value"
+  "footerInfo": "This is a footer namespace value",
+  "contactSectionHeader": "Need help?",
+  "contactHelp": "Contact",
+  "contactHelpMail": "Contact",
+  "responsibleOrganizationHeader": "Maintained by"
 }
diff --git a/frontend/public/locales/nb/common.json b/frontend/public/locales/nb/common.json
index 96be51a3348df0f7451619613948c127d5441d10..887180ac7c0f29a071985584d002416fa659c419 100644
--- a/frontend/public/locales/nb/common.json
+++ b/frontend/public/locales/nb/common.json
@@ -6,5 +6,12 @@
   "language": {
     "change": "Bytt språk til {{lang}}"
   },
-  "loading": "Laster..."
+  "header":{
+    "applicationTitleShort": "GREG",
+    "applicationTitleLong": "Gjesteregistrering",
+    "selectLanguage": "Velg språk"
+  },
+  "loading": "Laster...",
+  "termsHeader": "Vilkår",
+  "staging": "Staging"
 }
diff --git a/frontend/public/locales/nb/footer.json b/frontend/public/locales/nb/footer.json
index cb74ed15090a08f18e35249cd3e900c64a8d8c6c..436d90c7f38d75465ee6db23ba0202b7e93e7c6f 100644
--- a/frontend/public/locales/nb/footer.json
+++ b/frontend/public/locales/nb/footer.json
@@ -1,3 +1,7 @@
 {
-  "footerInfo": "Verdi i namespace footer"
+  "footerInfo": "Verdi i namespace footer",
+  "contactSectionHeader": "Trenger du hjelp?",
+  "contactHelp": "Kontakt",
+  "contactHelpMail": "Kontaktveileder",
+  "responsibleOrganizationHeader": "Ansvarlig for tjenesten"
 }
diff --git a/frontend/public/locales/nn/common.json b/frontend/public/locales/nn/common.json
index 9da0352ce402851e72b564f51b8c909428f57937..92c11d0febbfabda6240752d39f1573cbd218a8e 100644
--- a/frontend/public/locales/nn/common.json
+++ b/frontend/public/locales/nn/common.json
@@ -4,7 +4,15 @@
     "test": "Dette er ein 'nested' verdi"
   },
   "language": {
+    "languageName": "Språk",
     "change": "Bytt språk til {{lang}}"
   },
-  "loading": "Lastar..."
+  "header":{
+    "applicationTitleShort": "GREG",
+    "applicationTitleLong": "Gjesteregistrering",
+    "selectLanguage": "Velg språk"
+  },
+  "loading": "Lastar...",
+  "termsHeader": "Vilkår",
+  "staging": "Staging"
 }
diff --git a/frontend/public/locales/nn/footer.json b/frontend/public/locales/nn/footer.json
index cb74ed15090a08f18e35249cd3e900c64a8d8c6c..ce248c21dd6ff9285f77f8c4dbde08303ba943e3 100644
--- a/frontend/public/locales/nn/footer.json
+++ b/frontend/public/locales/nn/footer.json
@@ -1,3 +1,7 @@
 {
-  "footerInfo": "Verdi i namespace footer"
+  "footerInfo": "Verdi i namespace footer",
+  "contactSectionHeader": "Trenger du hjelp?",
+  "contactHelp": "Kontakt",
+  "contactHelpMail": "Kontaktrettleiar",
+  "responsibleOrganizationHeader": "Ansvarleg for tenesta"
 }
diff --git a/frontend/public/uio/uio-app-logo-en.png b/frontend/public/uio/uio-app-logo-en.png
new file mode 100644
index 0000000000000000000000000000000000000000..fa5c8e2e2d02c81ba3554a2aca6e56427991275f
Binary files /dev/null and b/frontend/public/uio/uio-app-logo-en.png differ
diff --git a/frontend/public/uio/uio-app-logo-nb.png b/frontend/public/uio/uio-app-logo-nb.png
new file mode 100644
index 0000000000000000000000000000000000000000..478f41b5abd9b52ed8b8dfb06ce0f46651e5353c
Binary files /dev/null and b/frontend/public/uio/uio-app-logo-nb.png differ
diff --git a/frontend/src/appConfig.ts b/frontend/src/appConfig.ts
index ea76eda4eccb697485a4299896781824d1b78438..84880d70a088ae516f630454601f05d9eb61b823 100644
--- a/frontend/src/appConfig.ts
+++ b/frontend/src/appConfig.ts
@@ -1,8 +1,8 @@
 declare global {
-  /* tslint:disable */
-  interface Window {
-    ENV: any
-  }
+    /* tslint:disable */
+    interface Window {
+        ENV: any
+    }
 }
 
 /* Locate the client environment */
@@ -16,7 +16,19 @@ export const appTimezone: string = 'Europe/Oslo'
 export const appVersion: string = process.env.REACT_APP_VERSION as string
 export const appName: string = process.env.REACT_APP_NAME as string
 
+/* Institution */
+export const appInst: string = env.REACT_APP_INST as string
+
 /* Theming */
 export const appTheme: string = env.REACT_APP_THEME
-  ? (env.REACT_APP_THEME as string)
-  : 'default'
+    ? (env.REACT_APP_THEME as string)
+    : 'default'
+
+/* Show warning in the footer about this being a staging/test system */
+export const appStagingWarning: boolean =
+    env.REACT_APP_STAGING_WARNING === 'true'
+
+/* Footer content */
+export const appTechnicalSupportLink: string = env.REACT_APP_SUPPORT_URL as string
+export const reponsibleOrganization: string = env.REACT_APP_RESPONSIBLE_ORGANIZATION as string
+export const responsibleOrganizationLink: string = env.REACT_APP_RESPONSIBLE_ORGANIZATION_LINK as string
diff --git a/frontend/src/components/button/index.tsx b/frontend/src/components/button/index.tsx
index d2b3635acbbc66ed2dbe9427bd6fa7ebd1b5664c..9eb200519a6b51d5342bd15a8a5b9a1b188e2fe7 100644
--- a/frontend/src/components/button/index.tsx
+++ b/frontend/src/components/button/index.tsx
@@ -15,3 +15,4 @@ export const Button = styled.a`
   }
   background: ${({ theme }) => theme.colors.main};
 `
+export default Button
diff --git a/frontend/src/components/dropdownmenu/index.tsx b/frontend/src/components/dropdownmenu/index.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..fbdbd36feb4135114f5c8be6d5260d3ad8c68013
--- /dev/null
+++ b/frontend/src/components/dropdownmenu/index.tsx
@@ -0,0 +1,131 @@
+import React, { useState } from 'react'
+import styled from 'styled-components/macro'
+
+
+const DropDownMenu = styled.div`
+  position: relative;
+
+  &:hover {
+    cursor: pointer;
+  }
+
+  width: fit-content;
+  display: inline-block;
+`
+
+const DropDownList = styled.ul`
+  position: absolute;
+  right: 1rem;
+  top: 2rem;
+  border-radius: 1rem;
+  list-style-type: none;
+  min-width: 10rem;
+  max-height: 50rem;
+  z-index: 100;
+  background: ${({ theme }) => theme.colors.dropDownMenuBackground};
+`
+
+interface IDropDownOption {
+  name: string;
+  value: any;
+}
+
+const getValueName = (value: any, options: Array<IDropDownOption>): string => {
+  for (let i = 0; i < options.length; i += 1) {
+    if (options[i].value === value) {
+      return options[i].name
+    }
+  }
+  return ''
+}
+
+interface IProps {
+  options: any[];
+  placeholder?: string;
+  value: any;
+  onChange: (event: any) => void;
+}
+
+
+function DropDown(props: IProps) {
+  const [open, setOpen] = useState(false)
+
+  const {
+    options,
+    placeholder,
+    value,
+    onChange,
+  } = props
+
+  function handleClick(option: any) {
+    setOpen(!open)
+    onChange(option.value)
+  }
+
+  function openMenu() {
+    if (!open) {
+      setOpen(true)
+    }
+  }
+
+  return (
+
+    <DropDownMenu
+      aria-haspopup='true'
+      aria-expanded={open}
+      /* eslint-disable-next-line react/jsx-no-bind */
+      onFocus={openMenu}
+      tabIndex={0}
+    >
+      <div>
+        <div role='button'
+             onKeyDown={(event) => {
+               if (event.key === 'Enter' || event.key === ' ') {
+                 openMenu()
+               }
+             }}
+             onClick={openMenu}
+             tabIndex={0}
+        >
+          {/* eslint-disable-next-line */}
+          {placeholder
+            ? placeholder
+            : getValueName(value, options).toLowerCase()}
+        </div>
+      </div>
+      <div>
+        {open && (
+          <DropDownList>
+            {options.map((option) => (
+              // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
+              <li
+                onKeyDown={(event) => {
+                  if (event.key === 'Enter' || event.key === ' ') {
+                    openMenu()
+                  }
+                }}
+
+                onClick={(
+                  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+                  event: React.MouseEvent<HTMLElement, MouseEvent>,
+                ) => {
+                  handleClick(option)
+                }}
+              >
+                <p>{option.name}</p>
+              </li>
+            ))}
+          </DropDownList>
+        )}
+      </div>
+    </DropDownMenu>
+  )
+}
+
+
+DropDown.defaultProps =
+  {
+    placeholder: undefined,
+  }
+
+export default DropDown
diff --git a/frontend/src/components/languageselector/index.tsx b/frontend/src/components/languageselector/index.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..575b56d7904b1558c685b9b4556a29b1c76b65e8
--- /dev/null
+++ b/frontend/src/components/languageselector/index.tsx
@@ -0,0 +1,38 @@
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import DropDown from '../dropdownmenu'
+
+
+function LanguageSelector() {
+  const { i18n, t } = useTranslation('common')
+
+  const options = [
+    {
+      name: 'English',
+      value: 'en',
+    },
+    {
+      name: 'Norsk nynorsk',
+      value: 'nn',
+    },
+    {
+      name: 'Norsk bokmål',
+      value: 'nb',
+    },
+  ]
+
+
+  return (
+    <DropDown options={options}
+              placeholder={t('header.selectLanguage')}
+              value={options[2]}
+              onChange={newValue => {
+                i18n.changeLanguage(newValue)
+              }
+              }
+    />
+  )
+}
+
+
+export default LanguageSelector
diff --git a/frontend/src/components/link/index.tsx b/frontend/src/components/link/index.tsx
index f3b06431ab0374ebc2ca642940b3f744e10e033c..f701b98cccd9dd2e504642548ed4607c520db7f5 100644
--- a/frontend/src/components/link/index.tsx
+++ b/frontend/src/components/link/index.tsx
@@ -1,13 +1,12 @@
-import styled, {css, DefaultTheme} from 'styled-components/macro'
-
+import React from 'react'
+import styled, { css } from 'styled-components/macro'
 
 interface IStyledLink {
-    theme: DefaultTheme
-    external?: boolean
-    underline?: boolean
-    marginRight?: boolean
-    inheritColor?: boolean
-    noUnderline?: boolean
+  external?: boolean
+  underline?: boolean
+  marginRight?: boolean
+  inheritColor?: boolean
+  noUnderline?: boolean
 }
 
 
@@ -32,25 +31,28 @@ const externalLinkIcon = (
                 d="M16.807.19a.596.596 0 0 0-.426-.173h-4.854a.596.596 0 0 0-.426.173.548.548 0 0 0-.18.409c0 .157.06.294.18.409l1.668 1.598-6.18 5.923a.281.281 0 0 0-.095.21c0 .078.031.148.094.208L7.67 9.983a.306.306 0 0 0 .436 0l6.18-5.923 1.67 1.599c.12.115.262.172.426.172a.596.596 0 0 0 .426-.172.547.547 0 0 0 .18-.409V.599a.548.548 0 0 0-.18-.409z"/>
         </g>
     </svg>
-);
+)
 
 const ExternalIcon = styled.span`
   margin-left: 1.2rem;
   position: relative;
   top: 1px;
-  zIndex: -1;
+  z-index: -1;
 `
 
 const baseInputStyles = css<IStyledLink>`
   display: inline-flex;
   align-items: center;
 
-  color: ${props => props.external ? props.theme.linkExternalColor : props.theme.linkInternalColor};
+  color: ${(props) =>
+          props.external
+                  ? props.theme.linkExternalColor
+                  : props.theme.linkInternalColor};
 
-  ${props => props.marginRight ? "margin-right: 3rem;" : ""}
-  ${props => props.inheritColor ? "color: inherit;" : ""}
-  ${props => props.underline ? "text-decoration: underline;" : ""}
-  ${props => props.noUnderline ? ":hover { text-decoration: none };" : ""}
+  ${(props) => (props.marginRight ? 'margin-right: 3rem;' : '')}
+  ${(props) => (props.inheritColor ? 'color: inherit;' : '')}
+  ${(props) => (props.underline ? 'text-decoration: underline;' : '')}
+  ${(props) => (props.noUnderline ? ':hover { text-decoration: none };' : '')}
 `
 
 const StyledLink = styled.a<IStyledLink>`
@@ -62,47 +64,67 @@ const StyledLink = styled.a<IStyledLink>`
 // `
 
 function Link(props: ILink) {
-    const {children, external, to, noExternalIcon, mail} = props
-
-    if (mail) {
-        return (
-            <StyledLink href={`mailto:${to}`} {...props}>
-                {children}
-            </StyledLink>
-        );
-    }
-
-    if (external) {
-        const urlRegex = /^((http|https):\/\/)/;
-        const href = urlRegex.test(to) ? to : `//${to}`;
-
-        return (
-            <StyledLink
-                href={href}
-                target="_blank"
-                rel="noopener noreferrer"
-                {...props}
-            >
-                {children}
-                {!noExternalIcon && (
-                    <ExternalIcon>{externalLinkIcon}</ExternalIcon>
-                )}
-            </StyledLink>
-        );
-    }
-
-    // TODO Use StyledRouterLink for internal links when routes are set up
-    // return (
-    //     <StyledRouterLink {...props}>
-    //         {children}
-    //     </StyledRouterLink>
-    // )
+  const { children, external, to, noExternalIcon, mail, underline, marginRight, inheritColor, noUnderline } = props
 
+  if (mail) {
     return (
-        <StyledLink {...props}>
-            {children}
-        </StyledLink>
+      <StyledLink href={`mailto:${to}`}
+                  external={external}
+                  underline={underline}
+                  marginRight={marginRight}
+                  inheritColor={inheritColor}
+                  noUnderline={noUnderline}>
+        {children}
+      </StyledLink>
     )
+  }
+
+  if (external) {
+    const urlRegex = /^((http|https):\/\/)/
+    const href = urlRegex.test(to) ? to : `//${to}`
+
+    return (
+      <StyledLink
+        href={href}
+        target='_blank'
+        rel='noopener noreferrer'
+        external={external}
+        underline={underline}
+        marginRight={marginRight}
+        inheritColor={inheritColor}
+        noUnderline={noUnderline}
+      >
+        {children}
+        {!noExternalIcon && <ExternalIcon>{externalLinkIcon}</ExternalIcon>}
+      </StyledLink>
+    )
+  }
+
+  // TODO Use StyledRouterLink for internal links when routes are set up
+  // return (
+  //     <StyledRouterLink {...props}>
+  //         {children}
+  //     </StyledRouterLink>
+  // )
+
+  return <StyledLink
+    external={external}
+    underline={underline}
+    marginRight={marginRight}
+    inheritColor={inheritColor}
+    noUnderline={noUnderline}>
+    {children}</StyledLink>
+}
+
+Link.defaultProps = {
+  external: false,
+  children: undefined,
+  marginRight: false,
+  noExternalIcon: false,
+  mail: false,
+  inheritColor: false,
+  underline: false,
+  noUnderline: false,
 }
 
-export default Link
\ No newline at end of file
+export default Link
diff --git a/frontend/src/components/link/link.test.tsx b/frontend/src/components/link/link.test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..6662674008c7c35a84994ff41ee79b61d3aa85cc
--- /dev/null
+++ b/frontend/src/components/link/link.test.tsx
@@ -0,0 +1,14 @@
+import React from 'react'
+import { render, waitFor, screen } from '@testing-library/react'
+import Link from './index'
+
+
+describe('Tests for Link component', () => {
+  test('External link prefixes route link with //', async () => {
+    render(<Link external to='example.org' />)
+
+    await waitFor(() => screen.getByRole('link'))
+
+    expect(screen.getByRole('link')).toHaveAttribute('href', '//example.org')
+  })
+})
\ No newline at end of file
diff --git a/frontend/src/components/logobars/LogoBar.tsx b/frontend/src/components/logobars/LogoBar.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..2af512f339f8742d8428050f52ea4753ea82c047
--- /dev/null
+++ b/frontend/src/components/logobars/LogoBar.tsx
@@ -0,0 +1,18 @@
+import React from 'react'
+import { appTheme } from 'appConfig'
+import UiOLogoBar from './UiO'
+import UiBLogoBar from './UiB'
+
+
+function LogoBar() {
+  switch (appTheme) {
+    case 'uio':
+      return <UiOLogoBar />
+    case 'uib':
+      return <UiBLogoBar />
+    default:
+      return <UiOLogoBar />
+  }
+}
+
+export default LogoBar
\ No newline at end of file
diff --git a/frontend/src/components/logobars/UiB.tsx b/frontend/src/components/logobars/UiB.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..059ac0d05a1f779ac73565cfba4937b26d3d246c
--- /dev/null
+++ b/frontend/src/components/logobars/UiB.tsx
@@ -0,0 +1,25 @@
+import React from 'react'
+import styled from 'styled-components/macro'
+
+
+const LogoBarWrapper = styled.div`
+  background-color: ${(props) => props.theme.colors.secondary});
+`
+
+const LogoBar = styled.div`
+  margin: 0 auto;
+  max-width: ${({ theme }) => theme.appMaxWidth});
+  padding: 0 ${({ theme }) => theme.horizontalMdPadding});
+`
+
+function UiBLogoBar() {
+  return (
+    <LogoBarWrapper>
+      <LogoBar>
+        Insert UiB logo here
+      </LogoBar>
+    </LogoBarWrapper>
+  )
+}
+
+export default UiBLogoBar
diff --git a/frontend/src/components/logobars/UiO.tsx b/frontend/src/components/logobars/UiO.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..8796ac584d1d430e2be16ad14e6a10c0b8f32dc3
--- /dev/null
+++ b/frontend/src/components/logobars/UiO.tsx
@@ -0,0 +1,33 @@
+import React from 'react'
+import styled from 'styled-components/macro'
+
+import { useTranslation } from 'react-i18next'
+
+
+const LogoBarWrapper = styled.div`
+  display: flex;
+  justify-content: center;
+  background-color: ${({ theme }) => theme.page.headerBackgroundColor};
+`
+
+type Language = {
+  language: string
+}
+
+const Logo = styled.div<Language>`
+  background: ${props => props.language === 'en' ? 'url("/uio/uio-app-logo-en.png") left center no-repeat' : 'url("/uio/uio-app-logo-nb.png") left center no-repeat'};
+  min-width: 20rem;
+  height: 2rem;
+`
+
+function UiOLogoBar() {
+  const { i18n } = useTranslation(['common', 'footer'])
+
+  return (
+    <LogoBarWrapper>
+      <Logo language={i18n.language} />
+    </LogoBarWrapper>
+  )
+}
+
+export default UiOLogoBar
diff --git a/frontend/src/components/page/index.tsx b/frontend/src/components/page/index.tsx
index 5d31ca8a20ff8c779db47e94380b346a8debdc21..811d46d7edec587ec56109b427987f45fb2683c1 100644
--- a/frontend/src/components/page/index.tsx
+++ b/frontend/src/components/page/index.tsx
@@ -1,3 +1,3 @@
 import Page from 'components/page/page'
 
-export { Page }
+export default Page
diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx
index 059e8b4984845324ad08cd6a2d330e181d0c0b74..4237ac3faad693edf181048c19b300c16adc68ab 100644
--- a/frontend/src/index.tsx
+++ b/frontend/src/index.tsx
@@ -8,8 +8,8 @@ import 'i18n'
 import getTheme from 'themes'
 import GlobalStyle from 'globalStyles'
 import App from 'routes'
-import reportWebVitals from './reportWebVitals'
 import Loading from 'components/loading'
+import reportWebVitals from './reportWebVitals'
 
 function appRoot() {
   return (
diff --git a/frontend/src/routes/components/footer.tsx b/frontend/src/routes/components/footer.tsx
index b36a7433dc291149b7100c5452e142bfb26d9204..f6821a56c801a7ae8588bbb35b3c1dc9c8f0306d 100644
--- a/frontend/src/routes/components/footer.tsx
+++ b/frontend/src/routes/components/footer.tsx
@@ -1,27 +1,91 @@
 import React from 'react'
 import styled from 'styled-components/macro'
+import { useTranslation } from 'react-i18next'
+import {
+  appStagingWarning,
+  appTechnicalSupportLink,
+  reponsibleOrganization, responsibleOrganizationLink,
+} from '../../appConfig'
+import Link from '../../components/link'
 
-// Placeholder footer component
 
-const StyledFooter = styled.footer`
+const FooterWrapper = styled.footer`
   background: ${({ theme }) => theme.footer.backgroundColor};
-  padding: 4rem 0;
+  height: fit-content;
+  padding: 0rem ${({ theme }) => theme.horizontalPadding};
   margin-top: auto;
 `
 
-const FooterWrapper = styled.div`
-  background: ${({ theme }) => theme.footer.backgroundColor};
-  color: white;
-  height: fit-content;
-  max-width: ${({ theme }) => theme.appMaxWidth};
-  margin: 0 auto;
-  padding: 0rem ${({ theme }) => theme.horizontalPadding};
+const FooterSection = styled.section`
+  header {
+    margin-bottom: 0.5rem;
+    font-size: 1.5rem;
+  }
+
+  padding-left: 1rem;
+`
+
+
+const FooterSectionContent = styled.div`
+  font-size: 1rem;
+  padding-left: 0.3rem;
+`
+
+const ContentContainer = styled.div`
+  width: fit-content;
+  color: ${({ theme }) => theme.footerTextColor};
+  display: flex;
+  flex-wrap: nowrap;
+  justify-content: space-between;
+  margin: auto;
+  padding-top: 1rem;
+  padding-right: 1rem;
+  padding-bottom: 1rem;
 `
 
-export default function Footer() {
+const Footer: React.FunctionComponent = () => {
+  const { t } = useTranslation(['common', 'footer'])
+
   return (
-    <StyledFooter>
-      <FooterWrapper>Footer 123</FooterWrapper>
-    </StyledFooter>
+    <>
+      <FooterWrapper>
+        <ContentContainer>
+          <FooterSection>
+            <header>{t('footer:contactSectionHeader')}</header>
+            <FooterSectionContent>
+              <Link
+                external
+                to={appTechnicalSupportLink}
+                inheritColor
+                underline
+              >
+                {t('footer:contactHelp')}
+              </Link>
+            </FooterSectionContent>
+          </FooterSection>
+
+          <FooterSection>
+            <header>{t('footer:responsibleOrganizationHeader')}</header>
+            <FooterSectionContent>
+              <Link
+                external
+                to={responsibleOrganizationLink}
+                inheritColor
+                underline
+              >
+                {reponsibleOrganization}
+              </Link>
+            </FooterSectionContent>
+          </FooterSection>
+        </ContentContainer>
+      </FooterWrapper>
+      {appStagingWarning && (
+        <div className='alert'>
+          {t('staging')}
+        </div>
+      )}
+    </>
   )
 }
+
+export default Footer
diff --git a/frontend/src/routes/components/header.tsx b/frontend/src/routes/components/header.tsx
index b297d552d8ed737f12652fc4623c20b4a58095d3..0a4f62cbdfa3e2ff31d8a8d7a05155ed1abf5361 100644
--- a/frontend/src/routes/components/header.tsx
+++ b/frontend/src/routes/components/header.tsx
@@ -1,45 +1,67 @@
 import React from 'react'
 import styled from 'styled-components/macro'
+import { useTranslation } from 'react-i18next'
+import LogoBar from '../../components/logobars/LogoBar'
+import LanguageSelector from '../../components/languageselector'
 
-// Placeholder header component!
 
 const MainWrapper = styled.div`
-  background-color: green;
+  color: ${({ theme }) => theme.page.headerColor};
+  background-color: ${({ theme }) => theme.page.headerBackgroundColor};
 `
 
 const Main = styled.div`
+  display: flex;
+  justify-content: space-between;
   margin: 0 auto;
-  max-width: ${({ theme }) => theme.maxWidth};
-  height: ${({ theme }) => theme.header.height};
-  padding: 2.5rem ${({ theme }) => theme.horizontalPadding} 3rem
-    ${({ theme }) => theme.horizontalPadding};
+  max-width: ${props => props.theme.appMaxWidth};
+  padding: ${props => `0.5rem ${props.theme.horizontalPadding} 1rem ${props.theme.horizontalPadding}`};
+`
+
+const Menu = styled.ul`
+  list-style-type: none;
+`
+
+const MenuItem = styled.div`
+  font-size: 1rem;
+  display: inline;
 `
 
-const MainRow = styled.div`
+const TitleBox = styled.div`
   display: flex;
-  justify-content: space-between;
+  flex-direction: column;
+  padding-left: 3rem;
 `
 
-const PageTitle = styled.div`
-  color: blue;
+const ShortTitle = styled.div`
+  font-size: 2rem;
 `
-const H2 = styled.h2`
-  font-size: 2.8rem;
-  line-height: 4.5rem;
+
+const LongTitle = styled.div`
+  font-size: 1rem;
 `
 
-export default function Header() {
+function Header() {
+  const { t } = useTranslation('common')
+
   return (
     <header>
+      <LogoBar />
       <MainWrapper>
         <Main>
-          <MainRow>
-            <PageTitle>
-              <H2>Greg!</H2>
-            </PageTitle>
-          </MainRow>
+          <TitleBox>
+            <ShortTitle>{t('header.applicationTitleShort')}</ShortTitle>
+            <LongTitle>{t('header.applicationTitleLong')}</LongTitle>
+          </TitleBox>
+          <Menu>
+            <MenuItem>
+              <LanguageSelector/>
+            </MenuItem>
+          </Menu>
         </Main>
       </MainWrapper>
     </header>
   )
 }
+
+export default Header
diff --git a/frontend/src/routes/components/notFound.tsx b/frontend/src/routes/components/notFound.tsx
index 3dbdd110d58ec4213cb4f945d958d1954607513b..a0bfc390680e2ec063f82e71c6b55109339a6f48 100644
--- a/frontend/src/routes/components/notFound.tsx
+++ b/frontend/src/routes/components/notFound.tsx
@@ -1,5 +1,5 @@
 import React from 'react'
-import { Page } from 'components/page'
+import Page from 'components/page'
 
 export default function NotFound() {
   return (
diff --git a/frontend/src/routes/frontpage/index.tsx b/frontend/src/routes/frontpage/index.tsx
index b552134e5c0110fd15b4df455ddacd263c4f3090..859b8c518425e74b075bb443bec5e6dbcb7e60d3 100644
--- a/frontend/src/routes/frontpage/index.tsx
+++ b/frontend/src/routes/frontpage/index.tsx
@@ -1,7 +1,7 @@
 import React, { useState } from 'react'
 
 import DateInput from 'components/dateinput'
-import { Page } from 'components/page'
+import Page from 'components/page'
 import { appTimezone, appVersion, appTheme } from 'appConfig'
 import { useTranslation } from 'react-i18next'
 
@@ -21,9 +21,9 @@ export default function FrontPage() {
           setApiHealth(result)
         }
       })
+        // eslint-disable-next-line @typescript-eslint/no-unused-vars
       .catch((error) => {
         setApiHealth('error')
-        console.log(error)
       })
   }
   return (
diff --git a/frontend/src/routes/register/index.tsx b/frontend/src/routes/register/index.tsx
index f9c66e80ccb980407473cf6b9e9af11cd58764d0..2e7736db6ab03af384e17a9c2d9ea3ec75b456b9 100644
--- a/frontend/src/routes/register/index.tsx
+++ b/frontend/src/routes/register/index.tsx
@@ -1,6 +1,6 @@
 import React from 'react'
 
-import { Page } from 'components/page'
+import Page from 'components/page'
 
 export default function Register() {
   return (
diff --git a/frontend/src/routes/sponsor/frontpage/index.tsx b/frontend/src/routes/sponsor/frontpage/index.tsx
index c63cb35ea86a3c651666b1fdc3db77c87dc36a4c..027197853901cf466126e072941b15561f21c2b2 100644
--- a/frontend/src/routes/sponsor/frontpage/index.tsx
+++ b/frontend/src/routes/sponsor/frontpage/index.tsx
@@ -1,6 +1,6 @@
 import React from 'react'
 
-import { Page } from 'components/page'
+import Page from 'components/page'
 
 function FrontPage() {
   return (
diff --git a/frontend/src/routes/sponsor/guestInfo/index.tsx b/frontend/src/routes/sponsor/guestInfo/index.tsx
index d4b0f7442bf70dcc9b4e726dd7d0a3bac918d7a2..bb652151f9fefa03d7e17ea0c4776947a68dec48 100644
--- a/frontend/src/routes/sponsor/guestInfo/index.tsx
+++ b/frontend/src/routes/sponsor/guestInfo/index.tsx
@@ -1,7 +1,7 @@
 import React from 'react'
 import { useParams } from 'react-router-dom'
 
-import {Page} from 'components/page'
+import Page from 'components/page'
 
 type GuestInfoParams = {
   id: string
diff --git a/frontend/src/setupProxy.js b/frontend/src/setupProxy.js
index b1f04abd2b8ecd454be5833ff912e2000a8f7f86..a7d60f51a8c878a73e44ebf9849b965a65b330db 100644
--- a/frontend/src/setupProxy.js
+++ b/frontend/src/setupProxy.js
@@ -1,6 +1,6 @@
 const { createProxyMiddleware } = require('http-proxy-middleware')
 
-module.exports = function (app) {
+module.exports = (app) => {
   app.use(
     '/api',
     createProxyMiddleware({
diff --git a/frontend/src/styled.d.ts b/frontend/src/styled.d.ts
index a91823417eaed6818da06451516dbc9dd07a8c78..7031296ac88d87b9ff9da09ddd7a0c4d7bfcc3f4 100644
--- a/frontend/src/styled.d.ts
+++ b/frontend/src/styled.d.ts
@@ -7,6 +7,7 @@ declare module 'styled-components' {
     colors: {
       main: string
       secondary: string
+      dropDownMenuBackground: string
     }
     footer: {
       backgroundColor: string
@@ -17,9 +18,14 @@ declare module 'styled-components' {
     horizontalPadding: string
     linkExternalColor: string
     linkInternalColor: string
+    footerBackgroundColor: string
+    footerTextColor: string
+    footerJustifyContent: string
+    horizontalMdPadding: string
     maxWidth: string
     page: {
       headerColor: string
+      headerBackgroundColor: string
       horizontalPadding: string
     }
   }
diff --git a/frontend/src/test-utils.tsx b/frontend/src/test-utils.tsx
index 10d9b958a0aa203673faa8bae81053b340b6dcf7..10782331e89c522f61684182381cdedda326771f 100644
--- a/frontend/src/test-utils.tsx
+++ b/frontend/src/test-utils.tsx
@@ -5,9 +5,7 @@ import { ThemeProvider } from 'styled-components/macro'
 import mainTheme from 'themes/main'
 
 // Providers used in test rendering
-const AllTheProviders = ({ children }: any) => {
-  return <ThemeProvider theme={mainTheme}> {children} </ThemeProvider>
-}
+const AllTheProviders = ({ children }: any) => <ThemeProvider theme={mainTheme}> {children} </ThemeProvider>
 
 // Custom testing-library/react renderer using our providers.
 const customRender = (ui: React.ReactElement, options?: any) =>
diff --git a/frontend/src/themes/color.ts b/frontend/src/themes/color.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d13e0f21856f5fa36dce9942881c5289cf1d0b45
--- /dev/null
+++ b/frontend/src/themes/color.ts
@@ -0,0 +1,10 @@
+
+export module Color {
+    export const white = '#FFFFFF'
+    export const hotPink = '#FF69B4'
+    export const blueish = '#2771bb'
+    export const lightOliveGreen = '#91BD60'
+    export const black = '#000000'
+    export const darkGray = '#2D2D2E'
+    export const lighterBlack = '#282c34'
+}
diff --git a/frontend/src/themes/main.ts b/frontend/src/themes/main.ts
index e03827205a038806e78c27605a3877ff81869820..8af960ea1c0c06ecbcf1b84c2efd321980353f78 100644
--- a/frontend/src/themes/main.ts
+++ b/frontend/src/themes/main.ts
@@ -6,6 +6,7 @@ const mainTheme: DefaultTheme = {
   colors: {
     main: 'hotPink',
     secondary: 'white',
+    dropDownMenuBackground: 'grey'
   },
   footer: {
     backgroundColor: 'black',
@@ -17,8 +18,13 @@ const mainTheme: DefaultTheme = {
   linkInternalColor: 'white',
   linkExternalColor: 'blueish',
   maxWidth: '110rem',
+  footerBackgroundColor: 'green',
+  footerTextColor: 'white',
+  footerJustifyContent: 'flex-end',
+  horizontalMdPadding: '6.5rem',
   page: {
-    headerColor: 'grey',
+    headerColor: 'white',
+    headerBackgroundColor: 'black',
     horizontalPadding: '0rem',
   },
 }
diff --git a/frontend/src/themes/uib.ts b/frontend/src/themes/uib.ts
index 0cd69cd5732a76e9f1a4873916a17536c4de9c51..2d272789dcaba43a0569514d6d72041c38f75a25 100644
--- a/frontend/src/themes/uib.ts
+++ b/frontend/src/themes/uib.ts
@@ -2,6 +2,7 @@ const uibTheme = {
   colors: {
     main: 'hotPink',
     secondary: 'black',
+    dropDownMenuBackground: 'grey'
   },
 }
 
diff --git a/frontend/src/themes/uio.ts b/frontend/src/themes/uio.ts
index 6481aeb85e9e5a362322625834cdcabf836b9c06..6f0f9a1c4c4893660ae49153eda119c888d684cf 100644
--- a/frontend/src/themes/uio.ts
+++ b/frontend/src/themes/uio.ts
@@ -2,6 +2,7 @@ const uioTheme = {
   colors: {
     main: 'hotPink',
     secondary: 'white',
+    dropDownMenuBackground: 'grey'
   },
 }
 
diff --git a/greg/api/urls.py b/greg/api/urls.py
index c20513efc8e5e216945fc5e20ab8518c1859abf2..a2c98d41ba310ce9dbba1f4672a56d103206fc37 100644
--- a/greg/api/urls.py
+++ b/greg/api/urls.py
@@ -13,7 +13,7 @@ from greg.api.views.person import (
 from greg.api.views.role_type import RoleTypeViewSet
 from greg.api.views.sponsor import SponsorViewSet, SponsorGuestsViewSet
 
-router = DefaultRouter()
+router = DefaultRouter(trailing_slash=False)
 router.register(r"persons", PersonViewSet, basename="person")
 router.register(r"roletypes", RoleTypeViewSet, basename="roletype")
 router.register(r"consenttypes", ConsentTypeViewSet, basename="consenttype")
@@ -23,33 +23,34 @@ router.register(r"orgunit", OrganizationalUnitViewSet, basename="orgunit")
 
 urlpatterns = router.urls
 
+# Allowing trailing slashes to be optional for the URLs below
 urlpatterns += [
     re_path(
-        r"^persons/(?P<person_id>[0-9]+)/roles/$",
+        r"^persons/(?P<person_id>[0-9]+)/roles/?$",
         RoleViewSet.as_view({"get": "list", "post": "create"}),
         name="person_role-list",
     ),
     re_path(
-        r"^persons/(?P<person_id>[0-9]+)/roles/(?P<id>[0-9]+)/$",
+        r"^persons/(?P<person_id>[0-9]+)/roles/(?P<id>[0-9]+)/?$",
         RoleViewSet.as_view(
             {"get": "retrieve", "patch": "partial_update", "delete": "destroy"}
         ),
         name="person_role-detail",
     ),
     re_path(
-        r"^persons/(?P<person_id>[0-9]+)/identities/$",
+        r"^persons/(?P<person_id>[0-9]+)/identities/?$",
         IdentityViewSet.as_view({"get": "list", "post": "create"}),
         name="person_identity-list",
     ),
     re_path(
-        r"^persons/(?P<person_id>[0-9]+)/identities/(?P<id>[0-9]+)$",
+        r"^persons/(?P<person_id>[0-9]+)/identities/(?P<id>[0-9]+)/?$",
         IdentityViewSet.as_view(
             {"get": "retrieve", "delete": "destroy", "patch": "partial_update"}
         ),
         name="person_identity-detail",
     ),
     re_path(
-        r"^sponsors/(?P<sponsor_id>[0-9]+)/guests/$",
+        r"^sponsors/(?P<sponsor_id>[0-9]+)/guests/?$",
         SponsorGuestsViewSet.as_view({"get": "list"}),
         name="sponsor_guests-list",
     ),