diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..17971afa0a759b322b214514e3381c85226c478c
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,28 @@
+image: node:latest
+
+stages:          # List of stages for jobs, and their order of execution
+  - build
+  - test
+  - deploy
+
+
+build-job:       # This job runs in the build stage, which runs first.
+  stage: build
+  script:
+    - npm install 
+    - npm run build
+
+unit-test-job:   
+  stage: test    
+  script:
+    - npm install
+    - npm run test:unit
+
+    
+# Create e2e test job
+
+# Create deploy job
+
+
+
+ 
\ No newline at end of file
diff --git a/README.md b/README.md
index d011822129a3b63288284127554e7ec3e71ddd54..090646411398b131e385c88825a58fd184c24d6f 100644
--- a/README.md
+++ b/README.md
@@ -1,34 +1,106 @@
-# frontend
+# Sparesti: Frontend
+Sparesti is a full-stack application developed as part of an assessment in the IDATT2106 Software Engineering course
+at the Norwegian University of Science and Technology (NTNU). The application is developed with Spring Boot and Vue.js.
 
-## Project setup
-```
-npm install
-```
+## Team
+- Olav Sie Rotvær
+- Gia Hy Nguyen 
+- Melissa Visnjic
+- Henrik Tefre
+- Hanne-Sofie Søreide
+- Jeffrey Yaw Annor Tabiri
+- Ramtin Forouzandehjoo Samavat
+- Tobias Skipevåg Oftedal
 
-### Compiles and hot-reloads for development
-```
-npm run serve
-```
+## Table of Contents
+- [Overview](#overview)
+- [Features](#features)
+- [Installation Manual](#installation-manual)
+- [Tests](#tests)
+- [Acknowledgements](#acknowledgements)
 
-### Compiles and minifies for production
-```
-npm run build
-```
+## Overview
+Sparesti is a full-stack web application designed to provide a secure
+and efficient platform for users to manage their savings. It includes
+a range of features that support secure authentication, goal tracking,
+and personalized challenges to encourage users to save money.
 
-### Run your unit tests
-```
+## Features
+- **Secure Authentication**: Ensures that users can securely log in, register, and manage their accounts. Passwords are encrypted and stored safely to protect user data.  
+- **Goals And Challenges**: Enables users to set personal financial goals, invite friends or family for collaboration, and design personalized challenges to achieve these goals effectively.  
+- **Progress Tracking**: Utilizes interactive game elements for real-time visualization of progress, and rewards users with badges for meeting financial milestones.  
+- **Financial Management**: Includes functionality for uploading PDF bank statements for automated budget creation and transaction management.  
+- **Enhanced Financial Knowledge**: Budgets are generated using SSB statistical data and personal income, displaying expected versus actual spending values to help users compare and adjust their financial plans. Additionally, users can access a dedicated page with news related to finance.  
+- **Accessibility Support**: Complies with WCAG and Firefox accessibility standards to ensure usability for all users.  
+
+The project utilizes the following technologies:
+- Frontend: Vue.js with Node.js.
+- Backend: Spring Boot V3 with Java 21 and Maven.
+- Database: MySQL V8 for runtime and H2 for tests.
+
+## Installation Manual
+
+### Prerequisites
+- Node.js
+
+### Setup and Run
+1. Clone the repository
+   ```sh
+   git clone https://gitlab.stud.idi.ntnu.no/idatt2106_2024_04/frontend.git
+   ```
+
+2. Navigate to the project structure
+   ```sh
+   cd frontend
+   ```
+   
+3. Install dependencies
+   ```sh
+   npm install
+   ```
+
+4. Compile 
+   1. Hot-Reload for Development: 
+      ```sh 
+      npm run serve
+      ```
+   2. Or build for Production: 
+      ```sh 
+      npm run build 
+      ```
+      ```
+      cd dist
+      ```
+      ```
+      sudo npm install -g http-server
+      ```
+      ```
+      http-server -p 8082
+      ```
+      
+
+### Test users
+Test user:
+ - Email: admin@example.com
+ - Password: password
+   
+## Tests
+### Run Unit Tests with [Jest](https://jestjs.io/) 
+```sh
 npm run test:unit
 ```
 
-### Run your end-to-end tests
-```
-npm run test:e2e
+### Run End-to-End Tests with [Cypress](https://www.cypress.io/)
+Ensure that both the backend and frontend applications are running.
+```sh
+npm run test:e2e:dev
 ```
 
-### Lints and fixes files
-```
-npm run lint
-```
+### Test users and data
+#### Admin:
+- Email: admin@example.com
+- Password: password
 
-### Customize configuration
-See [Configuration Reference](https://cli.vuejs.org/config/).
+## Acknowledgements
+Special thanks to the subject teachers and the product owners for creating this assignment and providing us with the
+opportunity to develop this project.
diff --git a/jest.config.js b/jest.config.js
index 0f9579148896b7a46684f5e561ea000ca557ba2f..60a593c8e21beb1381b2f21384404baac06a3a68 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -1,3 +1,16 @@
 module.exports = {
-  preset: '@vue/cli-plugin-unit-jest'
+  preset: '@vue/cli-plugin-unit-jest',
+  transform: {
+    '^.+\\.vue$': '@vue/vue3-jest',
+    '^.+\\.js$': 'babel-jest'
+  },
+  transformIgnorePatterns: [
+    '/node_modules/(?!axios).+\\.js$'
+  ],
+  moduleNameMapper: {
+    '^@/(.*)$': '<rootDir>/src/$1'
+  },
+  testMatch: [
+    '**/tests/unit/**/*.spec.js'
+  ]
 }
diff --git a/package-lock.json b/package-lock.json
index 2190da4ca973acceebe8449b3bbef10bbcbac794..7057035f91d5ebbcd16becf7dc744b865ae49697 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,6 +11,8 @@
         "axios": "^1.6.8",
         "chart.js": "^2.9.4",
         "core-js": "^3.8.3",
+        "js-cookie": "^3.0.5",
+        "jwt-decode": "^4.0.0",
         "vue": "^3.2.13",
         "vue-chartjs": "^3.5.1",
         "vue-router": "^4.0.3",
@@ -30,6 +32,7 @@
         "@vue/vue3-jest": "^27.0.0-alpha.1",
         "babel-jest": "^27.0.6",
         "cypress": "^9.7.0",
+        "cypress-file-upload": "^5.0.8",
         "eslint": "^7.32.0",
         "eslint-plugin-vue": "^8.0.3",
         "jest": "^27.0.5"
@@ -6684,6 +6687,18 @@
         "node": ">=12.0.0"
       }
     },
+    "node_modules/cypress-file-upload": {
+      "version": "5.0.8",
+      "resolved": "https://registry.npmjs.org/cypress-file-upload/-/cypress-file-upload-5.0.8.tgz",
+      "integrity": "sha512-+8VzNabRk3zG6x8f8BWArF/xA/W0VK4IZNx3MV0jFWrJS/qKn8eHfa5nU73P9fOQAgwHFJx7zjg4lwOnljMO8g==",
+      "dev": true,
+      "engines": {
+        "node": ">=8.2.1"
+      },
+      "peerDependencies": {
+        "cypress": ">3.0.0"
+      }
+    },
     "node_modules/cypress/node_modules/@types/node": {
       "version": "14.18.63",
       "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz",
@@ -12617,7 +12632,6 @@
       "version": "3.0.5",
       "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
       "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
-      "dev": true,
       "engines": {
         "node": ">=14"
       }
@@ -12801,6 +12815,14 @@
         "verror": "1.10.0"
       }
     },
+    "node_modules/jwt-decode": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
+      "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/keyv": {
       "version": "4.5.4",
       "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
diff --git a/package.json b/package.json
index 80a8c785e6fd26d97ab5cca700fab2e6710f393b..c53f66c45f59d9e650dce9e3159cb2b8975e58a7 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,8 @@
     "axios": "^1.6.8",
     "chart.js": "^2.9.4",
     "core-js": "^3.8.3",
+    "js-cookie": "^3.0.5",
+    "jwt-decode": "^4.0.0",
     "vue": "^3.2.13",
     "vue-chartjs": "^3.5.1",
     "vue-router": "^4.0.3",
@@ -32,6 +34,7 @@
     "@vue/vue3-jest": "^27.0.0-alpha.1",
     "babel-jest": "^27.0.6",
     "cypress": "^9.7.0",
+    "cypress-file-upload": "^5.0.8",
     "eslint": "^7.32.0",
     "eslint-plugin-vue": "^8.0.3",
     "jest": "^27.0.5"
diff --git a/public/favicon.ico b/public/favicon.ico
deleted file mode 100644
index df36fcfb72584e00488330b560ebcf34a41c64c2..0000000000000000000000000000000000000000
Binary files a/public/favicon.ico and /dev/null differ
diff --git a/public/index.html b/public/index.html
index cbd56e31eea5e5b1c70ef607c0bb8fe178f31634..badf55997de2338b85c55d8c59e6c1f700366204 100644
--- a/public/index.html
+++ b/public/index.html
@@ -2,9 +2,10 @@
 <html lang="">
   <head>
     <meta charset="utf-8">
-    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+      <meta name="viewport" content="width=device-width, initial-scale=1.0">
+      <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta name="viewport" content="width=device-width,initial-scale=1.0">
-    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+    <link rel="icon" href="<%= BASE_URL %>pigrich.png">
     <title>Sparesti</title>
   </head>
   <body>
diff --git a/public/pigrich.png b/public/pigrich.png
new file mode 100644
index 0000000000000000000000000000000000000000..d32f57f7a2c717a8e14643ff6e98414d85e072a0
Binary files /dev/null and b/public/pigrich.png differ
diff --git a/s b/s
new file mode 100644
index 0000000000000000000000000000000000000000..7c4e06869117eb19ad33b67300c869e70c47c77d
--- /dev/null
+++ b/s
@@ -0,0 +1,32 @@
+37fd109 (HEAD -> development, origin/development, Creating-verificationPage) HEAD@{0}: checkout: moving from Creating-verificationPage to development
+37fd109 (HEAD -> development, origin/development, Creating-verificationPage) HEAD@{1}: checkout: moving from development to Creating-verificationPage
+37fd109 (HEAD -> development, origin/development, Creating-verificationPage) HEAD@{2}: checkout: moving from Contact to development
+6eef22b HEAD@{3}: commit (merge): merge: resolved merge conflict
+b4a4dc5 HEAD@{4}: commit: feat: added contact view and did necessary changes regarding contact.
+6f0c426 HEAD@{5}: checkout: moving from development to Contact
+6f0c426 HEAD@{6}: merge origin/development: Fast-forward
+415aaf4 HEAD@{7}: checkout: moving from settingss to development
+e23fa84 HEAD@{8}: checkout: moving from settings to settingss
+e23fa84 HEAD@{9}: checkout: moving from development to settings
+415aaf4 HEAD@{10}: checkout: moving from settings to development
+e23fa84 HEAD@{11}: merge origin/settings: Fast-forward
+f24b5b9 HEAD@{12}: checkout: moving from development to settings
+415aaf4 HEAD@{13}: merge origin/development: Fast-forward
+8e0913d HEAD@{14}: checkout: moving from challenge to development
+123b273 HEAD@{15}: checkout: moving from settings to challenge
+f24b5b9 HEAD@{16}: commit: feat: added some settings in settings
+2a00215 HEAD@{17}: commit: fix: fixed bleeding issue
+123b273 HEAD@{18}: checkout: moving from challenge to settings
+123b273 HEAD@{19}: commit: feat: created challengeComponent shell
+fa07c29 HEAD@{20}: checkout: moving from development to challenge
+fa07c29 HEAD@{21}: merge origin/development: Fast-forward
+36436cf HEAD@{22}: merge origin/development: Fast-forward
+394c69c HEAD@{23}: checkout: moving from news to development
+78df7c2 HEAD@{24}: commit: feat: created newspage, without connection to backend
+cf404a9 HEAD@{25}: checkout: moving from development to news
+cf404a9 HEAD@{26}: merge origin/development: Fast-forward
+ae9cbc9 HEAD@{27}: checkout: moving from LogIn to development
+dc1c7ce HEAD@{28}: commit: feat: added login and register shell
+c8d21dc HEAD@{29}: checkout: moving from development to LogIn
+c8d21dc HEAD@{30}: checkout: moving from main to development
+fb4d17b (main) HEAD@{31}: clone: from gitlab.stud.idi.ntnu.no:idatt2106_2024_04/frontend.git
diff --git a/src/App.vue b/src/App.vue
index e64c3d6274ccf8f3ceaa6b4d75f37887bcf5cd1e..9894ade5a3c04edf3abc3924f113f26c2b1b5b55 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,77 +1,116 @@
 <script setup>
-import { computed } from 'vue';
+import {computed, onMounted, onUnmounted, ref} from 'vue';
 import { useRoute } from 'vue-router';
 import NavigationBar from "@/components/NavigationBar.vue";
-import ProfileView from '@/views/ProfileView.vue';
-import InfoBar from "@/components/InfoBar.vue";
+import InfoBar from "@/components/infobar/InfoBar.vue";
+import TokenChecker from './components/TokenChecker.vue';
+import StatsPreview from "@/components/infobar/StatsPreview.vue";
+import CookieService from "@/services/internal/CookieService";
+import CookieBanner from "@/components/CookieBanner.vue";
+import BadgePopup from './components/BadgePopup.vue';
 
 const route = useRoute();
-const isProfileRoute = computed(() => route.path.includes('/profile'));
-</script>
+const showSidebar = computed(() => route.meta.showSidebar || false);
+const hideStats = computed(() => route.meta.hideStats || false);
+const showBanner = ref(false);
+
+const checkBanner = () => {
+  if (sessionStorage.getItem('showBanner') === 'true') {
+    showBanner.value = true;
+    setTimeout(() => {
+      sessionStorage.setItem('showBanner', 'false');
+      showBanner.value = false;
+    }, 5000);
+  }
+};
+
 
+let intervalId;
+
+onMounted(() => {
+  document.documentElement.classList.add(CookieService.getCookieWithConsent('fontSize'));
+  document.documentElement.classList.add(CookieService.getCookieWithConsent('borderRadius'));
+  document.documentElement.classList.add(CookieService.getCookieWithConsent('fontFamily'));
+  intervalId = setInterval(checkBanner, 600);
+})
+
+onUnmounted(() => {
+  clearInterval(intervalId);
+});
+</script>
 <template>
-  <div class="grid-container" :class="{ 'profile-active': isProfileRoute }">
-    <NavigationBar />
-    <RouterView v-if="!isProfileRoute" />
-    <InfoBar v-if="!isProfileRoute"/>
-    <ProfileView v-else />
+  <NavigationBar class="navigation-bar" />
+  <div class="grid-container-app" :class="showSidebar ? 'with-sidebar' : 'without-sidebar'">
+    <stats-preview v-if="!hideStats" class="stats-preview">
+    </stats-preview>
+    <RouterView class="router-view" />
+    <InfoBar class="info-bar" v-if="showSidebar" />
+    <TokenChecker />
+    <CookieBanner />
+    <BadgePopup v-if="showBanner"/>
   </div>
 </template>
 
 <style>
 @import '@/assets/root.css';
+/* used in vue template */
+.without-sidebar {
+  grid-template-columns: 100% !important;
+}
 
 * {
   margin: 0;
   padding: 0;
   font-size: var(--font-size-general);
-  font-family: var(--font-family-general);
+  font-family: var(--font-family-general), serif;
+  border-radius: var(--border-radius-general);
+  box-sizing: border-box;
 }
 
-.grid-container {
+.grid-container-app {
   display: grid;
-  grid-template-columns: 25% 50% 25%;
-  height: 100vh;
-  width: 100vw;
   overflow: hidden;
+  width: 100vw;
+  height: 100vh;
   background-color: var(--background-general);
-  transition: all 0.5s ease;
   font-size: var(--font-size-general);
+  border-radius: var(--border-radius-general);
+  grid-template-columns: 75% 25%;
+  grid-template-areas:
+    "router-view stats-preview"
+    "router-view info-bar";
+  box-sizing: border-box;
 }
 
-.profile-active {
-  grid-template-columns: 25% 75%; 
-}
-
-.navigationBar, .routerView, .infoBar {
-  height: 100%;
-  overflow: hidden;
-}
-
-
-.NavigationBar {
-  grid-area: navigationBar;
-}
-
-.RouterView {
-  grid-area: routerView;
-  padding-top: 50px;
-}
-
-.InfoBar {
-  grid-area: infoBar;
+.router-view {
+  padding: 0 2.5% 0 2.5%;
+  display: flex;
+  text-align: center;
+  flex-direction: column;
+  height: 100vh;
   overflow-y: auto;
   scrollbar-width: none;
-  -ms-overflow-style: none;
-  padding-top: 50px;
+  grid-area: router-view;
 }
 
-.InfoBar::-webkit-scrollbar {
-  display: none;
+.stats-preview {
+  grid-area: stats-preview;
 }
 
-.ProfileView {
-  grid-area: profileView;
+.info-bar {
+  grid-area: info-bar;
 }
 
+@media screen and (max-width: 800px) {
+  .grid-container-app {
+    grid-template-columns: 100%;
+    grid-template-areas:
+      "stats-preview"
+      "router-view"
+  }
+
+  .info-bar {
+    display: none;
+  }
+}
 </style>
diff --git a/src/assets/css/dropBox.css b/src/assets/css/dropBox.css
deleted file mode 100644
index d62c3511df4759a8af2b95eb237bcda0418fb913..0000000000000000000000000000000000000000
--- a/src/assets/css/dropBox.css
+++ /dev/null
@@ -1,21 +0,0 @@
-.dropdown {
-    width: 200px;
-    padding: 8px 16px;
-    border: 2px solid var(--dark-color);
-    border-radius: 5px;
-    background-color: #f5f5f5;
-    color: var(--black-text);
-    font-size: var(--font-size-general);
-    cursor: pointer;
-    outline: none;
-    transition: all 0.3s ease;
-}
-
-.dropdown:hover {
-    background-color: #e2e2e2;
-}
-
-.dropdown:focus {
-    border-color: var(--dark-color);
-    box-shadow: 0 0 8px var(--dark-color);
-}
\ No newline at end of file
diff --git a/src/assets/img/accounts.png b/src/assets/img/accounts.png
new file mode 100644
index 0000000000000000000000000000000000000000..f774cdc556d677b6adc3ccb4cb5630bfeef569b0
Binary files /dev/null and b/src/assets/img/accounts.png differ
diff --git a/src/assets/img/back.png b/src/assets/img/back.png
new file mode 100644
index 0000000000000000000000000000000000000000..ae7c6aed59c7edaff7d489d879bead91ba425dad
Binary files /dev/null and b/src/assets/img/back.png differ
diff --git a/src/assets/img/backArrow.png b/src/assets/img/backArrow.png
new file mode 100644
index 0000000000000000000000000000000000000000..d602f1076ca3db03fea4168525f31d0fc2011254
Binary files /dev/null and b/src/assets/img/backArrow.png differ
diff --git a/src/assets/img/banner.png b/src/assets/img/banner.png
new file mode 100644
index 0000000000000000000000000000000000000000..d72f1cd2fc3365d01a5a1aabd8f4ee8ceb886aac
Binary files /dev/null and b/src/assets/img/banner.png differ
diff --git a/src/assets/img/boom.gif b/src/assets/img/boom.gif
new file mode 100644
index 0000000000000000000000000000000000000000..6e0e3ed1ef02d0f5bc3389efa9cef729cfb656eb
Binary files /dev/null and b/src/assets/img/boom.gif differ
diff --git a/src/assets/img/botIcon.png b/src/assets/img/botIcon.png
new file mode 100644
index 0000000000000000000000000000000000000000..e9d8c2bf782942f9eddfe5ed4867a2dacdaf24e9
Binary files /dev/null and b/src/assets/img/botIcon.png differ
diff --git a/src/assets/img/budget.png b/src/assets/img/budget.png
new file mode 100644
index 0000000000000000000000000000000000000000..ec81cf04cddb2c3386b8327aeca54a6cd5a6c3cb
Binary files /dev/null and b/src/assets/img/budget.png differ
diff --git a/src/assets/img/challengeIcon.png b/src/assets/img/challengeIcon.png
new file mode 100644
index 0000000000000000000000000000000000000000..4b1ce326d54f2d5302570d04f2df3d2044e979e9
Binary files /dev/null and b/src/assets/img/challengeIcon.png differ
diff --git a/src/assets/img/circular_x.png b/src/assets/img/circular_x.png
new file mode 100644
index 0000000000000000000000000000000000000000..5fddcc461510903012131b8a8ae913970aa7105f
Binary files /dev/null and b/src/assets/img/circular_x.png differ
diff --git a/src/assets/img/contact.png b/src/assets/img/contact.png
new file mode 100644
index 0000000000000000000000000000000000000000..9d998a322187d4b7130300cb319fbac8cebba831
Binary files /dev/null and b/src/assets/img/contact.png differ
diff --git a/src/assets/img/join.png b/src/assets/img/join.png
new file mode 100644
index 0000000000000000000000000000000000000000..a55e73e83cb25d0f592331b1aac3d29964d5b5e7
Binary files /dev/null and b/src/assets/img/join.png differ
diff --git a/src/assets/img/logo_banner.png b/src/assets/img/logo_banner.png
new file mode 100644
index 0000000000000000000000000000000000000000..519561fe6b1c34aef77362f620ae544286500a77
Binary files /dev/null and b/src/assets/img/logo_banner.png differ
diff --git a/src/assets/img/mail.png b/src/assets/img/mail.png
new file mode 100644
index 0000000000000000000000000000000000000000..cc58ca35553781284cac74454fd307dcae936b74
Binary files /dev/null and b/src/assets/img/mail.png differ
diff --git a/src/assets/img/menu.png b/src/assets/img/menu.png
new file mode 100644
index 0000000000000000000000000000000000000000..495e9d236088010a594f11d294b956196d4dfb4b
Binary files /dev/null and b/src/assets/img/menu.png differ
diff --git a/src/assets/img/money.png b/src/assets/img/money.png
new file mode 100644
index 0000000000000000000000000000000000000000..1851ab64dca7dc84eace1c5d955c61f93083f988
Binary files /dev/null and b/src/assets/img/money.png differ
diff --git a/src/assets/img/newBadge.png b/src/assets/img/newBadge.png
new file mode 100644
index 0000000000000000000000000000000000000000..7a1074c565d4dee0025f96253b885770f681a8c3
Binary files /dev/null and b/src/assets/img/newBadge.png differ
diff --git a/src/assets/img/news.png b/src/assets/img/news.png
new file mode 100644
index 0000000000000000000000000000000000000000..577fcd258a5a29934cd9926fc36e1755fca4b80b
Binary files /dev/null and b/src/assets/img/news.png differ
diff --git a/src/assets/img/pigDance.gif b/src/assets/img/pigDance.gif
new file mode 100644
index 0000000000000000000000000000000000000000..8c7e547f664029493435a22b3e0c28a3ddd5df0a
Binary files /dev/null and b/src/assets/img/pigDance.gif differ
diff --git a/src/assets/img/pigrich.png b/src/assets/img/pigrich.png
new file mode 100644
index 0000000000000000000000000000000000000000..741e81e9e1a202b329190eb1f5c6fe47e5d63150
Binary files /dev/null and b/src/assets/img/pigrich.png differ
diff --git a/src/assets/img/pluss_icon.png b/src/assets/img/pluss_icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..db8ffbb2fb003b6928eac0a3b75648ea1c0b6752
Binary files /dev/null and b/src/assets/img/pluss_icon.png differ
diff --git a/src/assets/img/printer.png b/src/assets/img/printer.png
new file mode 100644
index 0000000000000000000000000000000000000000..3b6e1ba247bcdc5b869a30e8fa112a938f09da6b
Binary files /dev/null and b/src/assets/img/printer.png differ
diff --git a/src/assets/img/profile.png b/src/assets/img/profile.png
new file mode 100644
index 0000000000000000000000000000000000000000..ac044e856c2fd5dd4837ab42c378f5368f8e4cfb
Binary files /dev/null and b/src/assets/img/profile.png differ
diff --git a/src/assets/img/settings.png b/src/assets/img/settings.png
new file mode 100644
index 0000000000000000000000000000000000000000..3a36b3a1ca22b0731f9d9d97303642e4a7e616cd
Binary files /dev/null and b/src/assets/img/settings.png differ
diff --git a/src/assets/img/snus.png b/src/assets/img/snus.png
new file mode 100644
index 0000000000000000000000000000000000000000..38e217d5d088b397d210c0ca29647fd2ab726084
Binary files /dev/null and b/src/assets/img/snus.png differ
diff --git a/src/assets/img/logo.png b/src/assets/img/temp.png
similarity index 100%
rename from src/assets/img/logo.png
rename to src/assets/img/temp.png
diff --git a/src/assets/img/transaction.png b/src/assets/img/transaction.png
new file mode 100644
index 0000000000000000000000000000000000000000..0496c003973bab2627ce1b4800dbebd076ff42c7
Binary files /dev/null and b/src/assets/img/transaction.png differ
diff --git a/src/assets/img/user.png b/src/assets/img/user.png
new file mode 100644
index 0000000000000000000000000000000000000000..64cf89621fa735b02a23dfa143b7bc4bd199b1f7
Binary files /dev/null and b/src/assets/img/user.png differ
diff --git a/src/assets/img/userDetails/couplewithkids.webp b/src/assets/img/userDetails/couplewithkids.webp
new file mode 100644
index 0000000000000000000000000000000000000000..7c82e4f284384257eb9452134f9b7b09db4d961b
Binary files /dev/null and b/src/assets/img/userDetails/couplewithkids.webp differ
diff --git a/src/assets/img/userDetails/couplewithoutkids.webp b/src/assets/img/userDetails/couplewithoutkids.webp
new file mode 100644
index 0000000000000000000000000000000000000000..8f510375b6d89075bee416a5ab5945f1b421ec7e
Binary files /dev/null and b/src/assets/img/userDetails/couplewithoutkids.webp differ
diff --git a/src/assets/img/userDetails/livingalone.webp b/src/assets/img/userDetails/livingalone.webp
new file mode 100644
index 0000000000000000000000000000000000000000..db194b3a88f50a37d17f63ed75c3899f319f1429
Binary files /dev/null and b/src/assets/img/userDetails/livingalone.webp differ
diff --git a/src/assets/img/userDetails/other.webp b/src/assets/img/userDetails/other.webp
new file mode 100644
index 0000000000000000000000000000000000000000..242da92162019387307d8479099512ad349fdbae
Binary files /dev/null and b/src/assets/img/userDetails/other.webp differ
diff --git a/src/assets/img/usersIcon.png b/src/assets/img/usersIcon.png
new file mode 100644
index 0000000000000000000000000000000000000000..1103c95b6b440b19072e1d69daabfd56378e1837
Binary files /dev/null and b/src/assets/img/usersIcon.png differ
diff --git a/src/assets/root.css b/src/assets/root.css
index 9104bded972626fcae63074a4b7fcaa3cc1af41a..293569a49dd2f455a969f069297426fdcf8a42e3 100644
--- a/src/assets/root.css
+++ b/src/assets/root.css
@@ -1,32 +1,55 @@
 :root {
     --dark-color: #304C6C;
     --middle-color: #336699;
-    --light-color: #99ccff;
+    --light-color: #80b3ff;
 
     --white-text: #ffffff;
+    --white-changing-text: #ffffff;
     --black-text: #000000;
+    --black-changing-text: #000000;
     --grey-text: grey;
-    --error-text: darkred;
+    --error-text: #7c0000FF;
+    --green-text: #026502;
 
-    /*Fjerne disse?*/
     --accent-color: #f5f5f5;
-    --accent-color-dark: #cccaca;
+    --accent-color-dark: #e2e2e2;
 
-    /*Fjern disse?*/
     --black-general: #000000;
-    --grey-general: grey;
+    --grey-general: #575757;
     --white-general: #ffffff;
 
     --background-general: #ffffff;
     --font-size-general: 1rem;
     --font-family-general: "Arial Rounded MT", sans-serif;
+
+    --border-radius-general: 10px;
 }
 
+/*HEADERS-------------------------------------------------------------------------------------------------------*/
 h1, h2, h3, h4, h5{
     margin: 0;
     padding: 0;
 }
 
+h1 {
+    font-size: calc(2.5 * var(--font-size-general));
+}
+
+h2 {
+    font-size: calc(1.8 * var(--font-size-general));
+}
+
+h3 {
+    font-size: calc(1.6 * var(--font-size-general));
+}
+
+h4 {
+    font-size: calc(1.4 * var(--font-size-general));
+}
+
+h5 {
+    font-size: calc(1.2 * var(--font-size-general));
+}
 /*DARK OG LIGHT MODE------------------------------------------------------------------------------------------------*/
 .white-theme {
     --background-general: #ffffff;
@@ -34,11 +57,7 @@ h1, h2, h3, h4, h5{
 .dark-theme {
     --background-general: #2b2929;
 }
-
 /*FONTSTØRRELSTE-----------------------------------------------------------------------------------------------------*/
-.font-size-extra-small {
-    --font-size-general: 0.75rem;
-}
 .font-size-small {
     --font-size-general: 0.875rem;
 }
@@ -46,10 +65,28 @@ h1, h2, h3, h4, h5{
     --font-size-general: 1rem;
 }
 .font-size-large {
-    --font-size-general: 1.25rem;
+    --font-size-general: 1.15rem;
 }
-.font-size-extra-large {
-    --font-size-general: 1.5rem;
+
+/*TITLER------------------------------------------------------------------------------------------------------------*/
+.font-size-small h1, .font-size-medium h1, .font-size-large h1 {
+    font-size: calc(2.5 * var(--font-size-general));
+}
+
+.font-size-small h2, .font-size-medium h2, .font-size-large h2 {
+    font-size: calc(1.8 * var(--font-size-general));
+}
+
+.font-size-small h3, .font-size-medium h3, .font-size-large h3 {
+    font-size: calc(1.6 * var(--font-size-general));
+}
+
+.font-size-small h4, .font-size-medium h4, .font-size-large h4 {
+    font-size: calc(1.4 * var(--font-size-general));
+}
+
+.font-size-small h5, .font-size-medium h5, .font-size-large h5 {
+    font-size: calc(1.2 * var(--font-size-general));
 }
 
 /*FONTTYPE----------------------------------------------------------------------------------------------------------*/
@@ -72,23 +109,23 @@ h1, h2, h3, h4, h5{
     --font-family-general: "Papyrus,fantasy";
 }
 
-
-
+/*BORDER RADIUS-------------------------------------------------------------------------------------------------*/
 .border-none {
-    border-radius: 0;
+    --border-radius-general: 0;
 }
 
 .border-small {
-    border-radius: 5px;
+    --border-radius-general: 5px;
 }
 
 .border-medium {
-    border-radius: 10px;
+    --border-radius-general: 10px;
 }
 
 .border-large {
-    border-radius: 20px;
+    --border-radius-general: 20px;
 }
+/*OTHER--------------------------------------------------------------------------------------------------------*/
 
 * {
     font-family: "Arial Rounded MT", fantasy;
@@ -99,3 +136,53 @@ body {
     margin: 0;
     padding: 0;
 }
+/*BUTTON----------------------------------------------------------------------------------------------------------*/
+button {
+    background-color: var(--middle-color);
+    color: var(--white-text);
+    cursor: pointer;
+    width: 40%;
+    height: auto;
+    min-height: 40px;
+    border-radius: var(--border-radius-general);
+    transition: transform 0.3s ease, background-color 0.3s ease;
+    box-shadow: 2px 2px 4px 0 rgba(0, 0, 0, 0.25);
+    outline: none;
+    border-style: none;
+    text-align: center;
+    padding: 10px;
+}
+
+button:hover, button:focus {
+    background-color: var(--dark-color);
+    transform: translateY(-2px);
+}
+
+button:disabled {
+    background-color: var(--light-color);
+    transform: translateY(0);
+    cursor: not-allowed;
+    color: var(--black-text);
+    opacity: 0.6;
+}
+
+.dropdown {
+    width: 200px;
+    padding: 8px 16px;
+    border: none;
+    border-radius: var(--border-radius-general);
+    background-color: var(--accent-color);
+    color: var(--black-text);
+    font-size: var(--font-size-general);
+    cursor: pointer;
+    outline: none;
+    transition: all 0.3s ease;
+}
+
+.dropdown:hover {
+    background-color: var(--accent-color-dark);
+}
+
+.dropdown:focus {
+    box-shadow: 0 0 8px var(--dark-color);
+}
diff --git a/src/components/BadgePopup.vue b/src/components/BadgePopup.vue
new file mode 100644
index 0000000000000000000000000000000000000000..a714f58e56fcf00d622b311239294cabce5001a5
--- /dev/null
+++ b/src/components/BadgePopup.vue
@@ -0,0 +1,53 @@
+<template>
+    <div v-if="showBadge" class="badge-container" :style="{ top: positionTop + 'px' }">
+      <div class="badge">Ny badge oppnådd</div>
+    </div>
+  </template>
+  
+  <script setup>
+  import { ref, onMounted } from 'vue';
+  
+  const showBadge = ref(false);
+  const positionTop = ref(-40);
+  
+  onMounted(() => {
+    showBadge.value = true;
+    setTimeout(() => {
+      positionTop.value = 0; 
+      setTimeout(() => {
+        positionTop.value = -40;
+      }, 4000);
+    }, 100); 
+  });
+  </script>
+  
+  <style scoped>
+  .badge-container {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    display: flex;
+    justify-content: center;
+    z-index: 9999;
+  }
+  
+  .badge {
+    background-color: #4CAF50;
+    color: white;
+    padding: 10px 20px;
+    border-radius: var(--border-radius-general);
+    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
+    animation: slideDown 0.5s ease;
+  }
+  
+  @keyframes slideDown {
+    0% {
+      transform: translateY(-40px);
+    }
+    100% {
+      transform: translateY(0);
+    }
+  }
+  </style>
+  
\ No newline at end of file
diff --git a/src/components/CookieBanner.vue b/src/components/CookieBanner.vue
new file mode 100644
index 0000000000000000000000000000000000000000..4c1bdbe45fbb8eca9f1addb1f582d075c5de2ed1
--- /dev/null
+++ b/src/components/CookieBanner.vue
@@ -0,0 +1,74 @@
+<script setup>
+import { ref, onMounted } from 'vue';
+import CookieService from "@/services/internal/CookieService";
+
+const showBanner = ref(true);
+
+onMounted(() => {
+  console.log(CookieService.listCookies());
+  if (CookieService.getCookie('cookiesAccepted')) {
+    showBanner.value = false;
+  }
+});
+
+function acceptCookies() {
+  showBanner.value = false;
+  return CookieService.setCookie('cookiesAccepted', 'true', 7 );
+}
+
+function declineCookies() {
+  showBanner.value = false;
+  return CookieService.setCookie('cookiesAccepted', 'false', 7 );
+}
+</script>
+
+<template>
+  <div v-if="showBanner" class="cookie-banner">
+    <div class="cookie-content">
+      <p>Dette nettstedet bruker informasjonskapsler (cookies) for å forbedre din brukeropplevelse. Ved å fortsette å bruke nettstedet, godtar du vår bruk av cookies.</p>
+      <div class="cookie-buttons-container">
+        <button tabindex="0" @keydown="declineCookies" role="button" id="deny" @click="declineCookies">Avslå</button>
+        <button tabindex="0" @keydown="acceptCookies" role="button" id="accept" @click="acceptCookies">Godta</button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+p {
+  text-align: center;
+}
+.cookie-banner {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  width: 100%;
+  background-color: var(--black-general);
+  opacity: 80%;
+  color: white;
+  padding: 10px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  z-index: 1000;
+}
+
+.cookie-content p {
+  margin: 0 20px 0 0;
+}
+
+.cookie-buttons-container {
+  margin-top: 20px;
+  display: flex;
+  justify-content: space-evenly;
+}
+
+#accept {
+  background-color: #016701;
+}
+
+#deny {
+  background-color: #770101;
+}
+
+</style>
diff --git a/src/components/InfoBar.vue b/src/components/InfoBar.vue
deleted file mode 100644
index 290e7c791f033b5b6b1143ebbe20bc1a6324d301..0000000000000000000000000000000000000000
--- a/src/components/InfoBar.vue
+++ /dev/null
@@ -1,139 +0,0 @@
-<script setup>
-import { ref } from 'vue';
-import StockService from '@/services/external/StockService';
-
-const stockInfo = ref(null);
-const searchQuery = ref('');
-
-async function fetchStockInfo() {
-  if (!searchQuery.value.trim()) {
-    alert("Please enter a valid stock ticker.");
-    return;
-  }
-  try {
-    const result = await StockService.fetchStockInfo(searchQuery.value);
-    stockInfo.value = result;
-  } catch (error) {
-    console.error('Failed to fetch stock info:', error);
-    stockInfo.value = null;
-  }
-}
-</script>
-
-
-
-
-
-<template>
-  <div class="container">
-      <div class="stats">
-          <img src="@/assets/img/goalIcon.png" alt="Home" />
-          <h2>2</h2>
-          <img src="@/assets/img/badgeIcon.png" alt="Home" />
-          <h2>2</h2>
-          <img src="@/assets/img/fireIcon.png" alt="Home" />
-          <h2>2</h2>
-      </div>
-      <div class="box">
-        <h3>Total Saved: {{ totalSaved }}kr</h3>
-      </div>
-      <div class="box">
-        <input type="text" v-model="searchQuery" placeholder="Enter stock ticker...">
-        <button @click="fetchStockInfo()">Search</button>
-        <div v-if="stockInfo">
-  <h3>{{ stockInfo.name }} ({{ stockInfo.ticker }}): ${{ stockInfo.price }}</h3>
-  <p :class="{ negative: stockInfo.change < 0, positive: stockInfo.change >= 0 }">
-    Change: ${{ stockInfo.change }} ({{ stockInfo.changePercent }}%)
-  </p>
-</div>
-
-      </div>
-      <div class="box">
-        <h2>Ukens rabatter:</h2>
-        <h3>10% på Bilia</h3>
-        <h3>10% på Zara</h3>
-        <h3>25% på Power</h3>
-      </div>
-      <div class="box">Box 4</div>
-      <div class="box">Box 5</div>
-  </div>
-</template>
-
-
-
-<style scoped>
-
-.container {
-  display: flex;
-  flex-direction: column;
-  width: 100%;
-  max-height: 100vh;
-  scrollbar-width: none;
-  -ms-overflow-style: none;
-  overflow-y: auto;
-}
-
-.stats {
-    display: flex;
-    flex-direction: row;
-    gap: 25px;
-}
-
-.stats img {
-  margin-top: 25px;
-  width: 25px;
-  height: 25px;
-}
-
-.box {
-  background-color: #304C6C;
-  width: 100%; 
-  height: 200px; 
-  padding: 20px; 
-  border-radius: 10px; 
-  margin-bottom: 10px;
-  color: white; 
-  box-sizing: border-box; 
-}
-
-.box h3{
-  font-size: 30px;
-  flex-direction: column;
-}
-
-.positive {
-  color: green;
-}
-
-.negative {
-  color: red;
-}
-
-
-.box h3 {
-  font-size: 20px;
-  color: white;
-}
-
-.box p {
-  font-size: 20px;
-}
-
-.box:last-child {
-  margin-bottom: 0; 
-}
-
-.box input[type="text"], .box button {
-  padding: 10px;
-  margin-top: 10px;
-}
-
-.box button {
-  cursor: pointer;
-  background-color: #406882;
-  color: white;
-  border: none;
-  border-radius: 5px;
-}
-
-</style>
\ No newline at end of file
diff --git a/src/components/NavigationBar.vue b/src/components/NavigationBar.vue
index f1098fd7cf128a936363e451dcbb22cff84cf700..0e349e87a7359bec4ec5dd425bcda5a0f8bd1998 100644
--- a/src/components/NavigationBar.vue
+++ b/src/components/NavigationBar.vue
@@ -1,144 +1,261 @@
 <script setup>
-import { ref } from 'vue';
+import {computed, ref} from 'vue';
+import {useRouter} from 'vue-router';
+import {useStore} from 'vuex';
 
 const menuOpen = ref(false);
+const router = useRouter();
+const store = useStore();
 
+/**
+ * Toggles the menu
+ */
 const toggleMenu = () => {
   menuOpen.value = !menuOpen.value;
 };
-</script>
 
+/**
+ * Logs out the user
+ * @returns {Promise<void>} - A promise of void type
+ */
+const logout = async () => {
+  toggleMenu();
+  await store.dispatch('logout');
+  await router.push('/login');
+};
+
+/**
+ * Logs in the user
+ */
+const login = () => {
+  toggleMenu();
+  router.push('/login');
+};
+
+const menuItems = [
+  {
+    route: '/transactions',
+    imgSrc: require('@/assets/img/transaction.png'),
+    title: 'Transaksjoner',
+    description: 'Oversikt over transaksjoner'
+  },
+  {
+    route: '/budget',
+    imgSrc: require('@/assets/img/budget.png'),
+    title: 'Budsjett',
+    description: 'FÃ¥ personlige budsjett!'
+  },
+  {
+    route: '/goals',
+    imgSrc: require('@/assets/img/goalIcon.png'),
+    title: 'Sparemål',
+    description: 'Få sparemål eller utfordringer'
+  },
+  {
+    route: '/profile',
+    imgSrc: require('@/assets/img/profile.png'),
+    title: 'Profil',
+    description: 'Endre eller se profilinfo'
+  },
+  {route: '/news', imgSrc: require('@/assets/img/news.png'), title: 'Nyheter', subtitle: 'Dagens nyheter fra dn.no'},
+  {
+    route: '/contact',
+    imgSrc: require('@/assets/img/contact.png'),
+    title: 'Kontakt oss!',
+    description: 'Send feedback eller spørsmål'
+  },
+];
+
+const isAuthenticated = computed(() => store.state.user.firstName);
+
+
+</script>
 
 <template>
-  <nav class="nav-container">
-    <div class="menu-icon" @click="toggleMenu">
-      <img src="@/assets/img/burgermenu.png" alt="Menu">
+  <nav class="nav-container" data-cy="navigation-component">
+    <div class="menu-icon" @click="toggleMenu" @keydown.enter="toggleMenu" tabindex="0" role="button" data-cy="navigation-button">
+      <img v-if="!menuOpen" src="@/assets/img/menu.png" alt="Menu">
+      <img v-else src="@/assets/img/circular_x.png" alt="Close menu" class="close-image"/>
     </div>
-    <div :class="{ 'menu-content': true, active: menuOpen }">
-      <div class="close-icon" @click="toggleMenu">
-        ✖
-      </div>
-      <router-link to="/" class="menu-item">
-        <img src="@/assets/img/logo.png" alt="Home" />
-      </router-link>
-      <span class="menu-item text">Sparesti</span>
-      <router-link to="/transactions" class="menu-item">
-        <h2>Transaksjoner</h2>
-      </router-link>
-      <router-link to="/budget" class="menu-item">
-        <h2>Budsjett</h2>
+    <div v-if="menuOpen" class="menu-content" data-cy="navigation-menu">
+      <router-link to="/" class="menu-item" @click="toggleMenu">
+        <div class="home-icon">
+          <img src="@/assets/img/pigrich.png" alt="Home"/>
+        </div>
       </router-link>
-      <router-link to="/goals" class="menu-item">
-        <h2>Sparemål</h2>
-      </router-link>
-      <router-link to="/profile" class="menu-item">
-        <h2>Profil</h2>
-      </router-link>
-      <router-link to="/news" class="menu-item">
-        <h2>Nyheter</h2>
-      </router-link>
-      <button class="menu-item" id="rounded-button">Log out</button>
+      <div class="route-container">
+        <router-link v-for="item in menuItems"
+                     :key="item.route"
+                     :to="item.route"
+                     class="menu-item"
+                     @click="toggleMenu"
+        >
+          <img
+              :src="item.imgSrc"
+              :alt="item.title"
+          />
+          <div class="router-item-text"
+               role="button"
+               tabindex="0"
+          >
+            <h2>{{ item.title }}</h2>
+            <h3>{{ item.description }}</h3>
+          </div>
+        </router-link>
+      </div>
+      <button @click="logout" v-if="isAuthenticated">Logg ut</button>
+      <button @click="login" v-else>Logg inn</button>
     </div>
   </nav>
 </template>
 <style scoped>
+
+@media (max-width: 900px) {
+  .menu-content {
+    width: 100% !important;
+  }
+}
+
+/*the linter is not able to find the usage of this class, but it is used for the currently active route*/
+.router-link-active {
+  h2 {
+    text-decoration: underline;
+  }
+}
+
+.close-image {
+  filter: invert(1);
+}
+
 .nav-container {
-  position: relative;
-  width: 100%;
-  height: 100%;
+  position: fixed;
+  top: 10px;
+  left: 10px;
+  display: flex;
+  flex-direction: column;
+  z-index: 60;
 }
 
 .menu-icon {
-  position: absolute;
-  top: 0;
-  left: 0;
-  z-index: 20;
+  z-index: 35;
+
+  :hover {
+    transform: scale(1.1);
+  }
+
+  width: 50px;
 }
 
 .menu-icon img {
+  width: inherit;
   cursor: pointer;
-  width: 30px;
+  transition: transform 0.3s ease-in-out;
 }
 
 .menu-item img {
   cursor: pointer;
-  width: 45px;
+  width: 35px;
+  filter: drop-shadow(0px 0px 10px rgba(0, 0, 0, 0.5));
 }
 
-
 .menu-content {
   position: fixed;
   top: 0;
   left: 0;
-  width: 25%;
   height: 100vh;
-  background-color: #304C6C;
+  background: linear-gradient(var(--middle-color), var(--dark-color));
   display: flex;
   flex-direction: column;
-  align-items: flex-start;
-  padding-top: 60px;
+  align-items: center;
   transition: transform 0.3s ease-in-out;
-  transform: translateX(-100%);
   z-index: 30;
+  box-shadow: 8px 0 15px -3px rgba(0, 0, 0, 0.5);
 }
 
-.menu-content.active {
-  transform: translateX(0);
+.route-container {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+  text-decoration: none;
+  padding-left: 30px;
+  padding-right: 30px;
+  margin-bottom: 10px;
 }
 
-.close-icon {
-  position: absolute;
-  top: 10px;
-  right: 10px;
-  cursor: pointer;
-  font-size: 24px;
+a {
+  text-decoration: none;
+  color: white;
 }
 
 .menu-item {
   display: flex;
   align-items: center;
-  justify-content: center;
+  justify-content: start;
   width: 100%;
   margin: 5px 0;
+
 }
 
-.router-link-active {
-  text-decoration: underline;
+.menu-item:hover,
+.menu-item:focus {
+  .router-item-text h2 {
+    text-decoration: underline;
+  }
+
+  img {
+    scale: 1.1;
+    filter: drop-shadow(0px 0px 10px grey);
+  }
 }
 
-#rounded-button {
-  background-color: #0056b3;
-  color: white;
-  border: none;
-  width: 60%;
-  padding: 10px 20px;
-  font-size: 16px;
-  border-radius: 20px;
-  box-shadow: 0 4px #003875;
-  cursor: pointer;
-  transition: all 0.3s ease;
-  margin: 40px auto;
+.home-icon {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 100%;
+  height: auto;
+  margin-bottom: 20px;
+
+  img {
+    height: 160px;
+    width: auto;
+  }
 
+  padding-top: 40px;
 }
 
-#rounded-button:hover {
-  background-color: #023366;
-  box-shadow: 0 6px #003875;
+button {
+  color: black;
+  background-color: var(--light-color);
 }
 
-#rounded-button:active {
-  box-shadow: 0 2px #003875;
-  transform: translateY(2px);
+button:hover {
+  color: black;
+  background-color: var(--light-color);
+  border: 2px solid white;
 }
 
-.menu-content h2, p {
-  font-size: 19px;
-  color: #ffffff;   
+.router-item-text {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: start;
+  padding-left: 10px;
+}
+
+.menu-content h2, .menu-content h3 {
+  color: #ffffff;
+  text-decoration: none;
 }
 
 .menu-content h2 {
-  text-decoration: underline; 
+  font-size: 19px;
+  color: white;
 }
 
+.menu-content h3 {
+  font-size: 11px;
+  color: var(--accent-color-dark);
+}
 
 </style>
diff --git a/src/components/TokenChecker.vue b/src/components/TokenChecker.vue
index 23390cfa6d104ea05b0187b04d5037f620304bd2..184df3e00bea87d0ac632cf13b5ce46c15457bac 100644
--- a/src/components/TokenChecker.vue
+++ b/src/components/TokenChecker.vue
@@ -1,96 +1,94 @@
-<template>
-    <div v-if="tokenExpiresSoon" class="modal-overlay">
-      <div class="modal-content">
-        <p>Token expires in 1 minute. Please save your work.</p>
-        <button @click="closeModal">Close</button>
-      </div>
-    </div>
-  </template>
-  
-  <script setup>
-  import { ref, onMounted, onUnmounted } from 'vue';
-  import { jwtDecode } from 'jwt-decode';
-  import { useRouter } from 'vue-router';
-  
-  const tokenExpiresSoon = ref(false);
-  const router = useRouter();
-  
-  const checkTokenExpiration = () => {
-    const authToken = sessionStorage.getItem('authToken');
-    if (authToken) {
-      try {
-        const decoded = jwtDecode(authToken);
-        const currentTime = Date.now() / 1000;
-        const timeRemaining = decoded.exp - currentTime;
-        console.log(timeRemaining);
-        if (timeRemaining < 0) {
-          console.log('Token expired. Logging out.');
-          sessionStorage.removeItem('authToken');
-          router.push({ name: 'login' });
-        } else if (timeRemaining < 60) { 
-          tokenExpiresSoon.value = true;
-        } else {
-          tokenExpiresSoon.value = false;
-        }
-      } catch (error) {
-        console.error('Error decoding token:', error);
-        sessionStorage.removeItem('authToken');
-        router.push({ name: 'login' });
+<script setup>
+import {ref, onMounted, onUnmounted} from 'vue';
+import {jwtDecode} from 'jwt-decode';
+import {useRouter} from 'vue-router';
+import {useStore} from 'vuex';
+import UserService from '@/services/internal/UserService';
+
+const tokenExpiresSoon = ref(false);
+const router = useRouter();
+const store = useStore();
+
+/**
+ * Logs out the user
+ * @returns {Promise<void>} - A promise of void type
+ */
+const logout = async () => {
+  await store.dispatch('logout');
+  await router.push('/login');
+};
+
+const checkTokenExpiration = () => {
+  const authToken = sessionStorage.getItem('authToken');
+  if (authToken) {
+    try {
+      const decoded = jwtDecode(authToken);
+      const currentTime = Date.now() / 1000;
+      const timeRemaining = decoded.exp - currentTime;
+      if (timeRemaining < 60) {
+        UserService.setAxiosAuthHeader(null);
+        logout();
+      } else if (timeRemaining < 120) {
+        tokenExpiresSoon.value = true;
+      } else {
+        tokenExpiresSoon.value = false;
       }
+    } catch (error) {
+      UserService.setAxiosAuthHeader(null);
+      logout();
     }
-  };
-  
-  const closeModal = () => {
-    tokenExpiresSoon.value = false;
-  };
-  
-  let intervalId;
-  
-  onMounted(() => {
-    checkTokenExpiration();
-    intervalId = setInterval(checkTokenExpiration, 60000);
-  });
-  
-  onUnmounted(() => {
-    clearInterval(intervalId);
-  });
-  </script>
-  
-  <style scoped>
-  .modal-overlay {
-    position: fixed;
-    top: 0;
-    left: 0;
-    width: 100%;
-    height: 100%;
-    background-color: rgba(0, 0, 0, 0.5);
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    z-index: 1000;
-  }
-  
-  .modal-content {
-    background-color: #fff;
-    padding: 20px;
-    border-radius: 5px;
-    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
-    text-align: center;
-  }
-  
-  button {
-    margin-top: 20px;
-    padding: 10px 20px;
-    font-size: 16px;
-    color: #fff;
-    background-color: #007bff;
-    border: none;
-    border-radius: 5px;
-    cursor: pointer;
   }
-  
-  button:hover {
-    background-color: #0056b3;
-  }
-  </style>
+};
+
+const closeModal = () => {
+  tokenExpiresSoon.value = false;
+};
+
+let intervalId;
+
+onMounted(() => {
+  checkTokenExpiration();
+  intervalId = setInterval(checkTokenExpiration, 60000);
+});
+
+onUnmounted(() => {
+  clearInterval(intervalId);
+});
+</script>
+
+<template>
+  <div v-if="tokenExpiresSoon" class="modal-overlay">
+    <div class="modal-content">
+      <p>Token utløper om 1 minutt. Vennligst lagre arbeidet ditt.</p>
+      <button @click="closeModal">Lukk</button>
+    </div>
+  </div>
+</template>
+
+
+<style scoped>
+button {
+  margin-top: 5px;
+}
+.modal-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.5);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  z-index: 1000;
+}
+
+.modal-content {
+  background-color: var(--accent-color);
+  padding: 20px;
+  border-radius: var(--border-radius-general);
+  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+  text-align: center;
+}
+</style>
   
\ No newline at end of file
diff --git a/src/components/budget/BudgetEditComponent.vue b/src/components/budget/BudgetEditComponent.vue
new file mode 100644
index 0000000000000000000000000000000000000000..89221f398246ebba6f8eea4a73600ceea35e156e
--- /dev/null
+++ b/src/components/budget/BudgetEditComponent.vue
@@ -0,0 +1,239 @@
+<script setup>
+import {ref, watch, defineProps, defineEmits} from 'vue';
+import BankStatementService from '@/services/internal/BankStatementService';
+import axios from "axios";
+
+const props = defineProps({
+  selectedAnalysis: Object
+});
+
+const emits = defineEmits(['update', 'close']);
+
+const errorMessage = ref('');
+const newExpectedValues = ref([]);
+const minValue = 0;
+const maxValue = 2000000000;
+const categoryMap = {
+  "01": {name: "Mat", englishName: "FOOD"},
+  "02": {name: "Alkohol og Tobakk", englishName: "ALCOHOL_AND_TOBACCO"},
+  "03": {name: "Klær og Sko", englishName: "CLOTHING_AND_SHOES"},
+  "04": {name: "Bolig og Elektrisitet", englishName: "HOUSING_AND_ELECTRICITY"},
+  "05": {name: "Møbler", englishName: "FURNITURE"},
+  "06": {name: "Helse", englishName: "HEALTH"},
+  "07": {name: "Transport", englishName: "TRANSPORT"},
+  "08": {name: "Kommunikasjon", englishName: "COMMUNICATION"},
+  "09": {name: "Fritid, Sport og Kultur", englishName: "LEISURE_SPORT_AND_CULTURE"},
+  "10": {name: "Utdanning", englishName: "EDUCATION"},
+  "11": {name: "Spise Ute", englishName: "EATING_OUT"},
+  "12": {name: "Forsikring", englishName: "INSURANCE"},
+  "13": {name: "Annet", englishName: "OTHER"}
+};
+
+/**
+ * Constructs the analysis DTO to be sent to the backend.
+ */
+const constructAnalysisDto = () => {
+  return {
+    id: props.selectedAnalysis.id,
+    analysisItems: newExpectedValues.value.map(item => ({
+      id: item.id,
+      category: item.englishName,
+      expectedValue: item.newExpectedValue,
+      actualValue: item.actualValue
+    }))
+  };
+};
+
+/**
+ * Watches for changes in the selectedAnalysis prop and updates the newExpectedValues ref accordingly.
+ * @param {Array} newVal - The new value of the selectedAnalysis prop.
+ */
+watch(() => props.selectedAnalysis?.analysisItems, (newVal) => {
+  if (Array.isArray(newVal)) {
+    newExpectedValues.value = newVal
+        .filter(item => item.category !== "00")
+        .map(item => ({
+          ...item,
+          category: categoryMap[item.category]?.name || "Ukjent Kategori",
+          englishName: categoryMap[item.category]?.englishName,
+          newExpectedValue: item.expectedValue
+        }));
+  }
+}, {immediate: true});
+
+/**
+ * Validates the new expected values.
+ */
+const validateValues = () => {
+  let isValid = true;
+  errorMessage.value = '';
+
+  for (let item of newExpectedValues.value) {
+    if (item.newExpectedValue > maxValue) {
+      errorMessage.value = `Verdien kan ikke være mer enn ${maxValue.toLocaleString('no-NO')} kr.`;
+      isValid = false;
+    } else if (item.newExpectedValue < minValue) {
+      errorMessage.value = `Verdien kan ikke være negativ.`;
+      isValid = false;
+    }
+
+    if (!isValid) {
+      return false;
+    }
+  }
+  return true;
+};
+
+/**
+ * Saves the new expected values to the backend.
+ */
+const saveNewValues = async () => {
+  if (!validateValues()) {
+    return;
+  }
+  const bankStatementAnalysisDTO = constructAnalysisDto();
+  try {
+    await BankStatementService.updateAnalysis(bankStatementAnalysisDTO);
+    emits('update');
+    errorMessage.value = '';
+  } catch (error) {
+    if (axios.isAxiosError(error) && error.response) {
+      errorMessage.value = 'Kunne ikke lagre endringene. Prøv igjen senere';
+    } else {
+      console.error('Cannot connect to server.', error);
+      errorMessage.value = 'Kan ikke koble til serveren. Vennligst prøv igjen senere.';
+    }
+  }
+};
+
+/**
+ * Closes the edit budget component.
+ */
+const closeEdit = () => {
+  emits('close');
+};
+
+/**
+ * Formats a number to a currency string.
+ * @param {*} value
+ */
+const formatCurrency = (value) => {
+  return `${value.toLocaleString('no-NO')} kr`;
+};
+</script>
+
+<template>
+  <div class="edit-budget-container">
+    <h1>Rediger Budsjett</h1>
+    <div v-if="errorMessage" class="error" data-cy="error-message">{{ errorMessage }}</div>
+    <div class="scrollable-container">
+      <table class="table-container">
+        <thead>
+        <tr>
+          <th>Kategori</th>
+          <th>Forventet verdi</th>
+          <th>Ny forventet verdi</th>
+        </tr>
+        </thead>
+        <tbody>
+        <tr v-for="(item, index) in newExpectedValues" :key="index">
+          <td>{{ item.category }}</td>
+          <td>{{ formatCurrency(item.expectedValue) }}</td>
+          <td>
+            <input type="number" v-model="item.newExpectedValue" :max="maxValue" :min="minValue" data-cy="number-input">
+          </td>
+        </tr>
+        </tbody>
+      </table>
+    </div>
+    <div class="button-content">
+      <button @click="saveNewValues" data-cy="save-button">Lagre</button>
+      <button @click="closeEdit" data-cy="close-button">Avbryt</button>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.edit-budget-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  text-align: center;
+}
+
+.button-content {
+  margin-top: 10px;
+  display: flex;
+  flex-direction: row;
+  justify-content: space-evenly;
+  align-self: center;
+  width: 100%;
+}
+
+.scrollable-container {
+  overflow-y: auto;
+  max-height: 80vh;
+  width: 100%;
+  margin-top: 20px;
+}
+
+.table-container {
+  width: 100%;
+  margin-top: 0;
+  border-collapse: collapse;
+  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
+}
+
+th, td {
+  padding: 12px 15px;
+  text-align: left;
+  border-bottom: 1px solid var(--accent-color);
+}
+
+input {
+  width: 100%;
+}
+
+th {
+  background-color: var(--middle-color);
+  color: white;
+  cursor: pointer;
+}
+
+th:hover {
+  background-color: var(--dark-color);
+}
+
+tbody tr:hover {
+  background-color: var(--accent-color);
+}
+
+.table-container th {
+  background-color: var(--dark-color);
+  color: white;
+  position: sticky;
+  top: 0;
+  z-index: 10;
+}
+
+.table-container td:last-child {
+  display: flex;
+    justify-content: center;
+}
+
+.error {
+  color: var(--error-text);
+  padding: 10px;
+}
+
+button {
+  min-width: 100px;
+}
+@media (max-width: 480px) {
+  .table-container th:nth-child(2), /* Forventet verdi kolonne header */
+  .table-container td:nth-child(2) { /* Forventet verdi kolonne data */
+    display: none;
+  }
+
+}
+</style>
diff --git a/src/components/budget/BudgetMain.vue b/src/components/budget/BudgetMain.vue
index 46dd2ee5285276dc1e845bcfd64ffdbcd8961ae5..844e4b41f516443f5748080808a3241775aa49c6 100644
--- a/src/components/budget/BudgetMain.vue
+++ b/src/components/budget/BudgetMain.vue
@@ -1,9 +1,402 @@
 <script setup>
+import {ref, watchEffect, computed, onMounted} from 'vue';
+import SpentChart from './SpentChart.vue';
+import MonthlySpendingChart from './MonthlySpendingChart.vue';
+import BankStatementService from '@/services/internal/BankStatementService';
+import TransactionService from '@/services/internal/TransactionService';
+import BudgetEditComponent from './BudgetEditComponent.vue';
+import router from "@/router";
 
+const currentDate = new Date();
+currentDate.setMonth(currentDate.getMonth() - 1);
+
+const currentMonthIndex = currentDate.getMonth();
+const currentYear = currentDate.getFullYear();
+const monthNames = ['Januar', 'Februar', 'Mars', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Desember'];
+
+const selectedMonthYear = ref(`${currentYear}-${String(currentMonthIndex + 1).padStart(2, '0')}-01`);
+const selectedAccountNumber = ref('');
+const selectedAnalysis = ref([]);
+
+const spent = ref(0);
+const budget = ref(0);
+const accountNumbers = ref([]);
+const financialSummary = ref([]);
+const monthlyBudgetData = ref([]);
+const hasData = computed(() => spent.value !== 0 || budget.value !== 0);
+const isEditOpen = ref(false);
+const errorMessage = ref('');
+
+const categoryMap = {
+  "01": "Mat",
+  "02": "Alkohol og Tobakk",
+  "03": "Klær og Sko",
+  "04": "Bolig og Elektrisitet",
+  "05": "Møbler",
+  "06": "Helse",
+  "07": "Transport",
+  "08": "Kommunikasjon",
+  "09": "Fritid, Sport og Kultur",
+  "10": "Utdanning",
+  "11": "Spise Ute",
+  "12": "Forsikring",
+  "13": "Annet"
+};
+
+/**
+ * Fetches the account numbers from the backend and sets the first account number as the selected account number.
+ */
+const fetchAccountNumbers = async () => {
+  try {
+    const accounts = await TransactionService.getAccountNumbers();
+    accountNumbers.value = accounts;
+    if (accounts.length > 0) {
+      selectedAccountNumber.value = accounts[0];
+    }
+  } catch (error) {
+    errorMessage.value = 'Kunne ikke hente kontoer';
+  }
+};
+
+/**
+ * Fetches the bank statements for the selected month and year.
+ * @param {number} month - The month number.
+ * @param {number} year - The year number.
+ * @returns {Promise} - The bank statements for the selected month and year.
+ */
+const fetchBankStatementsForMonth = async (month, year) => {
+  return await BankStatementService.retrieveBankStatementsWithMY(month, year);
+};
+
+/**
+ * Analyzes the bank statements and returns the analysis items.
+ * @param statements - The bank statements to analyze.
+ * @returns {Promise} - An array of all analysis items (excluding those in category "00") from all provided statements.
+ */
+const analyzeStatement = async (statements) => {
+  let allAnalysisItems = [];
+  for (let statement of statements) {
+    const analysis = await BankStatementService.analyzeBankStatement(statement.id);
+    selectedAnalysis.value = analysis;
+    allAnalysisItems.push(...analysis.analysisItems);
+  }
+  if (allAnalysisItems.length === 0) {
+    selectedAnalysis.value = null;
+  }
+  return allAnalysisItems.filter(item => item.category !== "00");
+};
+
+/**
+ * Analyzes the bank statements for the monthly budget and returns the analysis items.
+ * @param statements - The bank statements to analyze.
+ * @returns {Promise<Array>} - An array of all analysis items (excluding those in category "00") from all provided statements.
+ */
+const analyzeStatementForMonthlyBudget = async (statements) => {
+  let allAnalysisItems = [];
+  for (let statement of statements) {
+    const analysis = await BankStatementService.analyzeBankStatement(statement.id);
+    allAnalysisItems.push(...analysis.analysisItems);
+  }
+  return allAnalysisItems.filter(item => item.category !== "00");
+};
+
+/**
+ * Computes the financial summary based on the analysis items.
+ * The actual value and expected value for each category is summed up, and the total difference is calculated.
+ * @param {Array} analysisItems - The analysis items to compute the financial summary from.
+ * @returns {void}
+ */
+const computeFinancialSummary = (analysisItems) => {
+  let totalExpectedValue = 0;
+  let totalActualValue = 0;
+  const categoryTotals = analysisItems.reduce((acc, item) => {
+    const categoryName = getCategoryName(item.category);
+    if (!acc[categoryName]) {
+      acc[categoryName] = {id: item.id, category: categoryName, expectedValue: 0, actualValue: 0};
+    }
+    acc[categoryName].expectedValue += item.expectedValue;
+    acc[categoryName].actualValue += item.actualValue;
+    totalExpectedValue += item.expectedValue;
+    totalActualValue += item.actualValue;
+    return acc;
+  }, {});
+
+  budget.value = totalExpectedValue;
+  spent.value = totalActualValue;
+
+  financialSummary.value = [
+    ...Object.values(categoryTotals),
+    {
+      id: 'total',
+      category: 'Totalt',
+      expectedValue: totalExpectedValue,
+      actualValue: totalActualValue,
+      difference: totalActualValue - totalExpectedValue
+    }
+  ];
+};
+
+/**
+ * Fetches the financial data for the selected account number, month and year.
+ * The bank statements are fetched, then filtered by the selected account number and analyzed to compute the financial summary.
+ * @returns {void}
+ */
+const fetchFinancialData = async () => {
+  const [year, month] = selectedMonthYear.value.split('-');
+  try {
+    const statements = await fetchBankStatementsForMonth(parseInt(month), parseInt(year));
+    const filteredStatement = statements.filter(statement => statement.accountNumber === selectedAccountNumber.value);
+    const analysisItems = await analyzeStatement(filteredStatement);
+    computeFinancialSummary(analysisItems);
+  } catch (error) {
+    errorMessage.value = 'Kunne ikke hente budsjett';
+  }
+};
+
+/**
+ * Fetches the monthly budget data for the selected account number.
+ * The bank statements are fetched for each month of the year, then filtered by the selected account number and analyzed to compute the total spent.
+ * @returns {month: string, spent: number}[] - An array of objects containing the month name and the total spent for that month.
+ */
+const fetchMonthlyBudgetData = async () => {
+  try {
+    const yearNumber = parseInt(selectedMonthYear.value.split('-')[0]);
+    const allMonths = Array.from({length: 12}, (_, i) => i + 1);
+
+    monthlyBudgetData.value = await Promise.all(allMonths.map(async (monthNumber) => {
+      const statements = await fetchBankStatementsForMonth(monthNumber, yearNumber);
+      const filteredStatements = statements.filter(statement => statement.accountNumber === selectedAccountNumber.value);
+      const analysisItems = await analyzeStatementForMonthlyBudget(filteredStatements);
+      const totalSpent = analysisItems.reduce((acc, item) => acc + item.actualValue, 0);
+      return {month: monthNames[monthNumber - 1], spent: totalSpent};
+    }));
+  } catch (err) {
+    errorMessage.value = 'Kunne ikke hente månedlig budsjett data';
+  }
+};
+
+/**
+ * Returns the category name for the provided category code.
+ * @param {string} code - The category code.
+ */
+const getCategoryName = (code) => categoryMap[code] || "Ukjent Kategori";
+
+/**
+ * Formats the provided value as a currency string.
+ * @param {number} value - The value to format.
+ */
+const formatCurrency = (value) => `${value.toLocaleString('no-NO')} kr`;
+
+/**
+ * Toggles the edit mode for the budget.
+ */
+const toggleEdit = () => {
+  isEditOpen.value = !isEditOpen.value;
+};
+
+/**
+ * Handles the update event from the BudgetEditComponent.
+ * Fetches the financial data after the edit is closed.
+ * @returns {void}
+ */
+const handleUpdate = () => {
+  isEditOpen.value = false;
+  fetchFinancialData();
+};
+
+/**
+ * Watches the selected account number and fetches the financial data and monthly budget data when the account number changes.
+ */
+watchEffect(() => {
+  if (selectedAccountNumber.value) {
+    fetchFinancialData();
+    fetchMonthlyBudgetData();
+  }
+});
+
+/**
+ * Fetches the account numbers when the component is mounted.
+ */
+onMounted(() => {
+  fetchAccountNumbers();
+});
+
+/**
+ * Routes to the BankStatements component in the profile page.
+ */
+function addNewBankStatement() {
+  router.push({name: "profile", params: {component: "BankStatements"}});
+}
 </script>
 
 <template>
-  <div>
-    <p>Budget</p>
+  <div v-if="!isEditOpen" class="budget-main-container" data-cy="budget-component">
+    <div class="title">
+      <h1>Budsjett</h1>
+    </div>
+    <div class="selectors">
+      <div class="account-selector">
+        <label for="account-dropdown">Velg konto:</label>
+        <select class="dropdown" id="account-dropdown" v-model="selectedAccountNumber" data-cy="account-dropdown">
+          <option v-for="account in accountNumbers" :key="account" :value="account">{{ account }}</option>
+        </select>
+      </div>
+      <div class="year-month-selector">
+        <label for="selected-month-year">Velg måned og år:</label>
+        <input type="date" placeholder="Velg måned" v-model="selectedMonthYear" id="selected-month-year"
+               class="date-picker dropdown" data-cy="date-picker">
+      </div>
+    </div>
+    <div>
+      <div v-if="hasData">
+        <spent-chart :budget="budget" :spent="spent" data-cy="spent-chart"/>
+      </div>
+      <div v-else>
+        <p>Du har ikke lagt inn kontoutskrift for denne måneden enda</p>
+        <button @click="addNewBankStatement">Legg til</button>
+      </div>
+    </div>
+    <div class="income-section" data-cy="income-section">
+      <h2>Totalt budsjett</h2>
+      <p><strong>Denne måneden:</strong> {{ formatCurrency(budget) }}</p>
+    </div>
+    <div class="expenses-section" data-cy="expenses-section">
+      <div class="top-section">
+        <h2>Utgifter</h2>
+        <button v-if="hasData" @click="toggleEdit" data-cy="edit-button">Rediger budsjett</button>
+      </div>
+      <table>
+        <thead>
+        <tr>
+          <th>Kategori</th>
+          <th>Forventet</th>
+          <th>Faktisk</th>
+          <th>Differanse</th>
+        </tr>
+        </thead>
+        <tbody>
+        <tr v-for="item in financialSummary" :key="item.id" :class="{ 'total-row': item.id === 'total' }">
+          <td>{{ item.category }}</td>
+          <td>{{ formatCurrency(item.expectedValue) }}</td>
+          <td>{{ formatCurrency(item.actualValue) }}</td>
+          <td :class="{
+              'positive-difference': item.expectedValue - item.actualValue >= 0,
+              'negative-difference': item.expectedValue - item.actualValue < 0
+            }">
+            {{ formatCurrency(item.expectedValue - item.actualValue) }}
+          </td>
+        </tr>
+        </tbody>
+      </table>
+      <div v-if="errorMessage" class="error" data-cy="error-message">{{ errorMessage }}</div>
+    </div>
+    <div>
+      <monthly-spending-chart :budget-data="monthlyBudgetData" data-cy="monthly-spending-chart"/>
+    </div>
   </div>
+  <budget-edit-component v-else :selected-analysis="selectedAnalysis" @update="handleUpdate" @close="toggleEdit"/>
 </template>
+
+<style scoped>
+.title {
+  padding: 20px 0 20px 0;
+}
+
+.budget-main-container {
+  display: flex;
+  flex-direction: column;
+  text-align: center;
+  box-sizing: border-box;
+  margin-bottom: 20px;
+}
+
+table {
+  width: 100%;
+  border-collapse: collapse;
+}
+
+th,
+td {
+  border: 1px solid #ddd;
+  padding: 8px;
+}
+
+th {
+  background-color: #f0f0f0;
+}
+
+.income-section,
+.expenses-section {
+  text-align: left;
+  margin-top: 1rem;
+  margin-bottom: 20px;
+}
+
+.selectors {
+  display: flex;
+  align-self: center;
+  gap: 1rem;
+  margin-bottom: 1rem;
+  width: 100%;
+}
+
+.account-selector,
+.year-month-selector {
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+}
+
+.dropdown {
+  width: 100%;
+}
+
+.input .year-month-selector {
+  width: 100%;
+}
+
+.top-section {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 10px;
+}
+
+.positive-difference {
+  color: green;
+}
+
+.negative-difference {
+  color: var(--error-text);
+}
+
+.total-row {
+  font-weight: bold;
+}
+
+.error {
+  color: var(--error-text);
+  padding: 10px;
+}
+
+@media (max-width: 480px) {
+  th:nth-child(4),
+  td:nth-child(4) {
+    display: none;
+  }
+
+  .top-section {
+    display: none;
+  }
+
+  .income-section {
+    display: none;
+  }
+}
+
+@media (max-width: 400px) {
+  .chart-container {
+    display: none;
+  }
+}
+</style>
\ No newline at end of file
diff --git a/src/components/budget/MonthlySpendingChart.vue b/src/components/budget/MonthlySpendingChart.vue
new file mode 100644
index 0000000000000000000000000000000000000000..52418c5225e155247e4ae70d26b2657136f8e984
--- /dev/null
+++ b/src/components/budget/MonthlySpendingChart.vue
@@ -0,0 +1,110 @@
+<script setup>
+import { ref, onMounted, onUnmounted, defineProps, watch } from 'vue';
+import Chart from 'chart.js';
+
+const props = defineProps({
+  budgetData: Array
+});
+
+const chartContainer = ref(null);
+let myChart = null;
+
+/**
+ * Initializes the chart with the given data.
+ * @param {Array} filteredData - The data to initialize the chart with.
+ */
+const initializeChart = (filteredData) => {
+  const ctx = chartContainer.value.getContext('2d');
+  myChart = new Chart(ctx, {
+    type: 'line',
+    data: {
+      labels: filteredData.map(data => data.month),
+      datasets: [{
+        label: 'MÃ¥nedsforbruk',
+        data: filteredData.map(data => data.spent),
+        fill: false,
+        borderColor: '#304C6C',
+        tension: 0.1
+      }]
+    },
+    options: {
+      scales: {
+        yAxes: [{
+          ticks: {
+            beginAtZero: true
+          },
+          scaleLabel: {
+            display: true,
+            labelString: 'Forbruk (kr)',
+            fontSize: 16,
+            fontColor: '#304C6C'
+          }
+        }],
+        xAxes: [{
+          scaleLabel: {
+            display: true,
+            labelString: 'MÃ¥ned',
+            fontSize: 16,
+            fontColor: '#304C6C'
+          }
+        }]
+      },
+      legend: {
+        labels: {
+          fontSize: 16
+        }
+      }
+    }
+  });
+};
+
+/**
+ * Lifecycle hook that initializes the chart when the component is mounted.
+ * Filters out months with a spent value of 0.
+ */
+onMounted(() => {
+  const filteredData = props.budgetData.filter(data => data.spent > 0);
+  if (chartContainer.value) {
+    initializeChart(filteredData);
+  }
+});
+
+/**
+ * Lifecycle hook that updates the chart when the budget data changes.
+ * Filters out months with a spent value of 0.
+ * @param {Array} newData - The new budget data.
+ */
+watch(() => props.budgetData, (newData) => {
+  if (myChart) {
+    const filteredData = newData.filter(data => data.spent > 0);
+    myChart.data.labels = filteredData.map(data => data.month);
+    myChart.data.datasets[0].data = filteredData.map(data => data.spent);
+    myChart.update();
+  }
+}, { immediate: true });
+
+/**
+ * Lifecycle hook that destroys the chart when the component is unmounted.
+ */
+onUnmounted(() => {
+  if (myChart) {
+    myChart.destroy();
+    myChart = null;
+  }
+});
+</script>
+
+<template>
+  <div class="chart-container">
+    <canvas ref="chartContainer"></canvas>
+  </div>
+</template>
+
+<style scoped>
+.chart-container {
+  width: 100%;
+  height: auto;
+  padding-top: 30px;
+  margin-bottom: 20px;
+}
+</style>
diff --git a/src/components/budget/SpentChart.vue b/src/components/budget/SpentChart.vue
new file mode 100644
index 0000000000000000000000000000000000000000..f490512451e7cec1dee61e16f4a0129ff88748ca
--- /dev/null
+++ b/src/components/budget/SpentChart.vue
@@ -0,0 +1,104 @@
+<script setup>
+import {ref, onMounted, onUnmounted, watch, defineProps, computed} from 'vue';
+import Chart from 'chart.js';
+
+const props = defineProps({
+  spent: Number,
+  budget: Number
+});
+
+const chartRef = ref(null);
+let myChart = null;
+
+/**
+ * Calculates the remaining budget by subtracting the spent amount from the budget.
+ */
+const remaining = computed(() => props.budget - props.spent);
+
+/**
+ * Returns a message based on the remaining budget.
+ */
+const remainingMessage = computed(() => {
+  return remaining.value >= 0
+      ? `Du hadde ${remaining.value.toLocaleString('no-NO')} kr igjen.`
+      : `Du var <strong>${(-remaining.value).toLocaleString('no-NO')} kr</strong> over budsjett.`;
+
+});
+
+/**
+ * Creates the chart with the given data.
+ */
+const createChart = () => {
+  const chartContext = chartRef.value.getContext('2d');
+  myChart = new Chart(chartContext, {
+    type: 'doughnut',
+    data: {
+      labels: ['Brukt', 'Gjenstående'],
+      datasets: [{
+        backgroundColor: ['#304C6C', '#1fd655'],
+        data: [props.spent, Math.max(props.budget - props.spent, 0)]
+      }]
+    },
+    options: {
+      responsive: true,
+      maintainAspectRatio: false,
+      legend: {
+        display: true,
+        position: 'bottom'
+      }
+    }
+  });
+};
+
+/**
+ * Initializes the chart when the component is mounted.
+ */
+onMounted(() => {
+  if (chartRef.value) {
+    createChart();
+  } else {
+    console.error("Chart reference is not available.");
+  }
+});
+
+/**
+ * Updates the chart with the new data.
+ */
+const updateChart = () => {
+  if (myChart) {
+    myChart.data.datasets[0].data = [props.spent, Math.max(props.budget - props.spent, 0)];
+    myChart.update();
+  }
+};
+
+/**
+ * Watches the spent and budget props and updates the chart when they change.
+ */
+watch([() => props.spent, () => props.budget], () => {
+  updateChart();
+}, {immediate: true});
+
+/**
+ * Destroys the chart when the component is unmounted.
+ */
+onUnmounted(() => {
+  if (myChart) {
+    myChart.destroy();
+  }
+});
+</script>
+
+<template>
+  <div class="chart-container">
+    <p v-html="remainingMessage"></p>
+    <canvas ref="chartRef"></canvas>
+  </div>
+</template>
+
+<style scoped>
+.chart-container {
+  width: 100%;
+  height: 30vh;
+  padding: 1rem 0;
+}
+</style>
diff --git a/src/components/challenge/challengeComponent.vue b/src/components/challenge/challengeComponent.vue
deleted file mode 100644
index 380e056cf9fe8385a3e6e1b03c32b85deec6502b..0000000000000000000000000000000000000000
--- a/src/components/challenge/challengeComponent.vue
+++ /dev/null
@@ -1,100 +0,0 @@
-<script setup>
-//todo gjør underovserskriftene bold
-//todo legg til hjerter til grisene
-
-</script>
-
-<template>
-  <div class="challenge-container">
-    <h1>Challenges</h1>
-
-    <div class="current-challenge">
-      <h2>Current</h2>
-
-      <div class="challenge">
-        <p>Shoppestopp</p>
-        <p>Ikke kjøp noe annet enn matvarer i 20 dager!</p>
-        <div class="progress">Status : 30/30</div>
-        <div>Hjerte rosa</div>
-        <div>Hjerte rosa</div>
-        <div>Hjerte grå</div>
-      </div>
-    </div>
-
-    <div class="previous-challenges">
-      <h2>Previous</h2>
-
-      <div class="challenge">
-        <p>Alkohol stopp</p>
-        <p>Ikke kjøp alkohol på en uke!</p>
-        <span class="progress">Status : 7/7, </span>
-        <span class="progress">Completed  </span>
-        <span class="indicator completed"></span>
-      </div>
-
-      <div class="challenge">
-        <p>Mesterkokken</p>
-        <p>Lag all mat den neste måneden hjemme! Ikke kjøp mat ute, spis på restauranter eller bestill takeaway.</p>
-        <span class="progress">Status : 30/30, </span>
-        <span class="progress">Completed  </span>
-        <span class="indicator completed"></span>
-      </div>
-
-      <div class="challenge">
-        <p>Shoppestopp</p>
-        <p>Ikke kjøp noe annet enn matvarer i 20 dager!</p>
-        <span class="progress">Status : 11/20, </span>
-        <span class="progress">Not completed  </span>
-        <span class="indicator not-completed"></span>
-      </div>
-    </div>
-
-  </div>
-</template>
-
-<style scoped>
-.challenge-container {
-  width: 100%;
-  max-height: 100vh;
-  overflow-y: auto;
-  padding: 5% 5% 20px 5%;
-  box-sizing: border-box;
-  scrollbar-width: none; /* Firefox */
-}
-
-.challenge-container::-webkit-scrollbar {
-  display: none;
-}
-
-.challenge {
-  border: 1px solid var(--dark-color);
-  border-radius: 10px;
-  margin-bottom: 20px;
-  padding: 10px;
-  text-align: center;
-}
-
-.indicator {
-  height: 20px;
-  width: 20px;
-  border-radius: 50%;
-  display: inline-block;
-}
-
-.completed {
-  background-color: green;
-}
-
-.not-completed {
-  background-color: red;
-}
-
-p {
-  display: block;
-  color: black;
-  font-size: 1rem;
-  word-wrap: break-word;
-  overflow-wrap: break-word;
-  word-break: normal;
-}
-</style>
diff --git a/src/components/contact/ContactComponent.vue b/src/components/contact/ContactComponent.vue
new file mode 100644
index 0000000000000000000000000000000000000000..dc51d0cac8f4d79f5c9c9be9ae2f93d32e9ea906
--- /dev/null
+++ b/src/components/contact/ContactComponent.vue
@@ -0,0 +1,258 @@
+<script setup>
+import {ref, watch, computed} from 'vue';
+import EmailService from "@/services/internal/EmailService";
+import UserDetailsValidationService from "@/services/internal/UserDetailsInputService";
+import axios from "axios";
+
+const status = ref("");
+let selectedOptionSubject = ref('');
+let name = ref('');
+let email = ref('');
+let feedback = ref('');
+const options = ref([
+  {text: 'Feil og problemer', value: '1'},
+  {text: 'Tilbakemelding', value: '2'},
+  {text: 'Kundeservice', value: '3'},
+  {text: 'Annet', value: '4'}
+]);
+
+
+const isFormValid = computed(() => {
+  return name.value.trim() && email.value.trim() && feedback.value.trim() && selectedOptionSubject.value.trim();
+});
+
+/**
+ * Watcher for changes in the selected subject option.
+ */
+watch(selectedOptionSubject, (newVal, oldVal) => {
+  console.log(`changed from ${oldVal} to ${newVal}`);
+});
+
+/**
+ * Submits the form data.
+ */
+async function submitForm() {
+
+  if (!checkValidFormData()) {
+    return
+  }
+
+  try {
+    const messageBody = feedback.value + "\n\n" + "Name: " + name.value + '\n' + "Email: " + email.value
+    const message = {
+      recipient: "", subject: selectedOptionSubject.value, body: messageBody
+    };
+    status.value = "Sender melding...";
+    await EmailService.sendContactMessage(message)
+    status.value = "Meldingen ble sendt!";
+  } catch (error) {
+    if (axios.isAxiosError(error) && error.response) {
+      switch (error.response.status) {
+        case 500:
+          status.value = "Intern serverfeil. Vennligst prøv igjen senere.";
+          break;
+        default:
+          status.value = error.response.data.errorMessage;
+          break;
+      }
+    } else {
+      console.error('Cannot connect to server.', error);
+      status.value = 'Kan ikke koble til serveren. Vennligst prøv igjen senere.';
+    }
+  }
+
+}
+
+/**
+ * Validates the form data.
+ *
+ * @returns {boolean} True if the form data is valid, otherwise false.
+ */
+function checkValidFormData() {
+  try {
+    UserDetailsValidationService.validateNamePattern(name.value);
+    UserDetailsValidationService.validateEmailPattern(email.value);
+    UserDetailsValidationService.validateFeedbackPattern(feedback.value);
+  } catch (error) {
+    status.value = error.message;
+    return false;
+  }
+  return true;
+}
+
+
+</script>
+
+<template>
+  <div class="contact-container">
+    <div class="left-info">
+      <img class="side-image" src="@/assets/img/pigrich.png" alt="pig">
+      <p>Vi vil høre din mening! </p>
+      <p>Alle bidrag blitt tatt i nøye betraktning</p>
+    </div>
+
+    <div class="form-container">
+      <div class="form-container-content">
+        <h1>
+          Kontaktskjema
+        </h1>
+        <p class="confirmation-or-error-message">{{ status }}</p>
+
+        <form class="form-element-container" @submit.prevent="handleSubmit">
+          <label for="selectedOptionSubject">Tema</label>
+          <select id="selectedOptionSubject" class="form-element" v-model="selectedOptionSubject">
+            <option disabled value="">Velg tema</option>
+            <option v-for="option in options" :value="option.value" :key="option.value">
+              {{ option.text }}
+            </option>
+          </select>
+
+          <label for="name">Fornavn</label>
+          <input id="name" class="form-element" type="text" required v-model="name" placeholder="Fornavn"
+                 @input="validateForm"/>
+
+
+          <label for="email">E-postadresse</label>
+          <input id="email" class="form-element" type="email" required v-model="email" placeholder="E-postadresse"
+                 autocomplete="email"/>
+
+          <label for="feedback">Tilbakemelding</label>
+          <textarea class="form-element" id="feedback" required v-model="feedback" placeholder="Melding"></textarea>
+
+          <button :disabled="!isFormValid" @click.prevent="submitForm" type="submit" data-cy="submit-button">Send inn!</button>
+        </form>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+label {
+  text-align: left;
+  padding-left: 10px;
+  padding-bottom: 5px;
+  display: block;
+  width: 100%;
+}
+
+h1 {
+  margin-top: 0;
+}
+
+.contact-container {
+  display: flex;
+  flex-wrap: nowrap;
+  flex-direction: row;
+  padding: 20px;
+  justify-content: center;
+}
+
+.left-info {
+  padding: 20px;
+  background-color: var(--dark-color);
+  background-size: cover;
+  flex: 1;
+  border-radius: var(--border-radius-general) 0 0 var(--border-radius-general);
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  text-align: center;
+}
+
+.side-image {
+  width: 70%;
+  height: auto;
+  filter: drop-shadow(0px 0px 10px black);
+}
+
+.form-container {
+  flex: 1;
+  background-color: var(--white-text);
+  padding: 20px;
+  border-radius: 0 var(--border-radius-general) var(--border-radius-general) 0;
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  text-align: center;
+  box-sizing: border-box;
+  width: 100%;
+  overflow: auto;
+}
+
+.form-element-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-items: center;
+  margin-top: 5%;
+}
+
+.form-element {
+  box-shadow: 2px 2px 4px 0 rgba(0, 0, 0, 0.25);
+  box-sizing: border-box;
+}
+
+.form-element:focus {
+  outline: none;
+}
+
+textarea {
+  height: 150px;
+  resize: none;
+  padding-top: 10px;
+  flex-grow: 1;
+}
+
+select, input, textarea, button {
+  width: 100%;
+  padding: 10px;
+  margin-bottom: 10px;
+  border-radius: var(--border-radius-general);
+  border: 1px solid #ccc;
+  box-sizing: border-box;
+}
+
+p {
+  color: var(--white-text);
+}
+
+.confirmation-or-error-message {
+  color: var(--middle-color);
+  font-size: 14px;
+}
+
+.form-container-content {
+  width: 100%;
+  height: 100%;
+  box-sizing: border-box;
+}
+
+img {
+  margin-bottom: 10px;
+}
+
+
+@media screen and (max-width: 760px) {
+
+  .left-info {
+    display: none;
+  }
+
+  .form-container {
+    height: auto;
+  }
+}
+
+@media screen and (min-width: 760px) {
+
+  .left-info {
+    display: flex;
+  }
+
+  .form-container {
+    height: auto;
+  }
+}
+</style>
\ No newline at end of file
diff --git a/src/components/contact/FAQs.vue b/src/components/contact/FAQs.vue
new file mode 100644
index 0000000000000000000000000000000000000000..2b855af8da7ab715bf830683b2a7e7f866bfe07a
--- /dev/null
+++ b/src/components/contact/FAQs.vue
@@ -0,0 +1,159 @@
+<script setup>
+import {ref} from 'vue';
+
+const faqs = ref([
+  {
+    question: 'Hvordan oppretter jeg en konto?',
+    answer: `For å opprette en konto, trykk på "Registrer" og fyll ut skjemaet.
+    Du vil motta en e-post for å bekrefte kontoen din. Etter bekreftelse kan du
+    senere logge inn med e-post og passord.`,
+    visible: false
+  },
+  {
+    question: 'Kan jeg lage sparemål uten å opprette en konto?',
+    answer: `Det er desverre ikke mulig å opprette sparemål
+    uten en konto. Sparesti bruker dine kontoutskrifter for å analysere
+    inntekter og utgifter. Dette gjør det mulig å generere budsjett og forslag til utfordringer.`,
+    visible: false
+  },
+  {
+    question: 'Hvorfor må jeg legge inn ekstra informasjon etter registrering?',
+    answer: `Kontoutskriftene er nødvendige for å generere et budsjett basert på
+    din inntekt og bosituasjon. Dette gjør at vi kan komme med forslag til utfordringer
+    basert på dine utgifter.`,
+    visible: false
+  },
+  {
+    question: 'Kan jeg opprette delte sparemål?',
+    answer: `Du kan opprette både delte sparemål og delte utfordringer. Delte sparemål inneholder
+    en unik kode som du kan dele med venner og familie. Her kan dere sammen spare mot et felles mål.`,
+    visible: false
+  },
+  {
+    question: 'Hva gjør jeg om jeg vil endre passord?',
+    answer: `Passord og annen brukerinformasjon kan endres under profil.`,
+    visible: false
+  },
+  {
+    question: 'Hvordan kan jeg rapportere et problem?',
+    answer: `Under kontaktskjema kan du sende tilbakemelding om problemer
+    eller feil du har oppdaget. Vi setter pris på all tilbakemelding!`,
+    visible: false
+  },
+  {
+    question: 'Er min personlige informasjon trygg?',
+    answer: `Vi tar sikkerheten din på alvor og har beskyttet dataene dine i
+    henhold til våre regler for personvern og sikkerhet.`,
+    visible: false
+  },
+  {
+    question: 'Hvordan kan jeg slette kontoen min?',
+    answer: `For å slette kontoen din, gå til "Profil" og trykk på "Slett konto".
+    Du vil bli bedt om å bekrefte slettingen av kontoen din. Vær oppmerksom på at
+    all data vil bli slettet og kan ikke gjenopprettes.`,
+    visible: false
+  }
+]);
+
+function toggleFAQ(index) {
+  faqs.value[index].visible = !faqs.value[index].visible;
+}
+</script>
+
+<template>
+  <div class="FAQ-main-container">
+    <h2>Ofte stilte spørsmål:</h2>
+    <div class="gradient-line"></div>
+    <div class="faq-container">
+      <div class="faq-item" v-for="(faq, index) in faqs" :key="index" :id="`faq-item-${index}`">
+        <button class="faq-question" @click="toggleFAQ(index)">
+          {{ faq.question }}
+        </button>
+        <div v-show="faq.visible" class="faq-answer">
+          {{ faq.answer }}
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+
+<style scoped>
+.faq-header {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.FAQ-main-container {
+  height: 100%;
+  padding: 20px;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  box-sizing: border-box;
+  min-height: 500px;
+}
+
+
+.gradient-line {
+  width: 90%;
+  height: 2px;
+  background-image: linear-gradient(to right, transparent, var(--dark-color), transparent);
+}
+
+h2 {
+  justify-content: center;
+  align-items: center;
+}
+
+.faq-container {
+  text-align: center;
+  width: 100%;
+  overflow-y: auto;
+}
+
+.faq-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  text-align: center;
+}
+
+.faq-question {
+  width: 100%;
+  cursor: pointer;
+  font-weight: bold;
+  height: auto;
+  border-radius: var(--border-radius-general);
+  word-break: break-word;
+  overflow-wrap: break-word;
+  padding: 10px;
+  margin-top: 15px;
+  background-color: var(--dark-color);
+  color: var(--white-text);
+}
+
+.faq-question:hover {
+  background-color: var(--middle-color);
+}
+
+.faq-answer {
+  width: 90%;
+  padding: 10px;
+}
+
+@media screen and (max-width: 760px) {
+  .faq-question {
+    width: 80%;
+  }
+
+  .faq-answer {
+    width: 75%;
+    justify-content: left;
+    text-align: left;
+  }
+}
+</style>
diff --git a/src/components/goals/AddChoice.vue b/src/components/goals/AddChoice.vue
new file mode 100644
index 0000000000000000000000000000000000000000..5ff61354f0bf276eb1e68119e77ad961ef9e2237
--- /dev/null
+++ b/src/components/goals/AddChoice.vue
@@ -0,0 +1,86 @@
+<script setup>
+</script>
+
+<template>
+  <div class="button-container">
+    <div class="button-box">
+      <img src="@/assets/img/challengeIcon.png" alt="Challenge icon" data-cy="challenge-icon">
+      <button @click="$emit('createChallenge')" data-cy="start-challenge-button">Start en utfordring!</button>
+    </div>
+    <div class="button-box">
+      <img src="@/assets/img/goalIcon.png" alt="Goal icon" data-cy="goal-icon">
+      <button @click="$emit('createGoal')" data-cy="start-goal-button">Start et sparemål!</button>
+    </div>
+    <div class="button-box">
+      <div class="dual-img-box">
+        <img src="@/assets/img/challengeIcon.png" alt="Challenge icon" data-cy="challenge-icon-shared">
+        <img src="@/assets/img/usersIcon.png" alt="Users icon" data-cy="users-icon-shared">
+      </div>
+      <button @click="$emit('competitionChoice')" data-cy="start-shared-challenge-button">Start en delt
+        spare-utfordring!</button>
+    </div>
+    <div class="button-box">
+      <div class="dual-img-box">
+        <img src="@/assets/img/goalIcon.png" alt="Goal icon" data-cy="goal-icon-shared">
+        <img src="@/assets/img/usersIcon.png" alt="Users icon" data-cy="users-icon-shared">
+      </div>
+      <button @click="$emit('sharedChoice')" data-cy="start-shared-goal-button">Start et delt sparemål!</button>
+    </div>
+    <div class="button-box">
+      <img src="@/assets/img/back.png" alt="Back icon" data-cy="back-button">
+      <button @click="$emit('back')" data-cy="go-back-button">GÃ¥ tilbake</button>
+    </div>
+  </div>
+</template>
+
+
+<style scoped>
+.button-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 30px;
+  margin-top: 100px;
+  box-sizing: border-box;
+  padding-bottom: 100px;
+}
+
+.dual-img-box {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: center;
+  gap: 30px;
+}
+
+.button-box {
+  background-color: #f0f0f0;
+  border-radius: var(--border-radius-general);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 20px;
+  margin: 10px;
+  width: 70%;
+  text-align: center;
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+}
+
+button {
+  width: 70%;
+}
+
+img {
+  width: 10%;
+  height: auto;
+  margin-bottom: 20px;
+}
+
+@media (max-width: 800px) {
+  .button-container {
+    margin-top: 0;
+  }
+}
+</style>
diff --git a/src/components/goals/ChallengeChoice.vue b/src/components/goals/ChallengeChoice.vue
new file mode 100644
index 0000000000000000000000000000000000000000..69048eba10088974a710f6a3c66a1a27ddb8c356
--- /dev/null
+++ b/src/components/goals/ChallengeChoice.vue
@@ -0,0 +1,63 @@
+<script setup>
+</script>
+
+<template>
+  <div class="button-container">
+    <div class="button-box">
+      <img src="@/assets/img/pluss_icon.png" alt="Image description">
+      <button @click="$emit('create')">Start en utfordring</button>
+    </div>
+    <div class="button-box">
+      <img src="@/assets/img/botIcon.png" alt="Image description">
+      <button @click="$emit('automatic')">Generer personlige utfordringer</button>
+    </div>
+    <div class="button-box">
+      <img src="@/assets/img/back.png" alt="Image description">
+      <button @click="$emit('back')">GÃ¥ tilbake</button>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.button-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 30px;
+  margin-top: 100px;
+}
+
+.dual-img-box {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: center;
+  gap: 30px;
+}
+
+.button-box {
+  background-color: #f0f0f0;
+  border-radius: var(--border-radius-general);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 20px;
+  margin: 10px;
+  width: 70%;
+  text-align: center;
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+}
+
+button {
+  width: 70%;
+  margin-top: 10px;
+}
+
+img {
+  width: 10%;
+  height: auto;
+  margin-bottom: 20px;
+}
+</style>
\ No newline at end of file
diff --git a/src/components/goals/ChallengeCompetitionComponent.vue b/src/components/goals/ChallengeCompetitionComponent.vue
new file mode 100644
index 0000000000000000000000000000000000000000..07ef584a2be7af32f33d96747b0d8664989aa492
--- /dev/null
+++ b/src/components/goals/ChallengeCompetitionComponent.vue
@@ -0,0 +1,156 @@
+/**
+* This script setup initializes data for managing challenges and their states.
+* It loads challenge content based on provided challenge IDs and retrieves data
+* from ChallengeService to populate challenge information.
+* Additionally, it provides functions to load a friends challenge content.
+*/
+<script setup>
+import { ref, defineProps, onMounted } from 'vue';
+import ProgressComponent from './ProgressComponent.vue';
+import ChallengeService from '@/services/internal/ChallengeService';
+import ChallengeStateManagement from './ChallengeStateManagement.vue';
+import GoalErrorService from '@/services/error/GoalErrorService';
+
+const props = defineProps({
+  challengeId: Number,
+  friendChallengeId: Number
+});
+const statusMessage = ref('');
+const isContentLoaded = ref(false);
+const currentChallengeId = ref(null);
+const selectedChallenge = ref(null);
+const title = ref('');
+const description = ref('');
+const difficulty = ref('');
+const startDate = ref(0);
+const endDate = ref(0);
+const state = ref('');
+
+const friendCurrentChallengeId = ref(null);
+const friendSelectedChallenge = ref(null);
+const friendTitle = ref('');
+const friendDescription = ref('');
+const friendDifficulty = ref('');
+const friendStartDate = ref(0);
+const friendEndDate = ref(0);
+const friendState = ref('');
+const friendName = ref('');
+
+
+onMounted(async () => {
+  currentChallengeId.value = props.challengeId;
+  friendCurrentChallengeId.value = props.friendChallengeId;
+  await loadContent();
+  await friendLoadContent();
+  isContentLoaded.value = true;
+});
+
+/**
+ * Populates the content of a challenge based on the provided challengeId.
+ */
+const loadContent = async () => {
+  try {
+    if (currentChallengeId.value) {
+      selectedChallenge.value = await ChallengeService.getChallenge(currentChallengeId.value);
+      if (selectedChallenge.value) {
+        title.value = selectedChallenge.value.title;
+        description.value = selectedChallenge.value.description;
+        state.value = selectedChallenge.value.state;
+        difficulty.value = selectedChallenge.value.difficulty;
+        startDate.value = new Date(selectedChallenge.value.startDate);
+        endDate.value = new Date(selectedChallenge.value.endDate);
+      }
+    }
+  } catch (error) {
+    statusMessage.value = GoalErrorService.handleErrorGetChallenge(error);
+  }
+};
+
+/**
+ * Populates the content of a friends challenge based on the provided friendChallengeId.
+ */
+const friendLoadContent = async () => {
+  try {
+    if (friendCurrentChallengeId.value) {
+      const response = await ChallengeService.getFriendChallenge(friendCurrentChallengeId.value);
+      if (response && response.length > 0) {
+        const challenge = response[0];
+        friendSelectedChallenge.value = challenge;
+
+        friendTitle.value = challenge.title;
+        friendDescription.value = challenge.description;
+        friendState.value = challenge.state;
+        friendDifficulty.value = challenge.difficulty;
+        friendStartDate.value = new Date(challenge.startDate);
+        friendEndDate.value = new Date(challenge.endDate);
+        friendName.value = challenge.firstName + " " + challenge.lastName;
+      }
+    }
+  } catch (error) {
+    statusMessage.value = GoalErrorService.handleErrorGetFriendChallenge(error);
+  }
+};
+
+</script>
+
+<template>
+  <div class="button-container">
+    <div class="button-box">
+      <button @click="$emit('back')">GÃ¥ tilbake</button>
+    </div>
+    <div class="outer">
+      <h2>Deg:</h2>
+      <h4>{{ statusMessage }}</h4>
+      <ProgressComponent v-if="isContentLoaded" :title="title" :start-date="startDate" :end-date="endDate"
+        :difficulty="difficulty" :description="description" :state="state" class="outer-component" />
+      <ChallengeStateManagement @back="$emit('back')" v-if="isContentLoaded" :challenge-id="currentChallengeId" />
+      <h2> {{ friendName }}</h2>
+      <ProgressComponent v-if="isContentLoaded" :title="friendTitle" :start-date="friendStartDate"
+        :end-date="friendEndDate" :difficulty="friendDifficulty" :description="friendDescription" :state="friendState"
+        class="outer-component" />
+    </div>
+  </div>
+</template>
+
+
+<style scoped>
+.button-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 30px;
+  margin-top: 20px;
+  margin-bottom: 20px;
+}
+
+
+.button-box {
+  background-color: #f0f0f0;
+  border-radius: var(--border-radius-general);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 20px;
+  width: 70%;
+  text-align: center;
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+}
+
+.outer {
+  margin-top: 5px;
+  width: 85%;
+  border-radius: var(--border-radius-general);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+
+
+button {
+  width: 70%;
+  margin-top: 10px;
+}
+</style>
diff --git a/src/components/goals/ChallengeComponent.vue b/src/components/goals/ChallengeComponent.vue
new file mode 100644
index 0000000000000000000000000000000000000000..bfec8d4371086cdbd9368200b9fae13898f2765a
--- /dev/null
+++ b/src/components/goals/ChallengeComponent.vue
@@ -0,0 +1,115 @@
+/**
+* This script setup initializes data for managing a challenge and its states.
+* It loads challenge content based on a provided challenge ID and retrieves data
+* from ChallengeService to populate challenge information.
+*/
+<script setup>
+import { ref, defineProps, onMounted } from 'vue';
+import ProgressComponent from './ProgressComponent.vue';
+import ChallengeService from '@/services/internal/ChallengeService';
+import ChallengeStateManagement from './ChallengeStateManagement.vue';
+import GoalErrorService from '@/services/error/GoalErrorService';
+
+const props = defineProps({
+  challengeId: Number
+});
+
+const statusMessage = ref('');
+const isContentLoaded = ref(false);
+const currentChallengeId = ref(null);
+const selectedChallenge = ref(null);
+const title = ref('');
+const description = ref('');
+const difficulty = ref('');
+const startDate = ref(0);
+const endDate = ref(0);
+const progress = ref('');
+
+
+onMounted(async () => {
+  currentChallengeId.value = props.challengeId;
+  await loadContent();
+  isContentLoaded.value = true;
+});
+
+/**
+ * Populates the content of a challenge based on the provided challengeId.
+ */
+const loadContent = async () => {
+  try {
+    if (currentChallengeId.value) {
+      selectedChallenge.value = await ChallengeService.getChallenge(currentChallengeId.value);
+      if (selectedChallenge.value) {
+        title.value = selectedChallenge.value.title;
+        description.value = selectedChallenge.value.description;
+        progress.value = selectedChallenge.value.progress;
+        difficulty.value = selectedChallenge.value.difficulty;
+        startDate.value = new Date(selectedChallenge.value.startDate);
+        endDate.value = new Date(selectedChallenge.value.endDate);
+      }
+    }
+  } catch (error) {
+    statusMessage.value = GoalErrorService.handleErrorGetChallenge(error);
+  }
+};
+
+</script>
+
+<template>
+  <div class="button-container">
+    <div class="button-box">
+      <button @click="$emit('back')">GÃ¥ tilbake</button>
+    </div>
+    <div class="outer">
+      <h2>Deg:</h2>
+      <h4>{{ statusMessage }}</h4>
+      <ProgressComponent v-if="isContentLoaded" :title="title" :start-date="startDate" :end-date="endDate"
+        :difficulty="difficulty" :description="description" :state="state" class="outer-component" />
+      <ChallengeStateManagement @back="$emit('back')" v-if="isContentLoaded" :challenge-id="currentChallengeId" />
+    </div>
+  </div>
+</template>
+
+
+
+<style scoped>
+.button-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 30px;
+  margin-top: 20px;
+  margin-bottom: 20px;
+}
+
+
+.button-box {
+  background-color: var(--accent-color);
+  border-radius: var(--border-radius-general);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 20px;
+  width: 70%;
+  text-align: center;
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+}
+
+.outer {
+  margin-top: 5px;
+  width: 85%;
+  border-radius: var(--border-radius-general);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+
+
+button {
+  width: 70%;
+  margin-top: 10px;
+}
+</style>
diff --git a/src/components/goals/ChallengeStateManagement.vue b/src/components/goals/ChallengeStateManagement.vue
new file mode 100644
index 0000000000000000000000000000000000000000..d51523cd284d302db92078add9d338f33dd3cc91
--- /dev/null
+++ b/src/components/goals/ChallengeStateManagement.vue
@@ -0,0 +1,190 @@
+/**
+* This script setup initializes data for managing the state of a challenge.
+* The script provides methods for updating a challenge state.
+*/
+<script setup>
+import { ref, defineProps, onMounted } from 'vue';
+import ChallengeService from '@/services/internal/ChallengeService';
+import BadgeService from '@/services/internal/BadgeService';
+import { defineEmits } from 'vue';
+import GoalErrorService from '@/services/error/GoalErrorService';
+
+const emit = defineEmits(['back']);
+
+const statusMessage = ref('');
+const challengeId = ref(0);
+const selectedChallenge = ref(null);
+const showModal = ref(false);
+const showCompleted = ref(false);
+const confirmationStage = ref(false);
+
+const props = defineProps({
+    challengeId: Number
+});
+
+onMounted(async () => {
+    try {
+        challengeId.value = props.challengeId;
+        if (challengeId.value) {
+            selectedChallenge.value = await ChallengeService.getChallenge(challengeId.value);
+            const today = new Date();
+            if (getProgress(selectedChallenge.value.startDate, selectedChallenge.value.endDate, today) > 99) {
+                showCompleted.value = true;
+            }
+        }
+    } catch (error) {
+        statusMessage.value = GoalErrorService.handleErrorGetChallenge(error);
+    }
+});
+
+/**
+ * Updates a challenge state to failed.
+ */
+const failed = async () => {
+    try {
+        await ChallengeService.updateProgress(challengeId.value, "FAILED");
+        emit('back');
+    } catch (error) {
+        statusMessage.value = GoalErrorService.handleErrorUpdateProgress(error);
+    }
+};
+
+/**
+ * Updates a challenge state to completed.
+ */
+const completed = async () => {
+    try {
+        await ChallengeService.updateProgress(challengeId.value, "COMPLETED");
+    } catch (error) {
+        statusMessage.value = GoalErrorService.handleErrorUpdateProgress(error);
+    }
+    try {
+        await BadgeService.createBadge("NUMBER_OF_CHALLENGES_COMPLETED");
+    } catch (error) {
+        statusMessage.value = GoalErrorService.handleErrorCreateBadge(error);
+    }
+    emit('back');
+};
+
+/**
+ * Gets the progress of the mascot based on todays date.
+ * @param start The start date of the challenge.
+ * @param end The ending date of the challenge.
+ * @param today Todays date.
+ */
+const getProgress = (start, end, today) => {
+    const startDt = new Date(start);
+    const endDt = new Date(end);
+    const todayDt = new Date(today);
+
+    if (todayDt < startDt) {
+        return 0;
+    } else if (todayDt > endDt) {
+        return 100;
+    } else {
+        const totalDuration = endDt - startDt;
+        const timeElapsed = todayDt - startDt;
+        return Math.floor((timeElapsed / totalDuration) * 100);
+    }
+};
+
+/**
+ * Displays a popup.
+ */
+const updateProgress = async () => {
+    try {
+        if (challengeId.value) {
+            selectedChallenge.value = await ChallengeService.getChallenge(challengeId.value);
+            if (selectedChallenge.value && selectedChallenge.value.progress == "IN_PROGRESS") {
+                showModal.value = true;
+            }
+        }
+    } catch (error) {
+        statusMessage.value = GoalErrorService.handleErrorGetChallenge(error);
+    }
+};
+
+/**
+ * Updates the state of a challenge to failed.
+ */
+const confirmFailure = async () => {
+    try {
+        await ChallengeService.updateProgress(challengeId.value, "FAILED");
+        confirmationStage.value = true;
+    } catch (error) {
+        statusMessage.value = GoalErrorService.handleErrorUpdateProgress(error);
+    }
+};
+
+/**
+ * Closes the popup.
+ */
+const closePopup = () => {
+    showModal.value = false;
+};
+
+</script>
+
+<template>
+    <div>
+        <button class="btn" @click="updateProgress">Jeg har feilet på utfordringen</button>
+        <h4>{{ statusMessage }}</h4>
+        <div v-if="showModal" class="modal-overlay">
+            <div class="modal">
+                <p v-if="!confirmationStage">Er du sikker?</p>
+                <p v-else>Feilet</p>
+
+                <button v-if="!confirmationStage" @click="confirmFailure">Ja</button>
+                <button v-if="!confirmationStage" @click="closePopup">Nei</button>
+                <button v-if="confirmationStage" @click="$emit('back')">Ferdig</button>
+            </div>
+        </div>
+        <div v-if="showCompleted" class="modal-overlay">
+            <div class="modal">
+                <p>Gratulerer. Utfordringen er ferdig! Klarte du den?</p>
+                <button @click="completed">Ja</button>
+                <button @click="failed">Nei</button>
+            </div>
+        </div>
+    </div>
+</template>
+
+
+
+
+<style scoped>
+.modal-overlay {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background-color: rgba(0, 0, 0, 0.5);
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+}
+
+.modal {
+    background: white;
+    padding: 20px;
+    border-radius: var(--border-radius-general);
+    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+}
+
+button {
+    width: 100%;
+    margin-top: 10px;
+}
+
+.btn {
+    background-color: var(--error-text);
+    width: 100%;
+    margin-top: 10px;
+}
+</style>
diff --git a/src/components/goals/CompetitionChoice.vue b/src/components/goals/CompetitionChoice.vue
new file mode 100644
index 0000000000000000000000000000000000000000..1dd9afc844aa317ec3adc7b279d50b3b85126ea1
--- /dev/null
+++ b/src/components/goals/CompetitionChoice.vue
@@ -0,0 +1,63 @@
+<script setup>
+</script>
+
+<template>
+  <div class="button-container">
+    <div class="button-box">
+      <img src="@/assets/img/pluss_icon.png" alt="Image description">
+      <button @click="$emit('create')">Start en delt utfordring</button>
+    </div>
+    <div class="button-box">
+      <img src="@/assets/img/join.png" alt="Image description">
+      <button @click="$emit('join')">Bli med i en utfordring</button>
+    </div>
+    <div class="button-box">
+      <img src="@/assets/img/back.png" alt="Image description">
+      <button @click="$emit('back')">GÃ¥ tilbake</button>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.button-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 30px;
+  margin-top: 100px;
+}
+
+.dual-img-box {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: center;
+  gap: 30px;
+}
+
+.button-box {
+  background-color: #f0f0f0;
+  border-radius: var(--border-radius-general);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 20px;
+  margin: 10px;
+  width: 70%;
+  text-align: center;
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+}
+
+button {
+  width: 70%;
+  margin-top: 10px;
+}
+
+img {
+  width: 10%;
+  height: auto;
+  margin-bottom: 20px;
+}
+</style>
\ No newline at end of file
diff --git a/src/components/goals/CreateAutomaticChallenges.vue b/src/components/goals/CreateAutomaticChallenges.vue
new file mode 100644
index 0000000000000000000000000000000000000000..f596d6654ff58d7c86dc68682e2df7266d9814f0
--- /dev/null
+++ b/src/components/goals/CreateAutomaticChallenges.vue
@@ -0,0 +1,133 @@
+<script setup>
+import {ref} from 'vue';
+import AutomaticChallengeService from '@/services/internal/AutomaticChallengeService.js';
+import ChallengeService from '@/services/internal/ChallengeService.js';
+import {defineEmits} from 'vue';
+import GoalErrorService from '@/services/error/GoalErrorService';
+
+const title = ref('');
+const challenges = ref([]);
+const emit = defineEmits(['create', 'back']);
+
+
+const statusMessage = ref('');
+const hasAnalysis = ref(false);
+const fetchedChallenges = ref([]);
+
+
+const getChallenges = async () => {
+  try {
+    const challenges = await AutomaticChallengeService.getChallenge();
+    fetchedChallenges.value = challenges;
+    hasAnalysis.value = true;
+  } catch (error) {
+    statusMessage.value = await GoalErrorService.handleErrorGetAutomaticChallenge(error);
+  }
+};
+
+const chooseChallenge = async (challenge) => {
+  const {category, goalDescription, startDate, endDate} = challenge;
+  title.value = category;
+  try {
+    await ChallengeService.addChallenge('challengeDto', category, goalDescription, startDate, endDate, "EASY", "IN_PROGRESS");
+  } catch (error) {
+    statusMessage.value = await GoalErrorService.handleErrorAddChallenge(error);
+  }
+  await saveNext();
+};
+
+const saveNext = async () => {
+  try {
+    challenges.value = await ChallengeService.getChallenges(20, 0);
+  } catch (error) {
+    statusMessage.value = await GoalErrorService.handleErrorGetChallenges(error);
+  }
+  let newestChallenge = challenges.value[0];
+  for (let i = 1; i < challenges.value.length; i++) {
+    if (challenges.value[i].title === title.value) {
+      newestChallenge = challenges.value[i];
+    }
+  }
+  emit('challengeComponent', newestChallenge.id);
+};
+</script>
+
+<template>
+  <div class="createChallenge-main-container">
+    <div class="createChallenge-container">
+      <div class="container">
+        <button id="create-goal-button" class="btn" @click="getChallenges" :disabled="hasAnalysis">
+          Generer ny utfordring!
+        </button>
+        <button @click="$emit('back')">GÃ¥ tilbake</button>
+        <h4>{{ statusMessage }}</h4>
+      </div>
+
+      <div v-if="fetchedChallenges.length > 0" class="container">
+        <h3>Velg en utfordring:</h3>
+        <div v-for="(challenge, index) in fetchedChallenges" :key="index" class="container" id="challenge">
+          <h4>{{ challenge.category }}</h4>
+          <p>{{ challenge.goalDescription }}</p>
+          <p>Start Dato: {{ challenge.startDate }}</p>
+          <p>Slutt Dato: {{ challenge.endDate }}</p>
+          <button @click="chooseChallenge(challenge)">Velg utfordring</button>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.createChallenge-main-container {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 100%;
+  height: 100vh;
+  padding: 2.5%;
+}
+p {
+  word-wrap: break-word;
+  overflow-wrap: break-word;
+}
+
+#challenge {
+  background-color: var(--white-general);
+}
+
+button:disabled {
+  cursor: not-allowed;
+}
+
+.createChallenge-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 30px;
+  width: 100%;
+  height: 100%;
+}
+
+.container {
+  background-color: var(--accent-color);
+  border-radius: var(--border-radius-general);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 20px;
+  margin: 10px;
+  text-align: center;
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+  width: 100%;
+  max-width: 800px;
+  box-sizing: border-box;
+}
+
+button {
+  width: 70%;
+  margin-top: 10px;
+  height: auto;
+}
+</style>
diff --git a/src/components/goals/CreateChallenge.vue b/src/components/goals/CreateChallenge.vue
index dd4eb93c3a7059097d285b8e47f4303c09a02884..d33745beeeef4ed468658f0db0dc8590a8b5585e 100644
--- a/src/components/goals/CreateChallenge.vue
+++ b/src/components/goals/CreateChallenge.vue
@@ -1,67 +1,162 @@
+/**
+* This script provides methods for creating a challenge.
+*/
 <script setup>
+import { ref, computed } from 'vue';
+import ChallengeService from '@/services/internal/ChallengeService.js';
+import { defineEmits } from 'vue';
+import GoalErrorService from '@/services/error/GoalErrorService';
 
+const statusMessage = ref('');
+const title = ref('');
+const description = ref('');
+const startDate = ref('');
+const endDate = ref('');
+const challenges = ref([]);
+const emit = defineEmits(['create', 'back']);
+
+/**
+ * Checks if the startDate is not a past date.
+ */
+const isStartDateValid = computed(() => {
+  const today = new Date().toISOString().slice(0, 10);
+  return startDate.value >= today;
+});
+
+/**
+ * Checks if the endDate comes after the startdate and is not on the same date.
+ */
+const isEndDateValid = computed(() => {
+  return endDate.value > startDate.value;
+});
+
+/**
+ * Creates and saves a challenge based on input from the user.
+ */
+const handleNext = async () => {
+  try {
+    await ChallengeService.addChallenge('challengeDto', title.value, description.value, startDate.value, endDate.value, "EASY", "IN_PROGRESS");
+    await saveNext();
+  } catch (error) {
+    statusMessage.value = GoalErrorService.handleErrorAddChallenge(error);
+  }
+};
+
+/**
+ * Fetches the new challenge and displays it.
+ * Also checks if the challenge was saved correctly.
+ */
+const saveNext = async () => {
+  try {
+    challenges.value = await ChallengeService.getChallenges(20, 0);
+  } catch (error) {
+    statusMessage.value = GoalErrorService.handleErrorGetChallenges(error);
+  }
+  let newestChallenge = challenges.value[0];
+  for (let i = 1; i < challenges.value.length; i++) {
+    if (challenges.value[i].title === title.value) {
+      newestChallenge = challenges.value[i];
+    }
+  }
+  emit('challengeComponent', newestChallenge.id);
+};
 </script>
 
 <template>
+  <div class="createChallenge-container">
     <h1 id="title">Opprett ny utfordring!</h1>
     <div class="container">
-        <div>
-            <h3>Hva ønsker du å slutte med?</h3>
-            <input type="text" placeholder="Skriv inn det du ønsker å slutte med her">
+      <div class="challenge-and-date-container">
+        <div class="input-field-container">
+          <label for="title-label">
+            <h3>Tittel</h3>
+          </label>
+          <input id="title-label" type="text" v-model="title" placeholder="Skriv inn utfordringen din her" maxlength="20">
+        </div>
+        <div class="input-field-container">
+          <label for="challenge-description">
+            <h3>Beskrivelse</h3>
+          </label>
+          <input id="challenge-description" type="text" v-model="description" placeholder="Beskrivelse.." maxlength="255">
         </div>
         <div class="date-container">
-            <div>
-                <h3>Start dato</h3>
-                <input type="date">
-            </div>
-            <div>
-                <h3>Slutt dato</h3>
-                <input type="date">
-            </div>
+          <div>
+            <label for="start-date-label">
+              <h3>Start dato</h3>
+            </label>
+            <input id="start-date-label" type="date" v-model="startDate">
+          </div>
+          <div>
+            <label for="end-date-label">
+              <h3>Slutt dato</h3>
+            </label>
+            <input id="end-date-label" type="date" v-model="endDate">
+          </div>
         </div>
-        <button id="create-goal-button" class="btn" @click="$emit('create')">Start utfordring!</button>
-        <button id="create-goal-button" class="btn" @click="$emit('back')">GÃ¥ tilbake</button>
+      </div>
+      <h4>{{ statusMessage }}</h4>
+      <button id="create-goal-button" class="btn" @click="handleNext" :disabled="!isStartDateValid || !isEndDateValid">
+        Lag utfordring!
+      </button>
+      <button @click="$emit('back')">GÃ¥ tilbake</button>
     </div>
+  </div>
 </template>
 
 
 <style scoped>
+button:disabled {
+  cursor: not-allowed;
+}
+
+.createChallenge-container {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  background-color: var(--accent-color);
+  padding: 20px;
+  border-radius: var(--border-radius-general);
+  margin-top: 70px;
+}
+
+.input-field-container {
+  width: 100%;
+}
+
 #title {
-    font-style: italic;
-    padding-top: 3em;
-    padding-bottom: 1em;
-    text-align: center;
+  padding-bottom: 1em;
+  text-align: center;
 }
 
 .container {
-    display: flex;
-    flex-direction: column;
-    align-items: center;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  width: 80%;
+  justify-content: space-around;
 }
 
-
 .date-container {
-    display: flex;
-    flex-direction: row;
-    justify-content: space-evenly;
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  width: 100%;
+  gap: 10px;
 }
 
-#create-goal-button {
-    background-color: #0056b3;
-    color: white;
-    border: none;
-    width: 60%;
-    padding: 10px 20px;
-    font-size: 16px;
-    border-radius: 20px;
-    box-shadow: 0 4px #003875;
-    cursor: pointer;
-    transition: all 0.3s ease;
-    margin: 40px auto;
-}
-#create-goal-button:hover {
-    background-color: #023366;
-    box-shadow: 0 6px #003875;
+button {
+  width: 70%;
+  margin-top: 10px;
 }
 
-</style>
\ No newline at end of file
+input[type="text"],
+input[type="date"] {
+  width: 100%;
+  padding: 10px;
+  margin: 5px 0;
+  box-sizing: border-box;
+  border: 1px solid var(--accent-color);
+  border-radius: var(--border-radius-general);
+}
+</style>
diff --git a/src/components/goals/CreateChallengeCompetition.vue b/src/components/goals/CreateChallengeCompetition.vue
new file mode 100644
index 0000000000000000000000000000000000000000..8a6ac98e7062f5d9cf9e0524798aa049e7407fd1
--- /dev/null
+++ b/src/components/goals/CreateChallengeCompetition.vue
@@ -0,0 +1,200 @@
+/**
+* This script provides methods for creating a shared challenge.
+*/
+<script setup>
+import { ref, computed, defineEmits } from 'vue';
+import ChallengeService from '@/services/internal/ChallengeService.js';
+import EmailService from '@/services/internal/EmailService';
+import GoalErrorService from '@/services/error/GoalErrorService';
+import SpinningWheelComponent from "@/components/other/SpinningWheelComponent.vue";
+
+const title = ref('');
+const email = ref('');
+const description = ref('');
+const startDate = ref('');
+const endDate = ref('');
+const challenges = ref([]);
+const emit = defineEmits(['create', 'back']);
+const selectedChallenge = ref(null);
+const statusMessage = ref('');
+const isSubmitting = ref(false);
+
+
+/**
+ * Checks if the startDate is not a past date.
+ */
+const isStartDateValid = computed(() => {
+  const today = new Date().toISOString().slice(0, 10);
+  return startDate.value >= today;
+});
+
+/**
+ * Checks if the endDate comes after the startdate and is not on the same date.
+ */
+const isEndDateValid = computed(() => {
+  return endDate.value > startDate.value;
+});
+
+/**
+ * Creates and saves a challenge based on input from the user.
+ */
+const handleNext = async () => {
+  isSubmitting.value = true;
+  try {
+    await ChallengeService.addChallenge('SharedChallengeDto', title.value, description.value, startDate.value, endDate.value, "EASY", "IN_PROGRESS");
+  } catch (error) {
+    statusMessage.value = GoalErrorService.handleErrorAddChallenge(error);
+  }
+
+  await saveNext();
+  try {
+    await EmailService.sendChallengeCode(email.value, selectedChallenge.value.sharedChallengeId);
+  } catch (error) {
+    statusMessage.value = GoalErrorService.handleErrorSendChallengeCode(error);
+  }
+  statusMessage.value = 'Email sent successfully!';
+  emit('goalSharedComponent', selectedChallenge.value.id);
+  isSubmitting.value = false;
+};
+
+/**
+ * Fetches the new challenge and displays it.
+ * Also checks if the challenge was saved correctly.
+ */
+const saveNext = async () => {
+  try {
+    challenges.value = await ChallengeService.getChallenges(20, 0);
+  } catch (error) {
+    statusMessage.value = GoalErrorService.handleErrorGetChallenges(error);
+  }
+  let newestChallenge = challenges.value[0];
+  for (let i = 1; i < challenges.value.length; i++) {
+    if (challenges.value[i].title === title.value) {
+      newestChallenge = challenges.value[i];
+    }
+  }
+  try {
+    selectedChallenge.value = await ChallengeService.getChallenge(newestChallenge.id);
+  } catch (error) {
+    statusMessage.value = GoalErrorService.handleErrorGetChallenge(error);
+  }
+};
+</script>
+
+<template>
+  <div class="createChallenge-container">
+    <h1 id="title">Opprett ny delt utfordring!</h1>
+    <div class="container">
+      <div class="challenge-and-date-container">
+        <div class="input-field-container">
+          <h3>Tittel</h3>
+          <input type="text" v-model="title" placeholder="Skriv inn utfordringen din her" maxlength="20">
+        </div>
+        <div class="input-field-container">
+          <h3>Beskrivelse</h3>
+          <input type="text" v-model="description" placeholder="Beskrivelse.." maxlength="255">
+        </div>
+        <div class="input-field-container">
+          <h3>Email til venn</h3>
+          <input type="text" v-model="email" placeholder="Email...">
+        </div>
+        <div class="date-container">
+          <div>
+            <h3>Start dato</h3>
+            <input type="date" v-model="startDate">
+          </div>
+          <div>
+            <h3>Slutt dato</h3>
+            <input type="date" v-model="endDate">
+          </div>
+        </div>
+      </div>
+      <h1>{{ statusMessage }}</h1>
+      <button id="create-goal-button" class="btn" @click="handleNext" :disabled="!isStartDateValid || !isEndDateValid">
+        Lag utfordring!
+      </button>
+      <button @click="$emit('back')">GÃ¥ tilbake</button>
+      <spinning-wheel-component :visible="isSubmitting" />
+    </div>
+  </div>
+</template>
+
+
+<style scoped>
+.popup {
+  position: fixed;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  padding: 20px;
+  background-color: white;
+  border: 2px solid #ccc;
+  z-index: 10;
+}
+
+.overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.5);
+  z-index: 5;
+}
+
+
+button:disabled {
+  cursor: not-allowed;
+}
+
+.createChallenge-container {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  background-color: var(--accent-color);
+  padding: 20px;
+  border-radius: var(--border-radius-general);
+  margin-top: 70px;
+}
+
+.input-field-container {
+  width: 100%;
+}
+
+#title {
+  padding-bottom: 1em;
+  text-align: center;
+}
+
+.container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  width: 80%;
+  justify-content: space-around;
+}
+
+.date-container {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  width: 100%;
+  gap: 10px;
+}
+
+button {
+  width: 70%;
+  margin-top: 10px;
+}
+
+input[type="text"],
+input[type="date"] {
+  width: 100%;
+  padding: 10px;
+  margin: 5px 0;
+  box-sizing: border-box;
+  border: 1px solid var(--accent-color);
+  border-radius: var(--border-radius-general);
+}
+</style>
diff --git a/src/components/goals/CreateGoal.vue b/src/components/goals/CreateGoal.vue
new file mode 100644
index 0000000000000000000000000000000000000000..0df29a40405de81d9707e68f4935fd51e8a01e14
--- /dev/null
+++ b/src/components/goals/CreateGoal.vue
@@ -0,0 +1,152 @@
+/**
+* This script provides methods for creating a goal.
+*/
+<script setup>
+import { ref, computed } from 'vue';
+import GoalService from '@/services/internal/GoalService.js';
+import { defineEmits } from 'vue';
+import GoalErrorService from '@/services/error/GoalErrorService';
+
+const statusMessage = ref('');
+const title = ref('');
+const startDate = ref('');
+const endDate = ref('');
+const amount = ref(0);
+const emit = defineEmits(['next', 'back']);
+
+/**
+ * Checks if the startDate is not a past date.
+ */
+const isStartDateValid = computed(() => {
+  const today = new Date().toISOString().slice(0, 10);
+  return startDate.value >= today;
+});
+
+/**
+ * Checks if the endDate comes after the startdate and is not on the same date.
+ */
+const isEndDateValid = computed(() => {
+  return endDate.value > startDate.value;
+});
+
+/**
+ * Creates and saves a challenge based on input from the user.
+ * Fetches the new challenge and displays it.
+ */
+const handleNext = async () => {
+  try {
+    const goal = await GoalService.addGoal(title.value, amount.value, 3, startDate.value, endDate.value);
+    emit('goalComponent', goal.id);
+  } catch (error) {
+    statusMessage.value = GoalErrorService.handleErrorAddGoal(error);
+  }
+};
+
+/**
+ * Limits the number of digits for the goal amount to 8.
+ */
+ const limitDigits = () => {
+  if (amount.value && amount.value.toString().length > 6) {
+    amount.value = parseFloat(amount.value.toString().slice(0, 6));
+  }
+};
+
+</script>
+
+<template>
+  <div class="createChallenge-container">
+    <h1 id="title" data-cy="title">Opprett nytt sparemål!</h1>
+    <div class="container">
+      <div class="challenge-and-date-container">
+        <div data-cy="goal-title-container">
+          <h3>Hva sparer du til?</h3>
+          <input type="text" v-model="title" placeholder="Skriv inn målet ditt her" data-cy="goal-title-input" maxlength="20">
+        </div>
+        <div data-cy="goal-amount-container">
+          <h3>Hvor mye koster målet?</h3>
+          <input type="number" v-model="amount" placeholder="Skriv inn beløpet her" data-cy="goal-amount-input" @input="limitDigits">
+        </div>
+        <div class="date-container" data-cy="date-container">
+          <div data-cy="start-date-container">
+            <h3>Start dato</h3>
+            <input type="date" v-model="startDate" data-cy="start-date-input">
+          </div>
+          <div data-cy="end-date-container">
+            <h3>Slutt dato</h3>
+            <input type="date" v-model="endDate" data-cy="end-date-input">
+          </div>
+        </div>
+      </div>
+      <h4>{{ statusMessage }}</h4>
+      <button id="create-goal-button" class="btn" @click="handleNext" :disabled="!isStartDateValid || !isEndDateValid"
+        data-cy="create-goal-button">
+        Lag sparemål!
+      </button>
+      <button @click="$emit('back')" data-cy="back-button">GÃ¥ tilbake</button>
+    </div>
+  </div>
+</template>
+
+
+
+
+
+
+<style scoped>
+button:disabled {
+  cursor: not-allowed;
+}
+
+.createChallenge-container {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  background-color: var(--accent-color);
+  padding: 20px;
+  border-radius: var(--border-radius-general);
+  margin-top: 30px;
+}
+
+.input-field-container {
+  width: 100%;
+}
+
+#title {
+  font-style: italic;
+  padding-bottom: 1em;
+  text-align: center;
+}
+
+.container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  width: 80%;
+  justify-content: space-around;
+}
+
+.date-container {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  width: 100%;
+  gap: 10px;
+}
+
+button {
+  width: 70%;
+  margin-top: 10px;
+}
+
+input[type="text"],
+input[type="date"],
+input[type="number"] {
+  width: 100%;
+  padding: 10px;
+  margin: 5px 0;
+  box-sizing: border-box;
+  border: 1px solid var(--accent-color);
+  border-radius: var(--border-radius-general);
+}
+</style>
diff --git a/src/components/goals/CreateGoals.vue b/src/components/goals/CreateGoals.vue
deleted file mode 100644
index 11c6e0c916ce76d7efdafea00d4e3ef79d1116ce..0000000000000000000000000000000000000000
--- a/src/components/goals/CreateGoals.vue
+++ /dev/null
@@ -1,71 +0,0 @@
-<script setup>
-
-</script>
-
-<template>
-    <h1 id="title">Opprett nytt sparemål!</h1>
-    <div class="container">
-        <div>
-            <h3>Hva sparer du til?</h3>
-            <input type="text" placeholder="Skriv inn målet ditt her">
-        </div>
-        <div>
-            <h3>Hvor mye koster målet?</h3>
-            <input type="number" placeholder="Skriv inn beløpet her">
-        </div>
-        <div class="date-container">
-            <div>
-                <h3>Start dato</h3>
-                <input type="date">
-            </div>
-            <div>
-                <h3>Slutt dato</h3>
-                <input type="date">
-            </div>
-        </div>
-        <button id="create-goal-button" class="btn" @click="$emit('create')">Lag sparemål!</button>
-        <button id="create-goal-button" class="btn" @click="$emit('back')">GÃ¥ tilbake</button>
-    </div>
-</template>
-
-
-<style scoped>
-#title {
-    font-style: italic;
-    padding-top: 3em;
-    padding-bottom: 1em;
-    text-align: center;
-}
-
-.container {
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-}
-
-
-.date-container {
-    display: flex;
-    flex-direction: row;
-    justify-content: space-evenly;
-}
-
-#create-goal-button {
-    background-color: #0056b3;
-    color: white;
-    border: none;
-    width: 60%;
-    padding: 10px 20px;
-    font-size: 16px;
-    border-radius: 20px;
-    box-shadow: 0 4px #003875;
-    cursor: pointer;
-    transition: all 0.3s ease;
-    margin: 40px auto;
-}
-#create-goal-button:hover {
-    background-color: #023366;
-    box-shadow: 0 6px #003875;
-}
-
-</style>
\ No newline at end of file
diff --git a/src/components/goals/CreateSharedGoal.vue b/src/components/goals/CreateSharedGoal.vue
new file mode 100644
index 0000000000000000000000000000000000000000..eb268aeb9202b297a827fb2edb5d40f7916692c8
--- /dev/null
+++ b/src/components/goals/CreateSharedGoal.vue
@@ -0,0 +1,147 @@
+/**
+* This script provides methods for creating a goal.
+*/
+<script setup>
+import { ref, computed } from 'vue';
+import GoalService from '@/services/internal/GoalService.js';
+import { defineEmits } from 'vue';
+import GoalErrorService from '@/services/error/GoalErrorService';
+
+const statusMessage = ref('');
+const title = ref('');
+const startDate = ref('');
+const endDate = ref('');
+const amount = ref(0);
+const emit = defineEmits(['next', 'back']);
+
+/**
+ * Checks if the startDate is not a past date.
+ */
+const isStartDateValid = computed(() => {
+  const today = new Date().toISOString().slice(0, 10);
+  return startDate.value >= today;
+});
+
+/**
+ * Checks if the endDate comes after the startdate and is not on the same date.
+ */
+const isEndDateValid = computed(() => {
+  return endDate.value > startDate.value;
+});
+
+/**
+ * Creates and saves a challenge based on input from the user.
+ * Fetches the new challenge and displays it.
+ */
+const handleNext = async () => {
+  try {
+    const goal = await GoalService.addGoal(title.value, amount.value, 3, startDate.value, endDate.value);
+    emit('goalComponent', goal.id);
+  } catch (error) {
+    statusMessage.value = GoalErrorService.handleErrorAddGoal(error);
+  }
+};
+
+/**
+ * Limits the number of digits for the goal amount to 8.
+ */
+ const limitDigits = () => {
+  if (amount.value && amount.value.toString().length > 6) {
+    amount.value = parseFloat(amount.value.toString().slice(0, 6));
+  }
+};
+</script>
+<template>
+  <div class="createChallenge-container">
+    <h1 id="title">Opprett nytt delt sparemål!</h1>
+    <div class="container">
+      <div class="challenge-and-date-container">
+        <div>
+          <h3>Hva sparer du til?</h3>
+          <input type="text" v-model="title" placeholder="Skriv inn målet ditt her" maxlength="20">
+        </div>
+        <div>
+          <h3>Hvor mye koster målet?</h3>
+          <input type="number" v-model="amount" placeholder="Skriv inn beløpet her" @input="limitDigits">
+        </div>
+        <div class="date-container">
+          <div>
+            <h3>Start dato</h3>
+            <input type="date" v-model="startDate">
+          </div>
+          <div>
+            <h3>Slutt dato</h3>
+            <input type="date" v-model="endDate">
+          </div>
+        </div>
+      </div>
+      <h4>{{ statusMessage }}</h4>
+      <button id="create-goal-button" class="btn" @click="handleNext" :disabled="!isStartDateValid || !isEndDateValid">
+        Lag sparemål!
+      </button>
+      <button @click="$emit('back')">GÃ¥ tilbake</button>
+    </div>
+  </div>
+</template>
+
+
+
+
+
+<style scoped>
+button:disabled {
+  cursor: not-allowed;
+}
+
+.createChallenge-container {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  background-color: var(--accent-color);
+  padding: 20px;
+  border-radius: var(--border-radius-general);
+  margin-top: 70px;
+}
+
+.input-field-container {
+  width: 100%;
+}
+
+#title {
+  padding-bottom: 1em;
+  text-align: center;
+}
+
+.container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  width: 80%;
+  justify-content: space-around;
+}
+
+.date-container {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  width: 100%;
+  gap: 10px;
+}
+
+button {
+  width: 70%;
+  margin-top: 10px;
+}
+
+input[type="text"],
+input[type="date"],
+input[type="number"] {
+  width: 100%;
+  padding: 10px;
+  margin: 5px 0;
+  box-sizing: border-box;
+  border: 1px solid var(--accent-color);
+  border-radius: var(--border-radius-general);
+}
+</style>
\ No newline at end of file
diff --git a/src/components/goals/GoalComponent.vue b/src/components/goals/GoalComponent.vue
new file mode 100644
index 0000000000000000000000000000000000000000..0bf38fb12b7cde4b2023726171969898b1527f36
--- /dev/null
+++ b/src/components/goals/GoalComponent.vue
@@ -0,0 +1,149 @@
+/**
+* Script for fetching and calculating data for displaying a goal.
+*/
+<script setup>
+import {ref, defineProps, onMounted} from 'vue';
+import RoadComponent from './RoadComponent.vue';
+import RoadUsersComponent from './RoadUsersComponent.vue';
+import GoalService from '@/services/internal/GoalService';
+import GoalContributeComponent from './GoalContributeComponent.vue';
+import GoalErrorService from '@/services/error/GoalErrorService';
+
+const props = defineProps({
+  goalId: Number,
+  goalTitle: String,
+  goalState: String
+});
+
+const statusMessage = ref('');
+const componentKey = ref(0);
+const isContentLoaded = ref(false);
+const currentGoalId = ref(null);
+const selectedGoal = ref(null);
+const goalName = ref('');
+const daysLost = ref(0);
+const amountSaved = ref(0);
+const amountPerDay = ref(0);
+const amountOfCircles = ref(0);
+const currentCircle = ref(0);
+const startDate = ref(0);
+const endDate = ref(0);
+const state = ref("");
+const totalAmount = ref(0);
+const joinCode = ref(0);
+
+onMounted(async () => {
+  currentGoalId.value = props.goalId;
+  goalName.value = props.goalTitle;
+  state.value = props.goalState;
+  await loadContent();
+  isContentLoaded.value = true;
+});
+
+/**
+ * Fetches a goal based on currentGoalId and calcualtes display values.
+ */
+const loadContent = async () => {
+  if (currentGoalId.value) {
+    try {
+      selectedGoal.value = await GoalService.getGoal(currentGoalId.value);
+    } catch (error) {
+      statusMessage.value = GoalErrorService.handleErrorGetGoal(error);
+    }
+    try {
+      if (selectedGoal.value) {
+        goalName.value = selectedGoal.value.title;
+        daysLost.value = 3 - selectedGoal.value.lives;
+        startDate.value = new Date(selectedGoal.value.startDate);
+        endDate.value = new Date(selectedGoal.value.endDate);
+        totalAmount.value = selectedGoal.value.totalAmount;
+        joinCode.value = selectedGoal.value.joinCode;
+        const diffTime = Math.abs(endDate.value - startDate.value);
+        const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
+        amountOfCircles.value = diffDays;
+        amountSaved.value = await GoalService.getAmountSaved(currentGoalId.value, goalName.value, state.value);
+        amountPerDay.value = Math.floor(selectedGoal.value.totalAmount / getDaysBetweenDates(new Date(selectedGoal.value.startDate), new Date(selectedGoal.value.endDate)));
+        currentCircle.value = Math.floor((amountSaved.value / amountPerDay.value) - 1);
+        forceUpdate();
+      }
+    } catch (error) {
+      statusMessage.value = GoalErrorService.handleErrorGetAmountSaved(error);
+    }
+  }
+};
+
+/**
+ * Forces the component to refresh.
+ */
+const forceUpdate = () => {
+  componentKey.value++;
+};
+
+/**
+ * Returns the amount of days between to days.
+ *
+ * @param {*} startDate Date from.
+ * @param {*} endDate Date to.
+ */
+const getDaysBetweenDates = (startDate, endDate) => {
+  const start = new Date(startDate);
+  const end = new Date(endDate);
+  const millisecondsPerDay = 1000 * 60 * 60 * 24;
+  return Math.round((end - start) / millisecondsPerDay);
+}
+</script>
+
+<template>
+  <div :key="componentKey">
+    <div class="button-container">
+      <div class="button-box">
+        <button @click="$emit('back')" data-cy="back-button">GÃ¥ tilbake</button>
+      </div>
+      <div class="button-box" data-cy="code-box">
+        <h2>Kode: {{ joinCode }}</h2>
+      </div>
+      <div class="button-box">
+        <RoadUsersComponent v-if="isContentLoaded" :goal-id="currentGoalId"/>
+        <GoalContributeComponent v-if="isContentLoaded" @refresh="loadContent" :goal-id="currentGoalId"/>
+      </div>
+    </div>
+    <h4>{{ statusMessage }}</h4>
+    <RoadComponent v-if="isContentLoaded" :amount-saved="amountSaved" :amount-per-day="amountPerDay"
+                   :goal-name="goalName" :amount-of-circles="amountOfCircles" :current-circle="currentCircle"
+                   :start-date="startDate"
+                   :end-date="endDate" :total-amount="totalAmount" :goal-id="currentGoalId" @back="$emit('back')"/>
+  </div>
+</template>
+
+
+<style scoped>
+.button-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 30px;
+  margin-top: 20px;
+  margin-bottom: 20px;
+}
+
+
+.button-box {
+  background-color: var(--accent-color);
+  border-radius: var(--border-radius-general);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 20px;
+  width: 70%;
+  text-align: center;
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+}
+
+
+button {
+  width: 70%;
+  margin-top: 10px;
+}
+</style>
diff --git a/src/components/goals/GoalContributeComponent.vue b/src/components/goals/GoalContributeComponent.vue
new file mode 100644
index 0000000000000000000000000000000000000000..7cf89098a2732e546fc63d5bb0796397048bccb3
--- /dev/null
+++ b/src/components/goals/GoalContributeComponent.vue
@@ -0,0 +1,80 @@
+/**
+* Script to update the amount a user has saved in a specific goal.
+*/
+<script setup>
+import { defineProps, ref } from 'vue';
+import GoalService from '@/services/internal/GoalService';
+import { defineEmits } from 'vue';
+import GoalErrorService from '@/services/error/GoalErrorService';
+
+const emit = defineEmits(['refresh']);
+const contribution = ref('');
+const statusMessage = ref('');
+
+const props = defineProps({
+  goalId: Number
+});
+
+/**
+ * If the contribution has a value in the inputfield, updates the contribution amount.
+ */
+const addContribution = async () => {
+  if (!contribution.value || isNaN(contribution.value) || contribution.value <= 0) {
+    return;
+  }
+  try {
+    await GoalService.addContribution(props.goalId, parseFloat(contribution.value));
+    contribution.value = '';
+    updateAndRefresh();
+  } catch (error) {
+    statusMessage.value = GoalErrorService.handleErrorAddContribution(error);
+  }
+};
+
+/**
+ * Refreshes the outer component.
+ */
+const updateAndRefresh = async () => {
+  emit('refresh');
+};
+
+const limitDigits = () => {
+  if (contribution.value && contribution.value.toString().length > 6) {
+    contribution.value = parseFloat(contribution.value.toString().slice(0, 6));
+  }
+};
+</script>
+
+<template>
+  <div>
+    <label for="contribution">
+      Legg til bidrag
+    </label>
+    <input id="contribution" data-cy="input-contribution" type="number" v-model="contribution" placeholder="f.eks: 100kr" @input="limitDigits"/>
+    <h4>{{ statusMessage }}</h4>
+    <button data-cy="add-button-contribution" @click="addContribution">Legg til!</button>
+  </div>
+</template>
+
+
+<style scoped>
+ul {
+  list-style-type: none;
+  padding: 0;
+}
+
+li {
+  margin-bottom: 8px;
+}
+
+input[type="number"] {
+  margin-right: 10px;
+  padding: 5px;
+  width: 80%;
+}
+
+button {
+  width: 70%;
+  margin-top: 10px;
+}
+</style>
\ No newline at end of file
diff --git a/src/components/goals/GoalsChoice.vue b/src/components/goals/GoalsChoice.vue
deleted file mode 100644
index 2a2f90c8243b0023f1fdc66474bdc533c4175a28..0000000000000000000000000000000000000000
--- a/src/components/goals/GoalsChoice.vue
+++ /dev/null
@@ -1,31 +0,0 @@
-<script setup>
-</script>
-
-<template>
-  <div>
-    <button @click="$emit('challenge')">Start en utfordring!</button>
-    <button @click="$emit('goal')">Start ett sparemål!</button>
-    <button @click="$emit('back')">GÃ¥ tilbake</button>
-  </div>
-</template>
-
-<style scoped>
-button {
-    background-color: #0056b3;
-    color: white;
-    border: none;
-    width: 60%;
-    padding: 10px 20px;
-    font-size: 16px;
-    border-radius: 20px;
-    box-shadow: 0 4px #003875;
-    cursor: pointer;
-    transition: all 0.3s ease;
-    margin: 40px auto;
-}
-
-button:hover {
-    background-color: #023366;
-    box-shadow: 0 6px #003875;
-}
-</style>
\ No newline at end of file
diff --git a/src/components/goals/GoalsMain.vue b/src/components/goals/GoalsMain.vue
deleted file mode 100644
index fcf0b15be4eb9fb1bac01cffc5143d4483b12247..0000000000000000000000000000000000000000
--- a/src/components/goals/GoalsMain.vue
+++ /dev/null
@@ -1,34 +0,0 @@
-<script setup>
-</script>
-
-<template>
-  <div>
-    <h3>
-      Du har ingen sparemål eller utfordringer gående akkuratt nå!
-      Klikk under for å sette opp ett nytt sparemål eller lage
-      deg en ny utfordring!
-    </h3>
-    <button @click="$emit('switch')">Kom i gang!</button>
-  </div>
-</template>
-
-<style scoped>
-button {
-    background-color: #0056b3;
-    color: white;
-    border: none;
-    width: 60%;
-    padding: 10px 20px;
-    font-size: 16px;
-    border-radius: 20px;
-    box-shadow: 0 4px #003875;
-    cursor: pointer;
-    transition: all 0.3s ease;
-    margin: 40px auto;
-}
-
-button:hover {
-    background-color: #023366;
-    box-shadow: 0 6px #003875;
-}
-</style>
diff --git a/src/components/goals/GoalsRoad.vue b/src/components/goals/GoalsRoad.vue
deleted file mode 100644
index 5ad124af1956025646240b219c300d70e033e0b7..0000000000000000000000000000000000000000
--- a/src/components/goals/GoalsRoad.vue
+++ /dev/null
@@ -1,101 +0,0 @@
-<script setup>
-import { ref , computed } from 'vue';
-
-const amount_of_circles = 30;
-const days_lost = ref(2);
-const amount_per_day = 100;
-const amount_saved = 400;
-const goal_name = 'Tur til Spania';
-
-
-
-
-const current_circle = computed(() => Math.floor((amount_saved / amount_per_day) - 1));
-
-const circlePositions = ref([]);
-circlePositions.value.push(Math.floor(Math.random() * 100)); 
-for (let i = 1; i < amount_of_circles; i++) {
-  let lastPosition = circlePositions.value[i - 1];
-  let newPosition = lastPosition + (Math.random() * 40 - 20); 
-  circlePositions.value.push(newPosition);
-}
-
-const getCircleStyle = (index) => ({
-  left: `${circlePositions.value[index]}px`,
-  backgroundColor: index <= current_circle.value ? 'green' : 'grey',
-  position: 'relative'
-});
-
-const getImageSrc = () => days_lost.value >= 3 
-  ? require("@/assets/img/pigCry.gif")
-  : require("@/assets/img/pigWave.gif");
-</script>
-
-<template>
-<div class="title">
-  <h1>{{ goal_name }}</h1><h2>Total saved: {{ amount_saved }}</h2>
-</div>
-  <div class="outer">
-    <div style="position: relative;">
-      <div v-for="index in amount_of_circles" :key="index" class="circle" :style="getCircleStyle(index - 1)"> {{ amount_per_day }}
-        <div v-if="index - 1 === current_circle" class="content-container">
-          <img :src="getImageSrc()" alt="Pig" class="pig-image">
-          <div class="small-circles-container">
-            <div v-for="n in 3" :key="n" class="small-circle" :style="{ backgroundColor: n <= days_lost ? 'gray' : 'red' }"></div>
-          </div>
-        </div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<style scoped>
-
-.title {
-  display: flex;
-  flex-direction: column;
-  gap: 0;
-  align-items: center;
-  justify-content: center; 
-  margin: 10px;
-}
-
-.outer {
-  margin: 30px;
-}
-
-.circle {
-  width: 50px;
-  height: 50px;
-  border-radius: 50%;
-  margin: 10px 0;
-  display: flex;
-  align-items: center;
-  justify-content: center; 
-}
-.content-container {
-  position: absolute;
-  top: 50%; 
-  left: 50%; 
-  transform: translate(-50%, -50%); 
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  margin-left: 75px;
-}
-.pig-image {
-  width: 60px;
-  height: 60px;
-}
-.small-circles-container {
-  display: flex;
-  justify-content: center;
-  margin-top: 5px;
-}
-.small-circle {
-  width: 10px;
-  height: 10px;
-  border-radius: 50%;
-  margin: 0 5px;
-}
-</style>
diff --git a/src/components/goals/JoinChallengeCompetition.vue b/src/components/goals/JoinChallengeCompetition.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ad4ab7291fa71631880377d5bb761b2ccd964b39
--- /dev/null
+++ b/src/components/goals/JoinChallengeCompetition.vue
@@ -0,0 +1,82 @@
+/**
+* Script to add a user to an existing shared challenge.
+*/
+<script setup>
+import { ref, defineEmits } from 'vue';
+import ChallengeService from '@/services/internal/ChallengeService';
+import GoalErrorService from '@/services/error/GoalErrorService';
+
+const statusMessage = ref('');
+const inputData = ref('');
+const selectedChallenge = ref(null);
+const challenges = ref([]);
+const emit = defineEmits(['create', 'back']);
+
+/**
+ * Takes the inputted code from the user and tries to join a challenge.
+ */
+const saveAndEmitInput = async () => {
+  try {
+    const result = await ChallengeService.joinChallenge(inputData.value);
+    await saveNext();
+    emit('goalSharedComponent', selectedChallenge.value.id);
+    console.log(result.data);
+  } catch (error) {
+    statusMessage.value = GoalErrorService.handleErrorJoinChallenge(error);
+  }
+};
+
+/**
+ * Finds the newly joined challenge and loads it.
+ */
+const saveNext = async () => {
+  try {
+    challenges.value = await ChallengeService.getChallenges(20, 0);
+  } catch (error) {
+    statusMessage.value = GoalErrorService.handleErrorGetChallenges(error);
+  }
+  let newestChallenge = challenges.value[0];
+  for (let i = 1; i < challenges.value.length; i++) {
+    if (challenges.value[i].joinCode === inputData.value) {
+      newestChallenge = challenges.value[i];
+    }
+  }
+  try {
+    selectedChallenge.value = await ChallengeService.getChallenge(newestChallenge.id);
+  } catch (error) {
+    statusMessage.value = GoalErrorService.handleErrorGetChallenge(error);
+  }
+};
+</script>
+
+<template>
+  <div class="buttons">
+    <input v-model="inputData" type="text" placeholder="Skriv inn kode her!" class="input-text" />
+    <h4>{{ statusMessage }}</h4>
+    <button @click="saveAndEmitInput">Bli med i utfordring!</button>
+    <button @click="$emit('back')">GÃ¥ tilbake</button>
+  </div>
+</template>
+
+<style scoped>
+.buttons {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-direction: column;
+  margin-top: 100px;
+}
+
+.input-text {
+  width: 60%;
+  padding: 10px 20px;
+  margin-bottom: 20px;
+  border-radius: var(--border-radius-general);
+  border: 2px solid var(--dark-color);
+}
+
+button {
+  width: 70%;
+  margin-top: 10px;
+}
+</style>
diff --git a/src/components/goals/JoinSharedGoal.vue b/src/components/goals/JoinSharedGoal.vue
new file mode 100644
index 0000000000000000000000000000000000000000..5eeb1ebfe42dda5af9ca076fccaa8fa9b1ddba7c
--- /dev/null
+++ b/src/components/goals/JoinSharedGoal.vue
@@ -0,0 +1,57 @@
+/**
+* Scrpit for adding a user to an already existing goal based on a joinCode.
+*/
+<script setup>
+import { ref, defineEmits } from 'vue';
+import GoalService from '@/services/internal/GoalService';
+import GoalErrorService from '@/services/error/GoalErrorService';
+
+const statusMessage = ref('');
+const inputData = ref('');
+const emit = defineEmits(['create', 'back']);
+
+/**
+ * Checks if the inputted code is valid and joins the correct challenge.
+ * Loads the challenge and routes to it.
+ */
+const saveAndEmitInput = async () => {
+  try {
+    const goal = await GoalService.joinGoal(inputData.value);
+    emit('goalComponent', goal.id);
+  } catch (error) {
+    statusMessage.value = GoalErrorService.handleErrorJoinGoal(error);
+  }
+};
+</script>
+
+<template>
+  <div class="buttons">
+    <input v-model="inputData" type="text" placeholder="Skriv inn kode her!" class="input-text" />
+    <h4>{{ statusMessage }}</h4>
+    <button @click="saveAndEmitInput">Bli med i sparemål!</button>
+    <button @click="$emit('back')">GÃ¥ tilbake</button>
+  </div>
+</template>
+
+<style scoped>
+.buttons {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-direction: column;
+  margin-top: 100px;
+}
+
+.input-text {
+  width: 60%;
+  padding: 10px 20px;
+  margin-bottom: 20px;
+  border-radius: var(--border-radius-general);
+  border: 2px solid var(--dark-color);
+}
+
+button {
+  width: 70%;
+  margin-top: 10px;
+}
+</style>
diff --git a/src/components/goals/MainButtons.vue b/src/components/goals/MainButtons.vue
new file mode 100644
index 0000000000000000000000000000000000000000..76f126ac3fdbcbcabec0d016d0949dfd9d254e49
--- /dev/null
+++ b/src/components/goals/MainButtons.vue
@@ -0,0 +1,63 @@
+<script setup>
+</script>
+
+<template>
+  <div class="main-button-container">
+    <div class="button-container">
+      <div class="button-box">
+        <img src="@/assets/img/pluss_icon.png" alt="Legg til sparemål og utfordringer icon">
+        <button @click="$emit('add')" data-cy="add-goal-button">Legg til sparemål og utfordringer</button>
+      </div>
+      <div class="button-box">
+        <img src="@/assets/img/goalIcon.png" alt="Oversikt over sparemål og utfordringer icon">
+        <button @click="$emit('overview')" data-cy="overview-goals-button">Oversikt over sparemål og utfordringer
+        </button>
+      </div>
+    </div>
+  </div>
+</template>
+
+
+<style scoped>
+.main-button-container {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: 100%;
+  width: 100%;
+}
+.button-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 30px;
+  box-sizing: border-box;
+  width: 90%;
+}
+
+.button-box {
+  background-color: var(--accent-color);
+  border-radius: var(--border-radius-general);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 20px;
+  margin: 10px;
+  width: 70%;
+  text-align: center;
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+}
+
+button {
+  width: 70%;
+  white-space: normal;
+}
+
+img {
+  width: 10%;
+  height: auto;
+  margin-bottom: 20px;
+}
+</style>
diff --git a/src/components/goals/OverviewComponent.vue b/src/components/goals/OverviewComponent.vue
new file mode 100644
index 0000000000000000000000000000000000000000..b7eb6ddb9ec603a0b41efde89a24c5b39dc3629f
--- /dev/null
+++ b/src/components/goals/OverviewComponent.vue
@@ -0,0 +1,386 @@
+/**
+* Component to view all goals and challenges under a user.
+* Also displays status on all goals and challenges.
+*/
+<script setup>
+import { ref, computed, onMounted } from 'vue';
+import ChallengeService from '@/services/internal/ChallengeService.js';
+import GoalService from '@/services/internal/GoalService.js';
+import { defineEmits } from 'vue';
+import GoalErrorService from '@/services/error/GoalErrorService';
+
+const emit = defineEmits(['challengeComponent', 'goalComponent', 'goalCompetitionComponent', 'goalSharedComponent']);
+
+const statusMessage = ref('');
+const challenges = ref([]);
+const detailedChallenges = ref([]);
+const currentDate = new Date();
+const selectedChallenge = ref(null);
+const goals = ref([]);
+const detailedGoals = ref([]);
+
+/**
+ * Routes to a selected challenge based on a challengeId.
+ * @param {*} id challengeId.
+ */
+const saveChallengeId = async (id) => {
+  try {
+    selectedChallenge.value = await ChallengeService.getChallenge(id);
+  } catch (error) {
+    statusMessage.value = GoalErrorService.handleErrorGetChallenge(error);
+  }
+  if (selectedChallenge.value.challengeType == 'SharedChallengeDto') {
+    emit('challengeCompetitionComponent', id, selectedChallenge.value.sharedChallengeId);
+  } else {
+    emit('challengeComponent', id);
+  }
+};
+
+/**
+ * Routes to a selected goal based on a goalId.
+ * @param {*} id goalId.
+ */
+const saveGoalId = async (id) => {
+  emit('goalComponent', id);
+};
+
+
+
+onMounted(async () => {
+  try {
+    challenges.value = await ChallengeService.getChallenges(15, 0);
+  } catch (error) {
+    statusMessage.value = GoalErrorService.handleErrorGetChallenges(error);
+  }
+  detailedChallenges.value = await Promise.all(
+    challenges.value.map(async (challenge) => {
+      try {
+        return await ChallengeService.getChallenge(challenge.id);
+      } catch (error) {
+        statusMessage.value = GoalErrorService.handleErrorGetChallenge(error);
+      }
+    })
+  );
+
+  try {
+    goals.value = await GoalService.getGoals(15, 0);
+  } catch (error) {
+    statusMessage.value = GoalErrorService.handleErrorGetGoals(error);
+  }
+  try {
+  detailedGoals.value = await Promise.all(
+    goals.value.map(async (goal) => {
+        const detailedGoal = await GoalService.getGoal(goal.id);
+        const amountSaved = await GoalService.getAmountSaved(goal.id, goal.name, goal.state);
+
+      return {
+        ...detailedGoal,
+        currentAmountSaved: amountSaved
+      };
+    })
+  );
+  } catch (error) {
+    statusMessage.value = 'Error: finner ingen sparemål under bruker.';
+  }
+});
+
+/**
+ * Sorts all currently goiing challenges into a list.
+ */
+const currentChallenges = computed(() => {
+  return detailedChallenges.value.filter(challenge => {
+    const endDate = new Date(challenge.endDate);
+    return endDate >= currentDate;
+  });
+});
+
+/**
+ * Sorts all previouslt going challenges into a list.
+ */
+const previousChallenges = computed(() => {
+  return detailedChallenges.value.filter(challenge => {
+    const endDate = new Date(challenge.endDate);
+    return endDate < currentDate;
+  });
+});
+
+/**
+ * Sorts all currently going goals into a list;
+ */
+const currentGoals = computed(() => {
+  return detailedGoals.value.filter(goal => {
+    const endDate = new Date(goal.endDate);
+    return endDate >= currentDate;
+  });
+});
+
+/**
+ * Sorts all previously going goals into a list;
+ */
+const previousGoals = computed(() => {
+  return detailedGoals.value.filter(goal => {
+    const endDate = new Date(goal.endDate);
+    return endDate < currentDate;
+  });
+});
+
+/**
+ * Gets the amount of days inbetween two dates.
+ * 
+ * @param {*} startDate From data.
+ * @param {*} endDate To date.
+ */
+const getDaysBetweenDates = (startDate, endDate) => {
+  const start = new Date(startDate);
+  const end = new Date(endDate);
+  const millisecondsPerDay = 1000 * 60 * 60 * 24;
+  return Math.round((end - start) / millisecondsPerDay);
+}
+
+const translateState = (state) => {
+  if(state == "FINISHED") {
+    return "Fullført";
+  } else {
+    return "Mislykket"
+  }
+}
+
+const translateProgress = (progress) => {
+  if(progress == "COMPLETED") {
+    return "Fullført";
+  } else {
+    return "Mislykket"
+  }
+}
+</script>
+
+<template>
+  <div class="button-container">
+    <div class="button-box">
+      <button @click="$emit('back')">GÃ¥ tilbake</button>
+      <button @click="$emit('add')">Legg til sparemål og utfordringer</button>
+      <h4>{{ statusMessage }}</h4>
+    </div>
+  </div>
+  <div class="button-container" id="complete-site">
+    <div class="button-box">
+      <h2>Utfordringer:</h2>
+
+      <div class="challenges" v-if="currentChallenges.length">
+        <h1>Nåværende</h1>
+        <div class="challenge"
+             role="button"
+             tabindex="0"
+             @keydown.enter="challenge.progress !== 'FAILED'
+             && challenge.progress !== 'COMPLETED' ? saveChallengeId(challenge.id) : null"
+             id="inner-box"
+             v-for="challenge in currentChallenges" :key="challenge.id"
+             @click="challenge.progress !== 'FAILED'
+             && challenge.progress !== 'COMPLETED' ? saveChallengeId(challenge.id) : null">
+          <h1>{{ challenge.title }}</h1>
+          <div class="progress">
+            <span v-if="challenge.progress === 'FAILED' || challenge.progress === 'COMPLETED'"
+              :class="{ 'completed': challenge.progress === 'COMPLETED', 'failed': challenge.progress === 'FAILED' }">
+              Status: {{ translateProgress(challenge.progress) }}
+            </span>
+            <span v-else>
+              Status: {{
+                (getDaysBetweenDates(challenge.startDate, new Date())) + '/' + (getDaysBetweenDates(challenge.startDate,
+                  challenge.endDate))
+              }}
+            </span>
+          </div>
+        </div>
+      </div>
+
+
+      <div class="challenges" v-if="previousChallenges.length">
+        <h1>Tidligere</h1>
+        <div class="challenge"
+             role="button"
+             tabindex="0"
+             @keydown.enter="challenge.progress !== 'FAILED' && challenge.progress !== 'COMPLETED' ? saveChallengeId(challenge.id) : null"
+             id="inner-box"
+             v-for="challenge in previousChallenges"
+             :key="challenge.id"
+             @click="challenge.progress !== 'FAILED' && challenge.progress !== 'COMPLETED' ? saveChallengeId(challenge.id) : null">
+          <h1>{{ challenge.title }}</h1>
+          <span class="progress">
+            <span v-if="challenge.progress === 'FAILED' || challenge.progress === 'COMPLETED'"
+              :class="{ 'completed': challenge.progress === 'COMPLETED', 'failed': challenge.progress === 'FAILED' }">
+              Status: {{ translateProgress(challenge.progress) }}
+            </span>
+            <span v-else>
+              Status: {{
+                (getDaysBetweenDates(challenge.startDate, new Date())) + '/' + (getDaysBetweenDates(challenge.startDate,
+                  challenge.endDate))
+              }}
+            </span>
+          </span>
+        </div>
+      </div>
+      <h1></h1>
+    </div>
+
+    <div class="button-box">
+      <h2>Sparemål:</h2>
+
+
+      <div class="challenges" v-if="currentGoals.length">
+        <h1>Nåværende</h1>
+        <div class="challenge"
+             role="button"
+             tabindex="0"
+             @keydown.enter="goal.state !== 'FAILED' && goal.state !== 'FINISHED' ? saveGoalId(goal.id) : null"
+             id="inner-box"
+             v-for="goal in currentGoals" :key="goal.id"
+             @click="goal.state !== 'FAILED' && goal.state !== 'FINISHED' ? saveGoalId(goal.id) : null">
+          <h1>{{ goal.title }}</h1>
+          <div class="progress">
+            <span v-if="goal.state === 'FAILED' || goal.state === 'FINISHED'"
+              :class="{ 'completed': goal.state === 'FINISHED', 'failed': goal.state === 'FAILED' }">
+              Status: {{ translateState(goal.state) }}
+            </span>
+            <span v-else>
+              Status: {{ goal.currentAmountSaved + '/' + goal.totalAmount }}
+            </span>
+          </div>
+        </div>
+      </div>
+
+
+      <div class="challenges" v-if="previousGoals.length">
+        <h1>Tidligere</h1>
+        <div class="challenge"
+             id="inner-box"
+             role="button"
+             tabindex="0"
+             @keydown.enter="goal.state !== 'FAILED' && goal.state !== 'FINISHED' ? saveGoalId(goal.id) : null"
+             v-for="goal in previousGoals"
+             :key="goal.id"
+             @click="goal.state !== 'FAILED' && goal.state !== 'FINISHED' ? saveGoalId(goal.id) : null">
+          <h1>{{ goal.title }}</h1>
+          <span class="progress">
+            <span v-if="goal.state === 'FAILED' || goal.state === 'FINISHED'"
+              :class="{ 'completed': goal.state === 'FINISHED', 'failed': goal.state === 'FAILED' }">
+              Status: {{ translateState(goal.state) }}
+            </span>
+            <span v-else>
+              Status: {{ goal.currentAmountSaved + '/' + goal.totalAmount }}
+            </span>
+          </span>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+#complete-site {
+  box-sizing: border-box;
+  margin-bottom: 2.5%;
+}
+#inner-box {
+  background-color: var(--white-general);
+}
+.challenge-container {
+  width: 100%;
+  max-height: 100vh;
+  overflow-y: auto;
+  padding: 5% 5% 20px 5%;
+  box-sizing: border-box;
+  scrollbar-width: none;
+}
+
+.button-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  margin-top: 20px;
+}
+
+.button-box {
+  background-color: var(--accent-color);
+  border-radius: var(--border-radius-general);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 20px;
+  width: 70%;
+  text-align: center;
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+  margin-bottom: 30px;
+}
+
+.completed {
+  color: var(--green-text);
+  background-color: white;
+}
+
+.failed {
+  color: var(--error-text);
+}
+
+.challenge-container::-webkit-scrollbar {
+  display: none;
+}
+
+.challenge {
+  border: 1px solid var(--dark-color);
+  width: 100%;
+  padding: 10px 20px;
+  font-size: 16px;
+  border-radius: var(--border-radius-general);
+  box-shadow: 0 4px var(--middle-color);
+  margin-top: 10px;
+  margin-bottom: 10px;
+}
+
+.challenges {
+  width: 70%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+
+.challenge:hover, .challenge:focus {
+  background-color: var(--middle-color);
+  box-shadow: 0 6px var(--light-color);
+}
+
+.indicator {
+  height: 20px;
+  width: 20px;
+  border-radius: 50%;
+  display: inline-block;
+}
+
+p {
+  display: block;
+  color: black;
+  font-size: 1rem;
+  word-wrap: break-word;
+  overflow-wrap: break-word;
+  word-break: normal;
+}
+
+h1 {
+  text-align: center;
+  font-size: large;
+  margin-bottom: 20px;
+}
+
+button {
+  width: 70%;
+  margin-top: 10px;
+}
+
+@media (max-width: 800px) {
+  #complete-site {
+    margin-bottom: 80px;
+  }
+}
+</style>
diff --git a/src/components/goals/ProgressComponent.vue b/src/components/goals/ProgressComponent.vue
new file mode 100644
index 0000000000000000000000000000000000000000..1eac01e70684caaf4597d4efcdf702f9ea3107b6
--- /dev/null
+++ b/src/components/goals/ProgressComponent.vue
@@ -0,0 +1,191 @@
+/**
+* Component to show the progress of a challenge.
+*/
+<script setup>
+import { ref, defineProps, onMounted, computed } from 'vue';
+
+const title = ref('');
+const startDate = ref(0);
+const endDate = ref(0);
+const difficulty = ref('');
+const description = ref('');
+const state = ref('');
+const progress = ref(0);
+
+const props = defineProps({
+  title: String,
+  startDate: Number,
+  endDate: Number,
+  difficulty: String,
+  description: String,
+  state: String
+});
+
+
+onMounted(async () => {
+  title.value = props.title;
+  startDate.value = props.startDate;
+  endDate.value = props.endDate;
+  difficulty.value = props.difficulty;
+  description.value = props.description;
+  state.value = props.state;
+  await updateProgress();
+});
+
+/**
+ * Formats a date to only include month, day, and year.
+ * 
+ * @param {*} dateString original date format.
+ */
+function formatDate(dateString) {
+  const date = new Date(dateString);
+  const options = { month: 'short', day: 'numeric', year: 'numeric' };
+  return date.toLocaleDateString('no-NO', options);
+}
+
+/**
+ * Updates the progress of the progressbar by fetching the progress value.
+ */
+const updateProgress = async () => {
+  const today = new Date();
+  progress.value = getProgress(startDate.value, endDate.value, today);
+};
+
+/**
+ * Checks if a challenge is loaded.
+ */
+const isDataEmpty = computed(() => {
+  return !props.title && props.startDate === 0 && props.endDate === 0 && !props.difficulty && !props.description && !props.state;
+});
+
+/**
+ * Calculates the progress value based on todays date.
+ * 
+ * @param {*} start Startdate of the challenge.
+ * @param {*} end Enddate of the challenge.
+ * @param {*} today Todays date.
+ */
+const getProgress = (start, end, today) => {
+  const startDt = new Date(start);
+  const endDt = new Date(end);
+  const todayDt = new Date(today);
+
+  if (todayDt < startDt) {
+    return 0;
+  } else if (todayDt > endDt) {
+    return 100;
+  } else {
+    const totalDuration = endDt - startDt;
+    const timeElapsed = todayDt - startDt;
+    return Math.floor((timeElapsed / totalDuration) * 100);
+  }
+};
+</script>
+
+<template>
+  <div v-if="!isDataEmpty" class="outer">
+    <div class="header">
+      <h1 class="main-title">{{ title }}</h1>
+      <p class="description">Beskrivelse: {{ description }}</p>
+    </div>
+    <div class="progress-bar">
+      <div class="range" :style="`--p:${progress}`"></div>
+    </div>
+    <div class="date-display">
+      <span class="start-date">{{ formatDate(startDate) }}</span>
+      <span class="divider">|</span>
+      <span class="end-date">{{ formatDate(endDate) }}</span>
+    </div>
+  </div>
+  <h1 v-else>Venter på att utfordringen skal bli godtatt :D</h1>
+</template>
+
+<style scoped>
+.progress-bar {
+  width: 100%;
+}
+.outer {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+  align-items: center;
+  justify-content: center;
+  background-color: var(--accent-color);
+  padding: 20px;
+  border-radius: var(--border-radius-general);
+  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+}
+
+.header {
+  text-align: center;
+}
+
+.main-title {
+  margin: 0;
+  font-size: 24px;
+  color: var(--black-general);
+  font-weight: bold;
+}
+
+.description {
+  margin-top: 5px;
+  color: var(--dark-color);
+  font-size: 16px;
+}
+
+.range {
+  position: relative;
+  background-color: var(--black-text);
+  height: 30px;
+}
+
+.range:before {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: 0;
+  height: 100%;
+  background-color: #5cf580;
+  z-index: 0;
+  animation: load 1.0s forwards linear;
+}
+
+.range:after {
+  content: attr(data-progress) '';
+  color: var(--black-text);
+  position: absolute;
+  left: 5%;
+  top: 50%;
+  transform: translateY(-50%) skewX(-30deg);
+  z-index: 1;
+}
+
+.date-display {
+  display: flex;
+  justify-content: space-between;
+  width: 100%;
+  padding: 0 20px;
+  font-size: 16px;
+  color: var(--black-text);
+}
+
+.start-date,
+.end-date {
+  font-weight: bold;
+}
+
+.divider {
+  color: var(--black-general);
+}
+
+h1 {
+  padding-top: 20px;
+  padding-bottom: 20px;
+}
+
+@keyframes load {
+  to {
+    width: calc(var(--p) * 1%);
+  }
+}
+</style>
diff --git a/src/components/goals/RoadComponent.vue b/src/components/goals/RoadComponent.vue
new file mode 100644
index 0000000000000000000000000000000000000000..e1ab1292cdeda7dd70536313ea624262a620d182
--- /dev/null
+++ b/src/components/goals/RoadComponent.vue
@@ -0,0 +1,428 @@
+/**
+* Component to visualise the progress of a goal.
+*/
+<script setup>
+import { ref, defineProps, onMounted, computed } from 'vue';
+import GoalService from '@/services/internal/GoalService';
+import BadgeService from '@/services/internal/BadgeService';
+import GoalErrorService from '@/services/error/GoalErrorService';
+
+const statusMessage = ref('');
+const days_lost = ref(0);
+const amount_saved = ref(0);
+const amount_per_day = ref(0);
+const goal_name = ref(0);
+const amount_of_circles = ref(0);
+const current_circle = ref(0);
+const circlePositions = ref([]);
+const start_date = ref(0);
+const end_date = ref(0);
+const total_amount = ref(0);
+const goal_id = ref(0);
+const showFailed = ref(false);
+const showFinished = ref(false);
+
+const props = defineProps({
+  amountSaved: Number,
+  amountPerDay: Number,
+  goalName: String,
+  amountOfCircles: Number,
+  currentCircle: Number,
+  startDate: Number,
+  endDate: Number,
+  totalAmount: Number,
+  goalId: Number
+});
+
+onMounted(async () => {
+  amount_saved.value = props.amountSaved;
+  amount_per_day.value = props.amountPerDay;
+  goal_name.value = props.goalName;
+  amount_of_circles.value = props.amountOfCircles;
+  current_circle.value = props.currentCircle;
+  start_date.value = props.startDate;
+  end_date.value = props.endDate;
+  total_amount.value = props.totalAmount;
+  goal_id.value = props.goalId;
+  await loadCirclePositions();
+  await updateDaysLost();
+  await handleState();
+});
+
+/**
+ * Calculates how many days the user is behind or infront of the expected
+ * progressamount.
+ */
+function updateDaysLost() {
+  const daysFromStart = pigCircle.value;
+  const currentCircleValue = props.currentCircle;
+
+  if (daysFromStart < currentCircleValue) {
+    days_lost.value = 0;
+  } else {
+    days_lost.value = Math.abs(daysFromStart - currentCircleValue) - 1;
+  }
+}
+
+/**
+ * Updates the state of the goal.
+ * If the goal is reached it updates to FINISHED.
+ * If the users falls three days behind it updates to FAILED.
+ */
+async function handleState() {
+  if (total_amount.value <= amount_saved.value) {
+    try {
+      await GoalService.updateProgress(goal_id.value, "FINISHED");
+    } catch (error) {
+      statusMessage.value = GoalErrorService.handleErrorUpdateProgress(error);
+    }
+    try {
+      await BadgeService.createBadge("NUMBER_OF_SAVING_GOALS_ACHIEVED");
+    } catch (error) {
+      statusMessage.value = GoalErrorService.handleErrorCreateBadge(error);
+    }
+    try {
+      await BadgeService.createBadge("AMOUNT_SAVED");
+    } catch (error) {
+      statusMessage.value = GoalErrorService.handleErrorCreateBadge(error);
+    }
+    showFinished.value = true;
+  } else if (days_lost.value >= 3) {
+    try {
+      GoalService.updateProgress(goal_id.value, "FAILED");
+    } catch (error) {
+      statusMessage.value = GoalErrorService.handleErrorUpdateProgress(error);
+    }
+    showFailed.value = true;
+  }
+}
+
+/**
+ * Formats the startDate to a shorter readable date.
+ */
+const formattedStartDate = computed(() => {
+  const date = new Date(start_date.value);
+  return date.toLocaleDateString('no-NO', {
+    month: 'short',
+    day: '2-digit'
+  });
+});
+
+/**
+ * Formats the endDate to a shorter readable date.
+ */
+const formattedEndDate = computed(() => {
+  const date = new Date(end_date.value);
+  return date.toLocaleDateString('no-NO', {
+    month: 'short',
+    day: '2-digit'
+  });
+});
+
+/**
+ * Computes todays date.
+ */
+const currentDate = computed(() => {
+  return new Date().toLocaleDateString('no-NO', {
+    month: 'long', day: 'numeric'
+  });
+});
+
+/**
+ * Caluculates the positions of the circles to make them
+ * look like a trail.
+ */
+const loadCirclePositions = async () => {
+  circlePositions.value[0] = 50;
+  for (let i = 1; i < amount_of_circles.value; i++) {
+    let lastPosition = circlePositions.value[i - 1];
+    let newPosition = lastPosition + (Math.random() * 20 - 10);
+    circlePositions.value.push(newPosition);
+  }
+}
+
+/**
+ * Updates the styling of a circle to indicate progress.
+ * 
+ * @param {*} index progress in amount.
+ */
+const getCircleStyle = (index) => ({
+  left: `${circlePositions.value[index]}%`,
+  backgroundColor: index <= current_circle.value ? 'var(--green-text)' : 'var(--middle-color)',
+  position: 'relative'
+});
+
+/**
+ * Calculates the position of the mascot based on the date.
+ */
+const pigImagePosition = computed(() => ({
+  left: `20px`,
+  position: 'absolute',
+  top: `${(pigCircle.value - 1) * 60}px`,
+  transform: 'translateX(-50%)'
+}));
+
+/**
+ * Gets the amount of days between two days.
+ * 
+ * @param {*} start From date.
+ * @param {*} end To date.
+ */
+const daysBetweenDates = (start, end) => {
+  const startDate = new Date(start);
+  const endDate = new Date(end);
+  const timeDiff = endDate - startDate;
+  return Math.floor(timeDiff / (1000 * 60 * 60 * 24));
+};
+
+/**
+ * Helper to calculate the mascots position based on unexpected
+ * values.
+ */
+const pigCircle = computed(() => {
+  const today = new Date();
+  const startDate = new Date(start_date.value);
+  const endDate = new Date(end_date.value);
+
+  if (today < startDate) return 0;
+  if (today > endDate) return (daysBetweenDates(startDate, endDate) + 1);
+
+  return (daysBetweenDates(startDate, today) + 1);
+});
+
+/**
+ * Helper to decide if pig should be hidden based on unexpected values.
+ */
+const showPig = computed(() => {
+  const today = new Date();
+  const startDate = new Date(start_date.value);
+  return today >= startDate;
+});
+
+/**
+ * Switched the image src of the mascot based on the amount of
+ * days the user is behind on progress.
+ */
+const getImageSrc = () => days_lost.value >= 3
+  ? require("@/assets/img/pigCry.gif")
+  : require("@/assets/img/pigWave.gif");
+</script>
+
+<template>
+  <div class="roadComponent-main-container">
+    <div data-cy="amount-title" class="title">
+      <h4>{{ statusMessage }}</h4>
+      <h1>{{ goal_name }}</h1>
+      <h2>Fremgang: {{ amount_saved }} / {{ total_amount }}</h2>
+    </div>
+    <div class="outer" style="position: relative;">
+      <h1 class="dates">{{ formattedStartDate }}</h1>
+      <div v-for="index in amount_of_circles" :key="index" class="circle" :style="getCircleStyle(index - 1)">
+        {{ amount_per_day }} kr
+      </div>
+      <div class="content-container" v-if="showPig" :style="pigImagePosition">
+        <img :src="getImageSrc()" alt="Pig" class="pig-image">
+        <div class="small-circles-container">
+          <div v-for="n in 3" :key="n" class="small-circle"
+            :style="{ backgroundColor: n <= days_lost ? 'gray' : 'red' }"></div>
+        </div>
+        <div class="date-display">{{ currentDate }}</div>
+        <div class="horizontal-line"></div>
+      </div>
+      <h1 class="dates">{{ formattedEndDate }}</h1>
+    </div>
+  </div>
+  <div v-if="showFinished" class="modal-overlay">
+    <div class="modalFinished">
+      <p>Gratulerer, du klarte målet ditt!</p>
+      <img src="@/assets/img/pigWave.gif" alt="Pig" class="pig-image">
+      <button @click="$emit('back')">Tilbake</button>
+    </div>
+  </div>
+  <div v-if="showFailed" class="modal-overlay">
+    <div class="modalFailed">
+      <p>Beklager, du klarte ikke målet ditt.</p>
+      <img src="@/assets/img/pigCry.gif" alt="Pig" class="pig-image">
+      <button @click="$emit('back')">Tilbake</button>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.roadComponent-main-container {
+  height: 100%;
+  width: 100%;
+  box-sizing: border-box;
+}
+
+.modal-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.5);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+
+button {
+  background-color: var(--dark-color);
+  color: var(--white-text);
+  border: none;
+  width: 60%;
+  padding: 10px 20px;
+  font-size: 16px;
+  border-radius: var(--border-radius-general);
+  box-shadow: 0 4px var(--middle-color);
+  cursor: pointer;
+  margin: 20px auto;
+  transition: background-color 0.3s ease, box-shadow 0.3s ease;
+
+}
+
+button:hover {
+  background-color: var(--middle-color);
+  box-shadow: 0 6px var(--light-color);
+}
+
+.modalFinished {
+  background: white;
+  padding: 20px;
+  border-radius: var(--border-radius-general);
+  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+
+.modalFailed {
+  background: white;
+  padding: 20px;
+  border-radius: var(--border-radius-general);
+  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+
+.center-circle {
+  left: 50%;
+  transform: translateX(-50%);
+}
+
+.date-display {
+  position: absolute;
+  margin-left: 140px;
+  top: 20%;
+  width: 100%;
+  text-align: center;
+  font-size: 16px;
+  z-index: 3;
+}
+
+.horizontal-line {
+  width: 180px;
+  height: 2px;
+  background-color: black;
+  position: absolute;
+  top: 50%;
+  left: 0;
+  transform: translateY(-50%);
+  z-index: 0;
+}
+
+.dates {
+
+  margin-right: 40px;
+  text-align: right;
+  font-size: 16px;
+  z-index: 3;
+}
+
+.h1 {
+  font-size: large;
+}
+
+.h2 {
+  font-size: medium;
+}
+
+.title {
+  display: flex;
+  flex-direction: column;
+  gap: 0;
+  align-items: center;
+  justify-content: center;
+  margin: 10px;
+}
+
+.outer {
+  margin: 30px;
+  box-sizing: border-box;
+}
+
+.circle {
+  width: 70px;
+  height: 50px;
+  color: white;
+  box-shadow: 0 14px 6px -6px gray;
+  border-radius: 50%;
+  margin: 10px 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  transition: box-shadow 0.3s ease-in-out;
+  z-index: 1;
+}
+
+.circle:hover {
+  box-shadow: 0 14px 6px -6px #eded9d;
+}
+
+.content-container {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  margin-left: 75px;
+}
+
+.pig-image {
+  width: 60px;
+  height: 60px;
+  border-radius: 60%;
+  z-index: 1;
+}
+
+.small-circles-container {
+  display: flex;
+  justify-content: center;
+  margin-top: 5px;
+}
+
+.small-circle {
+  width: 10px;
+  height: 10px;
+  border-radius: 50%;
+  margin: 0 5px;
+}
+
+button {
+  margin: 10px;
+}
+
+@media (max-width: 600px) {
+  .date-display,
+  .horizontal-line {
+    display: none;
+  }
+}
+</style>
diff --git a/src/components/goals/RoadUsersComponent.vue b/src/components/goals/RoadUsersComponent.vue
new file mode 100644
index 0000000000000000000000000000000000000000..611b370b01607594bdbed62c43efe7700c394060
--- /dev/null
+++ b/src/components/goals/RoadUsersComponent.vue
@@ -0,0 +1,59 @@
+/**
+* Script to fetch all contributors to a goal and display their names
+* and the amount they have contributed.
+*/
+<script setup>
+import { defineProps, onMounted, ref } from 'vue';
+import GoalService from '@/services/internal/GoalService';
+import GoalErrorService from '@/services/error/GoalErrorService';
+
+const props = defineProps({
+  goalId: Number
+});
+
+const statusMessage = ref('');
+const contributors = ref([]);
+
+onMounted(async () => {
+  if (props.goalId) {
+    try {
+      const response = await GoalService.getGoalContributors(props.goalId);
+      if (response && response.data && Array.isArray(response.data)) {
+        contributors.value = response.data;
+      } else {
+        console.error('No contributors found or bad response:', response);
+        if (Array.isArray(response)) {
+          contributors.value = response;
+        }
+      }
+    } catch (error) {
+      statusMessage.value = await GoalErrorService.handleErrorGetGoalContribution(error);
+    }
+  }
+});
+</script>
+
+<template>
+  <div data-cy="title">
+    <h4>Bidragsytere:</h4>
+    <h4>{{ statusMessage }}</h4>
+  </div>
+  <div>
+    <ul>
+      <li data-cy="list-of-names" v-for="contributor in contributors" :key="contributor.email">
+        {{ contributor.firstName }} {{ contributor.lastName }}: {{ contributor.contributedAmount.toLocaleString('en-US')}}
+      </li>
+    </ul>
+  </div>
+</template>
+
+<style scoped>
+ul {
+  list-style-type: none;
+  padding: 0;
+}
+
+li {
+  margin-bottom: 8px;
+}
+</style>
diff --git a/src/components/goals/SharedChoice.vue b/src/components/goals/SharedChoice.vue
new file mode 100644
index 0000000000000000000000000000000000000000..429da929a3eee8a085175a804ff2779d6d8a1347
--- /dev/null
+++ b/src/components/goals/SharedChoice.vue
@@ -0,0 +1,63 @@
+<script setup>
+</script>
+
+<template>
+  <div class="button-container">
+    <div class="button-box">
+      <img src="@/assets/img/pluss_icon.png" alt="Image description">
+      <button @click="$emit('createSharedGoal')">Start ett delt sparemål</button>
+    </div>
+    <div class="button-box">
+      <img src="@/assets/img/join.png" alt="Image description">
+      <button @click="$emit('joinSharedGoal')">Bli med i ett sparemål</button>
+    </div>
+    <div class="button-box">
+      <img src="@/assets/img/back.png" alt="Image description">
+      <button @click="$emit('back')">GÃ¥ tilbake</button>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.button-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 30px;
+  margin-top: 100px;
+}
+
+.dual-img-box {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: center;
+  gap: 30px;
+}
+
+.button-box {
+  background-color: var(--accent-color);
+  border-radius: var(--border-radius-general);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 20px;
+  margin: 10px;
+  width: 70%;
+  text-align: center;
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+}
+
+button {
+  width: 70%;
+  margin-top: 10px;
+}
+
+img {
+  width: 10%;
+  height: auto;
+  margin-bottom: 20px;
+}
+</style>
\ No newline at end of file
diff --git a/src/components/home/HomeMain.vue b/src/components/home/HomeMain.vue
index 1555b7484fca9160c46c36c56c77977abc803122..f46dfc00bfc08d45c94d68e9a96810e33a6ad857 100644
--- a/src/components/home/HomeMain.vue
+++ b/src/components/home/HomeMain.vue
@@ -1,20 +1,25 @@
 <script setup>
-
 </script>
 
 <template>
-  <div class="content-container">
-    <h1>Velkommen til Sparesti!</h1>
-    <p>Sparesti har spart sine kunder over 150.000kr! Kom i gang med din sparing her></p>
-    <button id="rounded-button">Log in!</button>
-  </div>
+    <div class="home-container">
+        <img src="@/assets/img/pigrich.png" alt="">
+        <h1>Bli rik med Sparesti!</h1>
+        <router-link to="/goals">
+            <button >Lag nytt sparemål</button>
+        </router-link>
+    </div>
 </template>
 
 <style scoped>
-.content-container {
+.home-container img {
+  filter: drop-shadow(0px 0px 10px rgba(0, 0, 0, 0.5));
+}
+
+.home-container {
   display: flex;
   flex-direction: column;
-  justify-content: space-evenly;
+  justify-content: center;
   align-items: center;
   height: 100vh; 
   text-align: center;
@@ -34,27 +39,21 @@ p {
   font-size: 30px;
 }
 
-#rounded-button {
-  background-color: #0056b3;
-  color: white;
-  border: none;
-  width: 60%;
-  padding: 10px 20px;
-  font-size: 16px;
-  border-radius: 20px;
-  box-shadow: 0 4px #003875;
-  cursor: pointer;
-  transition: all 0.3s ease;
-  margin: 40px auto;
+button {
+  width: 100%;
 }
 
-#rounded-button:hover {
-  background-color: #023366;
-  box-shadow: 0 6px #003875;
-}
+@media (max-width: 420px) {
+  .home-container img {
+    width: 80%;
+  }
+
+  h1 {
+    font-size: 32px;
+  }
 
-#rounded-button:active {
-  box-shadow: 0 2px #003875;
-  transform: translateY(2px);
+  p {
+    font-size: 24px;
+  }
 }
-</style>
+</style>
\ No newline at end of file
diff --git a/src/components/home/HomeWelcome.vue b/src/components/home/HomeWelcome.vue
new file mode 100644
index 0000000000000000000000000000000000000000..b27c30b7ad71e668beddc08b30d15b3a31930f9c
--- /dev/null
+++ b/src/components/home/HomeWelcome.vue
@@ -0,0 +1,98 @@
+<script setup>
+</script>
+
+<template>
+  <div class="content-container">
+    <div class="title">
+      <h1>Velkommen til </h1>
+      <img class="home-img" src="@/assets/img/logo_banner.png" alt="Home"/>
+    </div>
+    <p>Sparesti har spart sine kunder over 150.000kr! Kom i gang med din sparing her:</p>
+    <router-link to="/login">
+      <button>Logg in / registrer!</button>
+    </router-link>
+  </div>
+</template>
+
+<style scoped>
+.title {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  gap: 10px;
+}
+
+.home-img {
+  width: 200px; 
+  height: 40px;
+  z-index: 0;  
+  border-radius: var(--border-radius-general);
+}
+
+.content-container {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  height: 100vh;
+  text-align: center;
+  padding: 40px;
+  gap: 20px;
+}
+
+h1, p {
+  margin: 5px 0;
+}
+
+h1 {
+  color: black;
+  font-size: 40px;
+}
+
+p {
+  font-size: 30px;
+  color: var(--black-text);
+}
+
+button {
+  width: 100%;
+}
+
+@media (max-width: 768px) {
+  .home-img {
+    width: 150px;
+    height: 30px;
+  }
+
+  h1 {
+    font-size: 32px;
+  }
+
+  p {
+    font-size: 24px;
+  }
+
+  .content-container {
+    padding: 20px;
+  }
+}
+
+@media (max-width: 480px) {
+  .home-img {
+    width: 120px;
+    height: 25px;
+  }
+
+  h1 {
+    font-size: 28px;
+  }
+
+  p {
+    font-size: 20px;
+  }
+
+  button {
+    width: 80%;
+  }
+}
+</style>
diff --git a/src/components/infobar/InfoBar.vue b/src/components/infobar/InfoBar.vue
new file mode 100644
index 0000000000000000000000000000000000000000..2eb8f526c1d6dc600f03cc7f8da34f668a6049a4
--- /dev/null
+++ b/src/components/infobar/InfoBar.vue
@@ -0,0 +1,89 @@
+<script setup>
+import StockInfo from "@/components/infobar/StockInfo.vue";
+import WeeklyDiscount from "@/components/infobar/WeeklyDiscount.vue";
+</script>
+
+<template>
+  <div class="info-bar-container">
+    <div class="scrollable-content">
+      <div class="box">
+        <StockInfo></StockInfo>
+      </div>
+      <div class="box">
+        <WeeklyDiscount></WeeklyDiscount>
+      </div>
+
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.info-bar-container {
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+  max-height: 100%;
+  overflow: hidden;
+  box-sizing: border-box;
+  padding-right: 20px;
+}
+
+.goals-dropdown-items p {
+  color: black;
+  padding: 12px 16px;
+  text-decoration: none;
+  display: block;
+}
+
+.goals-dropdown-items p:hover {
+  background-color: var(--dark-color);
+  cursor: pointer;
+  color: var(--white-general);
+}
+
+.goals-dropdown-items p:active {
+  background-color: var(--middle-color);
+}
+
+.stats img {
+  width: 50%;
+  height: 50%;
+}
+
+.scrollable-content {
+  margin-top: 4%;
+  overflow-y: auto;
+  flex-grow: 1; 
+  scrollbar-width: none; 
+}
+
+.box {
+  background-color: var(--dark-color);
+  width: 100%;
+  padding: 20px;
+  border-radius: var(--border-radius-general);
+  margin-bottom: 10px;
+  color: var(--white-general);
+  box-sizing: border-box;
+}
+
+.box:last-child {
+  margin-bottom: 0;
+}
+
+.box input[type="text"], .box button {
+  padding: 10px;
+  margin-top: 10px;
+}
+
+
+.box button {
+  cursor: pointer;
+  background-color: #406882;
+  color: white;
+  border: none;
+  border-radius: var(--border-radius-general);
+}
+
+
+</style>
\ No newline at end of file
diff --git a/src/components/infobar/StatsPreview.vue b/src/components/infobar/StatsPreview.vue
new file mode 100644
index 0000000000000000000000000000000000000000..48bf817f9b0955e6884d474afac84bf2779d145f
--- /dev/null
+++ b/src/components/infobar/StatsPreview.vue
@@ -0,0 +1,227 @@
+<script setup>
+
+import {onMounted, ref, computed} from "vue";
+import StreakService from "@/services/internal/StreakService";
+import BadgeService from "@/services/internal/BadgeService";
+import GoalService from "@/services/internal/GoalService";
+import router from "@/router";
+import {useStore} from "vuex";
+
+const goalLength = ref(0);
+const streak = ref(0);
+const totalSaved = ref("");
+const totalBadges = ref(0);
+const goals = ref([]);
+const store = useStore();
+const isAuthenticated = computed(() => store.state.user.isAuthenticated);
+
+/**
+ * Fetches the streak, total saved and badges for authenticated user when the component is mounted.
+ */
+onMounted(() => {
+  if (isAuthenticated.value) {
+    getStreak();
+    getTotalSaved();
+    getGoals();
+    getBadges();
+  }
+});
+
+/**
+ * Fetches the current streak.
+ */
+async function getStreak() {
+  try {
+    let unParsedStreak = await StreakService.getStreak();
+    streak.value = unParsedStreak.numberOfDays;
+  } catch (error) {
+    streak.value = 0;
+  }
+}
+
+/**
+ * Fetches the total saved amount.
+ */
+async function getTotalSaved() {
+  try {
+    const savedAsNumber = await GoalService.getTotalSaved();
+    totalSaved.value = savedAsNumber + " kr";
+  } catch (error) {
+    totalSaved.value = 0;
+  }
+}
+
+/**
+ * Fetches the goals and sets the goalLength to the length of the goals array.
+ */
+async function getGoals() {
+  try {
+    goals.value = await GoalService.getGoals(15, 0);
+    goalLength.value = goals.value.length;
+  } catch (error) {
+    goals.value = [];
+    goalLength.value = 0;
+  }
+}
+
+/**
+ * Fetches the badges and sets the totalBadges to the length of the badges array.
+ */
+async function getBadges() {
+  try {
+    const badges = await BadgeService.getBadges(15, 0);
+    totalBadges.value = badges.length;
+  } catch (error) {
+    totalBadges.value = 0;
+  }
+}
+
+/**
+ * Routes to the profile page with the badges component.
+ */
+function handleBadgeClick() {
+  router.push({ name: "profile", params: { component: "Badges"} });
+}
+</script>
+
+<template>
+  <div class="stats-preview">
+    <div class="icons">
+      <div class="icon-container goals"
+           tabindex="0"
+           role="button">
+        <img src="@/assets/img/goalIcon.png" alt="goals">
+        <p>{{ goalLength }}</p>
+      </div>
+      <div class="icon-container badges"
+           @click="handleBadgeClick"
+           @keydown.enter="handleBadgeClick"
+           tabindex="0"
+           role="button"
+      >
+        <img src="@/assets/img/badgeIcon.png" alt="badges"/>
+        <p>{{ totalBadges }}</p>
+      </div>
+      <div class="icon-container">
+        <img src="@/assets/img/fireIcon.png" alt="streak">
+        <p>{{ streak }}</p>
+      </div>
+      <div class="icon-container">
+        <img src="@/assets/img/money.png" alt="money">
+        <p class="total-saved-text">{{ totalSaved }}</p>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.stats-preview {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background-color: white;
+  width: 100%;
+  height: 100px;
+  box-sizing: border-box;
+
+}
+
+.stats-preview p {
+  white-space: wrap;
+  text-overflow: ellipsis; /* Adds ellipsis for overflowed text */
+}
+
+.icons {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-around;
+  padding-right: 20px;
+  gap: 30px;
+}
+
+.goal-dropdown {
+  width: 300px;
+  top: calc(100% + 10px);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  z-index: 50;
+  margin: 0;
+  position: absolute;
+  left: 50%;
+  transform: translateX(-50%);
+  border: var(--black-general) solid 3px;
+  background-color: white;
+  border-radius: var(--border-radius-general);
+}
+
+.goal-dropdown::before {
+  content: "";
+  border-left: 10px solid transparent;
+  border-right: 10px solid transparent;
+  border-bottom: 10px solid var(--black-general);
+  position: absolute;
+  top: -12px;
+
+}
+
+.dropdown-item {
+  width: 100%;
+  height: 50px;
+  background-color: var(--white-general);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border-radius: var(--border-radius-general);
+  box-sizing: border-box;
+}
+
+.dropdown-item:hover {
+  background-color: var(--middle-color);
+  color: var(--white-general);
+  cursor: pointer;
+}
+
+.icon-container {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  position: relative;
+  text-align: center;
+  min-width: 0;
+}
+
+.goals:hover, .badges:hover {
+  cursor: pointer;
+}
+
+
+img {
+  width: 100%;
+  height: auto;
+}
+
+@media screen and (max-width: 800px) {
+  .stats-preview {
+    justify-content: flex-end;
+    width: 70%;
+    margin-left: 20%;
+  }
+
+  .icon-container {
+    flex-direction: row;
+    width: 100%;
+  }
+
+  .icons {
+    gap: 0;
+  }
+
+  img {
+    width: 50%;
+    height: auto;
+  }
+}
+</style>
\ No newline at end of file
diff --git a/src/components/infobar/StockInfo.vue b/src/components/infobar/StockInfo.vue
new file mode 100644
index 0000000000000000000000000000000000000000..1d8787d1b34e49e78b00d4016c1abd646f329919
--- /dev/null
+++ b/src/components/infobar/StockInfo.vue
@@ -0,0 +1,175 @@
+<script setup>
+import {onMounted, ref, watch} from 'vue';
+import StockService from '@/services/internal/StockService';
+import CookieService from "@/services/internal/CookieService";
+import axios from "axios";
+
+const stockInfo = ref(null);
+const searchQuery = ref("");
+const errorMessage = ref("");
+
+/**
+ * Watcher to reset error message and stock info when search query changes.
+ */
+watch([searchQuery], () => {
+  errorMessage.value = "";
+  stockInfo.value = null;
+})
+
+/**
+ * Retrieves stock symbol from cookie and stock data from backend when the component is mounted.
+ */
+onMounted(async () => {
+  let stockSearch = CookieService.getCookieWithConsent('stockSearch')
+  if (stockSearch) {
+    searchQuery.value = stockSearch;
+    await fetchStockInfo();
+  }
+});
+
+/**
+ * Retrieves stock data based on the search query.
+ */
+async function fetchStockInfo() {
+  if (!searchQuery.value.trim()) {
+    return;
+  }
+  try {
+    stockInfo.value = await StockService.fetchStockInfo(searchQuery.value);
+  } catch (error) {
+    if (axios.isAxiosError(error) && error.response) {
+      switch (error.response.status) {
+        case 404:
+          errorMessage.value = "Aksjedata ikke funnet for symbol: " + searchQuery.value;
+          break;
+        case 500:
+          errorMessage.value = "Intern serverfeil. Vennligst prøv igjen senere.";
+          break;
+        default:
+          errorMessage.value = error.response.data.errorMessage;
+          break;
+      }
+    } else {
+      console.error('Cannot connect to server.', error);
+      errorMessage.value = 'Kan ikke koble til serveren. Vennligst prøv igjen senere.';
+    }
+  }
+}
+
+/**
+ * Prevent entering space in input fields.
+ *
+ * @param event The keydown event object.
+ */
+function preventSpace(event) {
+  if (event.key === " " || event.code === "Space") {
+    event.preventDefault();
+  }
+}
+
+</script>
+
+<template>
+  <div class="stockInfo">
+    <h2>Børs!</h2>
+    <div class="stock-elements">
+      <label class="stock-input-label" for="stock-input-field">Aksje symbol:</label>
+
+      <div class="stock-input-container">
+        <input
+            id="stock-input-field"
+            class="stock-input-field"
+            type="text"
+            v-model="searchQuery"
+            @keydown="preventSpace"
+            placeholder="TSLA...">
+
+        <button tabindex="0" role="button" class="stock-button" @click="fetchStockInfo()"
+                :disabled="searchQuery === ''">Søk
+        </button>
+
+      </div>
+      <div class="stock-info" v-if="stockInfo">
+        <h5>{{ searchQuery.toUpperCase() }}: ${{ stockInfo.currentPrice }}</h5>
+        <p>Endring: {{ stockInfo.changePercent }}%</p>
+      </div>
+      <div class="error-message" v-if="errorMessage">{{ errorMessage }}</div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.stockInfo {
+  min-height: 180px;
+}
+
+.stock-elements {
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+}
+
+.stock-input-label {
+  margin-top: 3%;
+  margin-bottom: 3%;
+}
+
+.stock-input-container {
+  display: flex;
+  flex-direction: row;
+}
+
+.stock-input-field {
+  width: 100%;
+  padding: 10px;
+}
+
+.stock-button {
+  cursor: pointer;
+  background-color: black;
+  color: white;
+  border: none;
+  border-radius: var(--border-radius-general);
+  margin-left: 3%;
+  width: 100%;
+}
+
+.stock-button[disabled] {
+  background-color: #484040;
+  cursor: not-allowed;
+}
+
+.stock-info {
+  margin-top: 5%;
+}
+
+.stock-info p {
+  margin-top: 2%;
+}
+
+.error-message {
+  margin-top: 5%;
+}
+
+@media only screen and (max-width: 1000px) {
+  .stock-input-container {
+    display: flex;
+    flex-direction: column;
+  }
+
+  .stock-input-field {
+    width: 100%;
+    padding: 10px;
+  }
+
+  .stock-button {
+    color: white;
+    border: none;
+    border-radius: var(--border-radius-general);
+    margin-top: 5%;
+    width: 95%;
+    padding: 10px;
+  }
+}
+
+</style>
\ No newline at end of file
diff --git a/src/components/infobar/WeeklyDiscount.vue b/src/components/infobar/WeeklyDiscount.vue
new file mode 100644
index 0000000000000000000000000000000000000000..1ea5ae9d236fb00b3b0ef1802db7dea10c9f9a3d
--- /dev/null
+++ b/src/components/infobar/WeeklyDiscount.vue
@@ -0,0 +1,38 @@
+<script setup>
+
+</script>
+
+<template>
+  <div class="weekly-discount">
+    <h2>Ukens rabatter</h2>
+    <p>10% på Bilia</p>
+    <p>10% på Zara</p>
+    <p>25% på Power</p>
+    <p>25% på Power</p>
+    <p>25% på Power</p>
+    <p>25% på Power</p>
+    <p>25% på Power</p>
+    <p>25% på Power</p>
+    <p>25% på Power</p>
+    <p>25% på Power</p>
+    <p>25% på Power</p>
+    <p>25% på Power</p>
+    <p>25% på Power</p>
+    <p>25% på Power</p>
+    <p>25% på Power</p>
+    <p>25% på Power</p>
+    <p>25% på Power</p>
+    <p>25% på Power</p>
+  </div>
+</template>
+
+<style scoped>
+.weekly-discount {
+  min-height: 450px;
+}
+
+.weekly-discount p {
+  margin-top: 2%;
+}
+
+</style>
\ No newline at end of file
diff --git a/src/components/login/ForgotPassword.vue b/src/components/login/ForgotPassword.vue
new file mode 100644
index 0000000000000000000000000000000000000000..7866279658810ab21200a14c6b85dc74cd5dc92e
--- /dev/null
+++ b/src/components/login/ForgotPassword.vue
@@ -0,0 +1,396 @@
+<script setup>
+
+import SpinningWheelComponent from "@/components/other/SpinningWheelComponent.vue";
+import {ref, computed, watch} from "vue";
+import EmailService from "@/services/internal/EmailService";
+import VerificationView from "@/views/VerificationView.vue";
+import UserDetailsService from "@/services/internal/UserDetailsService";
+import ResetPasswordDTO from "@/models/user/ResetPasswordDTO";
+import UserDetailsInputService from "@/services/internal/UserDetailsInputService";
+import router from "@/router";
+import axios from "axios";
+
+const newPassword = ref("");
+const email = ref("");
+const statusMessage = ref("");
+const resettingPassword = ref(false);
+const confirmedReset = ref(false);
+const timerDeadline = ref(null);
+
+/**
+ * Watch for changes in email and newPassword to clear statusMessage.
+ */
+watch([email, newPassword], () => {
+  statusMessage.value = "";
+});
+
+/**
+ * Handles the forgot password functionality.
+ */
+const handleForgotPassword = async () => {
+
+  if(!await verifyIfEmailExists(email.value)) {
+    console.log("test")
+    return;
+  }
+
+  if(!validatePassword(newPassword.value)) {
+    return;
+  }
+
+  confirmedReset.value = true;
+
+  try {
+    const emailCodeExpirationDto = await EmailService.sendRegisterToken(email.value);
+    timerDeadline.value = new Date(emailCodeExpirationDto.expirationTimestamp);
+    resettingPassword.value = true;
+  } catch (error) {
+    handleError(error);
+  }
+
+  confirmedReset.value = false;
+};
+
+/**
+ * Verifies if the email exists.
+ *
+ * @param {string} email - The email address to verify.
+ * @returns {boolean} - Indicates if the email exists.
+ */
+async function verifyIfEmailExists(email){
+  try {
+    await EmailService.verifyEmailExistence(email);
+    return true;
+  } catch (error) {
+    console.log("test")
+    handleError(error);
+    return false;
+  }
+}
+
+/**
+ * Validates the password using UserDetailsInputService.
+ *
+ * @param {string} password - The password to validate.
+ * @returns {boolean} - Indicates if the password is valid.
+ */
+function validatePassword (password) {
+  try {
+    UserDetailsInputService.validatePasswordPattern(password);
+    return true;
+  } catch (error) {
+    statusMessage.value = error.message;
+    return false;
+  }
+}
+
+/**
+ * Handles the reset password functionality.
+ * Resets the password using UserDetailsService.
+ * Handles errors using ErrorService.
+ *
+ * @param {string} emailVerificationCode - The verification code for resetting the password.
+ */
+async function handleResetPassword(emailVerificationCode) {
+
+  const resetPasswordDto = new ResetPasswordDTO(email.value, emailVerificationCode, newPassword.value)
+
+  try {
+    await UserDetailsService.resetPassword(resetPasswordDto);
+    await router.push('/login');
+  } catch (error) {
+    handleError(error);
+  }
+}
+
+/**
+ * Handles errors and displaying appropriate messages to the user.
+ *
+ * @param {Error} error - The error object.
+ */
+function handleError(error) {
+  if (axios.isAxiosError(error) && error.response) {
+    switch (error.response.status) {
+      case 400:
+        statusMessage.value = "Ugyldig verifiseringskode";
+        break;
+      case 404:
+        statusMessage.value = "Bruker med e-postadresse '"+ email.value +"' eksisterer ikke."
+        break;
+      case 500:
+        statusMessage.value = "Intern serverfeil. Vennligst prøv igjen senere.";
+        break;
+      default:
+        statusMessage.value = error.response.data.errorMessage;
+        break;
+    }
+  } else {
+    console.error('Cannot connect to server.', error);
+    statusMessage.value = 'Kan ikke koble til serveren. Vennligst prøv igjen senere.';
+  }
+}
+
+/**
+ * Computed property to check if the form is filled out.
+ */
+const isFormFilledOut = computed(() => {
+  return (email.value.trim() && newPassword.value.trim());
+});
+
+</script>
+
+<template>
+  <div class="forgotPassword-container" v-if="!resettingPassword">
+    <div class="center-element-container">
+      <div class="left-info">
+        <img src="@/assets/img/pigrich.png" alt="Home"/>
+      </div>
+      <div class="form-container">
+        <h4>Tilbakestill passord</h4>
+        <form class="form-element-container" @submit.prevent="handleForgotPassword">
+          <label for="email-input-filed">E-postadresse</label>
+          <input
+              id="email-input-filed"
+              class="form-element"
+              type="email"
+              required
+              v-model="email"
+              placeholder="eksempel@mail.no"/>
+          <label for="password-input-filed">Nytt passord</label>
+          <input
+              id="password-input-filed"
+              class="form-element"
+              type="password"
+              required
+              v-model="newPassword"
+              placeholder="passord"/>
+          <button
+              :disabled="!isFormFilledOut"
+              @click.prevent="handleForgotPassword"
+          >
+            Send kode for tilbakestilling
+          </button>
+          <router-link to="/login" class="router-link">Tilbake til logg inn</router-link>
+          <p class="status-message" v-if="statusMessage">{{ statusMessage }}</p>
+        </form>
+      </div>
+    </div>
+    <spinning-wheel-component :visible="confirmedReset"/>
+  </div>
+
+  <div v-else>
+    <verification-view
+        @verification-submit="handleResetPassword"
+        @go-back="resettingPassword = false"
+        :email="email"
+        :timer-deadline="timerDeadline"
+        :error-message="statusMessage"
+    >
+    </verification-view>
+  </div>
+
+</template>
+
+<style scoped>
+
+.forgotPassword-container {
+  height: 100vh;
+  display: flex;
+  flex-direction: row;
+  justify-content: center;
+  box-sizing: border-box;
+  align-items: center;
+  padding: 5px;
+}
+
+.center-element-container {
+  display: flex;
+  width: 100%;
+  min-height: 420px;
+  flex-wrap: nowrap;
+  flex-direction: row;
+  justify-content: center;
+  box-sizing: border-box;
+}
+
+.left-info {
+  min-width: 350px;
+  max-width: 350px;
+  min-height: 280px;
+  display: flex;
+  flex: 1;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  text-align: center;
+  padding: 20px;
+  background-color: var(--dark-color);
+  border-radius: var(--border-radius-general) 0 0 var(--border-radius-general);
+}
+
+.form-container {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  padding: 20px;
+  min-width: 350px;
+  max-width: 350px;
+  min-height: 280px;
+  border-radius: 0 var(--border-radius-general) var(--border-radius-general) 0;
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+  background-color: var(--accent-color);
+  box-sizing: border-box;
+}
+
+.form-element-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-items: center;
+  width: 100%;
+  height: 100%;
+  margin-top: 7%;
+}
+
+.form-element {
+  box-shadow: 2px 2px 4px 0 rgba(0, 0, 0, 0.25);
+  box-sizing: border-box;
+  justify-content: left;
+}
+
+.form-element:focus {
+  outline: none;
+}
+
+option:hover {
+  background-color: var(--light-color);
+}
+
+select,
+input,
+textarea,
+button {
+  width: 100%;
+  padding: 10px;
+  margin-bottom: 10px;
+  border-radius: var(--border-radius-general);
+  border: 1px solid #ccc;
+  box-sizing: border-box;
+}
+
+.navigation span {
+  cursor: pointer;
+  font-size: 1em;
+  padding: 3px;
+  transition: color 0.3s, text-decoration 0.3s;
+  text-align: center;
+}
+
+select, .register option {
+  font-weight: normal !important;
+}
+
+img {
+  filter: drop-shadow(0px 0px 10px black);
+  width: 100%;
+}
+
+label {
+  align-self: flex-start;
+  padding: 2px;
+  margin-left: 3px;
+}
+
+.router-link {
+  color: var(--middle-color);
+  text-decoration: none;
+  margin-top: 2%;
+  display: inline-block;
+  transition: color 0.3s;
+  font-weight: bold;
+}
+
+.router-link:hover {
+  color: var(--dark-color);
+  text-decoration: underline;
+}
+
+.status-message {
+  margin-top: 4%;
+}
+
+@media screen and (max-width: 919px) {
+  .container {
+    flex-direction: column;
+    align-items: center;
+  }
+
+  .center-element-container {
+    width: 80%;
+    height: auto;
+  }
+
+  .left-info {
+    display: none;
+  }
+
+  .form-container {
+    width: 100%;
+    height: auto;
+    border-radius: var(--border-radius-general);
+  }
+}
+
+@media screen and (max-width: 400px) {
+  .container {
+    flex-direction: column;
+    padding: 10px;
+  }
+
+  .center-element-container {
+    width: 100%;
+    flex-direction: column;
+  }
+
+  .form-container, .left-info {
+    width: 100%;
+    min-width: auto;
+    max-width: none;
+    margin: 0 auto;
+    padding: 15px;
+    border-radius: var(--border-radius-general);
+  }
+
+  .form-element-container {
+    margin-top: 5%;
+  }
+
+  .form-element, select, input, textarea, button {
+    margin-bottom: 5px;
+  }
+
+  label {
+    margin-left: 0;
+  }
+
+  .router-link {
+    margin-top: 5%;
+  }
+
+  .status-message {
+    margin-top: 10px;
+  }
+
+  img {
+    width: 80%;
+    margin: 0 auto;
+  }
+
+  button {
+    height: auto;
+    padding: 8px 16px;
+  }
+}
+
+</style>
\ No newline at end of file
diff --git a/src/components/login/LoginComponent.vue b/src/components/login/LoginComponent.vue
index aada5b5a8924d1927cf85d78b67e0b929e310e15..9205bb9c5065dcb42a4979f54ca047bb765de953 100644
--- a/src/components/login/LoginComponent.vue
+++ b/src/components/login/LoginComponent.vue
@@ -1,106 +1,204 @@
 <script setup>
-import { ref, computed, onMounted, onUnmounted } from "vue";
-import UserService from '@/services/internal/UserService.js';
+import {ref, computed, defineProps} from "vue";
+import {useStore} from 'vuex';
+import {useRoute, useRouter} from "vue-router";
+import axios from "axios";
+import SpinningWheelComponent from "@/components/other/SpinningWheelComponent.vue";
 
-const input = ref({ email: "", password: "" });
+const input = ref({email: "", password: ""});
 const statusMessage = ref("");
+const router = useRouter();
+const store = useStore();
+const route = useRoute();
 
-const handleResize = () => {
-  const newIsMobileView = window.innerWidth <= 760;
-  if (isMobileView.value !== newIsMobileView) {
-    isMobileView.value = newIsMobileView;
-    console.log(`Window resized, isMobileView: ${isMobileView.value}`);
-  }
-};
+const isSubmitting = ref(false);
+const isLoginActive = ref(true);
+
+defineProps({
+  isLoginActive: Boolean
+});
 
+/**
+ * Handles the login click event, validates the form and performs login.
+ */
 const handleLoginClick = async () => {
-  if (input.value.email.trim() && input.value.password.trim()) {
-    try {
-      const response = await UserService.login(input.value.email, input.value.password);
-
-      console.log('Login successful:', response);
-      statusMessage.value = "Login successful!";
-    } catch (error) {
-      console.error("Login failed", error);
-      statusMessage.value = "Login failed. Please check your credentials.";
-    }
-  } else {
+  const email = input.value.email || "";
+  const password = input.value.password || "";
+  if (!(email.trim() && password.trim())) {
     statusMessage.value = "Please fill out both email and password.";
+    return
   }
-};
 
+  isSubmitting.value = true;
 
-const isMobileView = ref(window.innerWidth <= 760);
+  try {
+    await store.dispatch('loginUser', {
+      email: input.value.email.trim(),
+      password: input.value.password.trim()
+    });
+    await pushToRedirectOrHome();
+  } catch (error) {
+    handleError(error);
+  }
 
-const isFormFilledOut = computed(() => {
-  return input.value.email.trim() && input.value.password.trim();
-});
+  isSubmitting.value = false;
+};
 
-onMounted(() => {
-  window.addEventListener("resize", handleResize);
-});
+/**
+ * Redirects the user to a specified route or home if no specific redirect is set.
+ */
+async function pushToRedirectOrHome() {
+  try {
+    await router.push(route.query.redirect);
+  } catch (error) {
+    await router.push("/");
+  }
+}
 
-onUnmounted(() => {
-  window.removeEventListener("resize", handleResize);
-});
+/**
+ * Handles errors during login, displaying appropriate messages to the user.
+ * @param {Error} error - The error object from login attempt.
+ */
+const handleError = (error) => {
+  if (axios.isAxiosError(error) && error.response) {
+    switch (error.response.status) {
+      case 401:
+        statusMessage.value = "E-post eller passord var feil.";
+        break;
+      case 404:
+        statusMessage.value = "Bruker ikke funnet."
+        break;
+      case 500:
+        statusMessage.value = "Intern serverfeil. Vennligst prøv igjen senere.";
+        break;
+      default:
+        statusMessage.value = error.response.data.errorMessage;
+        break;
+    }
+  } else {
+    console.error('Cannot connect to server.', error);
+    statusMessage.value = 'Kan ikke koble til serveren. Vennligst prøv igjen senere.';
+  }
+};
 
-const isLoginActive = ref(true);
+/**
+ * Checks whether both email and password fields are filled out.
+ * @returns {boolean} True if both email and password fields contain non-empty strings, false otherwise.
+ */
+const isFormFilledOut = computed(() => {
+  return input.value.email?.trim() && input.value.password?.trim();
+});
 </script>
+
 <template>
-  <div class="container">
+  <div class="login-container">
     <div class="center-element-container">
-      <div
-        class="left-info"
-        v-if="!isMobileView.value">
+      <div class="left-info">
+        <img src="@/assets/img/pigrich.png" alt="Home"/>
       </div>
+
       <div class="form-container">
         <div class="navigation">
-          <span
-            @click="$emit('toggleView', true)"
-            :class="{
-              onFocusedLink: isLoginActive,
-              defaultLink: !isLoginActive,
-            }"
-            >Log In</span
-          >
-          <span
-            @click="$emit('toggleView', false)"
-            :class="{
-              onFocusedLink: !isLoginActive,
-              defaultLink: isLoginActive,
-            }"
-            >Sign Up</span
-          >
+          <h5
+              tabindex="0"
+              role="tab"
+              @keydown.enter="$emit('toggleView', true)"
+              @click="$emit('toggleView', true)"
+              :class="{
+            onFocusedLink: isLoginActive,
+            defaultLink: !isLoginActive,
+          }">Logg inn</h5>
+          <h5
+              tabindex="0"
+              role="tab"
+              @keydown.enter="$emit('toggleView', false)"
+              @click="$emit('toggleView', false)"
+              :class="{
+            onFocusedLink: !isLoginActive,
+            defaultLink: isLoginActive,
+          }">Registrer bruker</h5>
         </div>
-        <form class="form-element-container" @submit.prevent="handleLoginClick">
+
+        <form class="form-element-container"
+              @submit.prevent="handleLoginClick">
+          <label
+              for="email-input-filed">
+            E-postadresse
+          </label>
           <input
-            class="form-element"
-            for="email"
-            type="text"
-            required
-            v-model="input.email"
-            placeholder="email"
-          />
+              id="email-input-filed"
+              class="form-element"
+              type="email"
+              required
+              v-model="input.email"
+              placeholder="eksempel@mail.no"/>
+
+          <label
+              for="password-input-filed">Passord
+          </label>
           <input
-            class="form-element"
-            for="password"
-            required
-            v-model="input.password"
-            type="Password"
-            placeholder="password"
-          />
-          <button :disabled="!isFormFilledOut" @click="handleLoginClick">
-            Log In
+              id="password-input-filed"
+              class="form-element"
+              required
+              v-model="input.password"
+              type="Password"
+              placeholder="passord"/>
+
+          <button id="login-button" tabindex="0"
+                  @keydown.enter="handleLoginClick"
+                  :disabled="!isFormFilledOut"
+                  @click.prevent="handleLoginClick">
+            Logg inn
           </button>
-          <a href="/" class="forgot-password-link">Forgot password?</a>
-          <p v-if="statusMessage.trim().length > 0">{{ statusMessage }}</p>
+          <router-link to="/forgot-password" class="forgot-password-link">Glemt passord?</router-link>
+          <p class="status-message" v-if="statusMessage">{{ statusMessage }}</p>
         </form>
       </div>
     </div>
+    <spinning-wheel-component :visible="isSubmitting"/>
   </div>
 </template>
 
 <style scoped>
+.login-container {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 100vh;
+  width: 100vw;
+  padding: 5px;
+}
+
+.onFocusedLink,
+.defaultLink {
+  text-decoration: none;
+  color: var(--grey-text);
+}
+
+.onFocusedLink {
+  color: var(--black-text);
+  text-decoration: underline;
+}
+
+.defaultLink:hover {
+  text-decoration: underline;
+}
+
+.forgot-password-link {
+  color: var(--middle-color);
+  text-decoration: none;
+  margin-top: 2%;
+  display: inline-block;
+  transition: color 0.3s;
+  font-weight: bold;
+}
+
+.forgot-password-link:hover {
+  color: var(--dark-color);
+  text-decoration: underline;
+}
+
+
 .container {
   height: 100vh;
   display: flex;
@@ -112,43 +210,44 @@ const isLoginActive = ref(true);
 
 .center-element-container {
   display: flex;
-  margin: 5%;
   width: 100%;
-  min-height: 300px;
-  max-height: 350px;
+  min-height: 420px;
   flex-wrap: nowrap;
   flex-direction: row;
+  justify-content: center;
+  box-sizing: border-box;
 }
 
 .left-info {
-  padding: 20px;
-  background-color: var(--dark-color);
-  flex: 1;
-  border-radius: 8px 0 0 8px;
+  min-width: 350px;
+  max-width: 350px;
+  min-height: 280px;
   display: flex;
+  flex: 1;
   flex-direction: column;
   justify-content: center;
   align-items: center;
   text-align: center;
+  padding: 20px;
+  background-color: var(--dark-color);
+  border-radius: var(--border-radius-general) 0 0 var(--border-radius-general);
 }
 
-
 .form-container {
   flex: 1;
   display: flex;
   flex-direction: column;
   justify-content: space-between;
   padding: 20px;
-  min-width: 300px;
+  min-width: 350px;
   max-width: 350px;
   min-height: 280px;
-  max-height: 450px;
-  border-radius: 0 8px 8px 0;
+  border-radius: 0 var(--border-radius-general) var(--border-radius-general) 0;
   box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
   background-color: var(--accent-color);
+  box-sizing: border-box;
 }
 
-
 .form-element-container {
   display: flex;
   flex-direction: column;
@@ -168,29 +267,8 @@ const isLoginActive = ref(true);
   outline: none;
 }
 
-button {
-  background-color: var(--middle-color);
-  color: var(--white-text);
-  cursor: pointer;
-  width: 40%;
-  height: 40px;
-  border-radius: 10px;
-  transition: transform 0.3s ease;
-  box-shadow: 2px 2px 4px 0 rgba(0, 0, 0, 0.25);
-  outline: none;
-
-}
-
-button:hover {
-  background-color: var(--dark-color);
-  transform: translateY(-3px);
-}
-
-button:disabled {
-  background-color: var(--accent-color-dark);
-  transform: translateY(0);
-  cursor: not-allowed;
-  color: var(--grey-text);
+option:hover {
+  background-color: var(--light-color);
 }
 
 select,
@@ -200,17 +278,17 @@ button {
   width: 100%;
   padding: 10px;
   margin-bottom: 10px;
-  border-radius: 4px;
-  border: 1px solid var(--accent-color);
+  border-radius: var(--border-radius-general);
+  border: 1px solid #ccc;
   box-sizing: border-box;
 }
 
 .navigation {
   display: flex;
   justify-content: center;
-  align-items: center;
   gap: 20px;
   margin-bottom: 20px;
+  align-items: center;
 }
 
 .navigation span {
@@ -221,21 +299,31 @@ button {
   text-align: center;
 }
 
+select, .register option {
+  font-weight: normal !important;
+}
+
+img {
+  filter: drop-shadow(0px 0px 10px black);
+  width: 100%;
+}
+
+label {
+  align-self: flex-start;
+  padding: 2px;
+  margin-left: 3px;
+}
+
 .onFocusedLink {
-  color: var(--black-text);
-  text-decoration: underline;
+  cursor: pointer;
 }
 
 .defaultLink {
-  color: var(--grey-text);
-  text-decoration: none;
+  cursor: pointer;
 }
 
-.forgot-password-link {
-  text-decoration: none;
-  color: var(--dark-color);
-  margin-top: 10px;
-  display: inline-block;
+.status-message {
+  margin-top: 7%;
 }
 
 @media screen and (max-width: 919px) {
@@ -254,9 +342,59 @@ button {
   }
 
   .form-container {
-    width: 90%;
+    width: 100%;
     height: auto;
-    border-radius: 8px;
+    border-radius: var(--border-radius-general);
   }
 }
+
+
+@media screen and (max-width: 400px) {
+  .center-element-container {
+    width: 100%;
+    flex-direction: column;
+  }
+
+  .form-container, .left-info {
+    width: 100%;
+    min-width: auto;
+    max-width: none;
+    margin: 0 auto;
+    padding: 15px;
+    border-radius: var(--border-radius-general);
+    justify-content: flex-start;
+  }
+
+  .form-element-container {
+    margin-top: 5%;
+  }
+
+  .form-element, select, input, textarea, button {
+    margin-bottom: 5px;
+  }
+
+  label {
+    margin-left: 0;
+  }
+
+  .router-link {
+    margin-top: 5%;
+  }
+
+  .status-message {
+    margin-top: 10px;
+  }
+
+  img {
+    width: 80%;
+    margin: 0 auto;
+  }
+
+  button {
+    height: auto;
+    padding: 8px 16px;
+  }
+}
+
+
 </style>
diff --git a/src/components/login/RegisterComponent.vue b/src/components/login/RegisterComponent.vue
index 2d07cd4ad2e6ae4afed809c4405b1e9c9cffeaf4..89722cecd82e2663a57054078a04daa92f27a978 100644
--- a/src/components/login/RegisterComponent.vue
+++ b/src/components/login/RegisterComponent.vue
@@ -1,165 +1,345 @@
 <script setup>
-import {ref, computed, onMounted, onUnmounted} from "vue";
-import UserService from '@/services/internal/UserService.js';
-
-const input = ref({email: "", firstname: "", lastname: "", password: ""});
-const statusMessage = ref("");
-const form = ref(null);
-const isSubmitting = ref(false);
-
-const handleResize = () => {
-  const newIsMobileView = window.innerWidth <= 760;
-  if (isMobileView.value !== newIsMobileView) {
-    isMobileView.value = newIsMobileView;
-    console.log(`Window resized, isMobileView: ${isMobileView.value}`);
+import { ref, defineProps, computed } from 'vue';
+import { useStore } from 'vuex';
+import router from '@/router';
+import UserDetailsInputService from "@/services/internal/UserDetailsInputService";
+import EmailService from '@/services/internal/EmailService';
+import VerificationView from '@/views/VerificationView.vue';
+import SpinningWheelComponent from "@/components/other/SpinningWheelComponent.vue";
+import axios from "axios";
+
+const input = ref({ email: '', firstname: '', lastname: '', password: '', emailVerificationCode: '' });
+const statusMessage = ref('');
+const isSubmitted = ref(false);
+const isSubmitting = ref(false)
+const userStore = useStore();
+const timerDeadline = ref(null);
+
+defineProps({
+  isLoginActive: Boolean
+});
+
+/**
+ * Checks if the provided email address does not already exist in the system.
+ *
+ * @param {string} email - The email address to check.
+ * @returns {Promise<boolean>} - A promise that resolves to true if the email is available, false otherwise.
+ */
+async function verifyEmailIsAvailable(email){
+  try {
+    await EmailService.verifyEmailAvailability(email)
+    return true;
+  } catch (error) {
+    handleError(error);
+    return false;
+  }
+}
+
+/**
+ * Validates the email address against a specified pattern and checks its availability.
+ *
+ * @param {string} email - The email address to validate.
+ * @returns {Promise<boolean>} - A promise that resolves to true if the email is valid and available, false otherwise.
+ */
+const validateEmail = async (email) => {
+  try {
+    UserDetailsInputService.validateEmailPattern(email);
+    return await verifyEmailIsAvailable(email);
+  } catch (error) {
+    statusMessage.value = error.message;
+    return false;
+  }
+};
+
+/**
+ * Validates a given name against a specified pattern.
+ *
+ * @param {string} name - The name to validate.
+ * @returns {boolean} - True if the name matches the pattern,
+ * false if it does not or if validation has not been attempted.
+ */
+const validateName = (name) => {
+  try {
+    UserDetailsInputService.validateNamePattern(name);
+  } catch (error) {
+    statusMessage.value = error.message;
+    return false;
+  }
+  return true;
+};
+
+/**
+ * Validates a given password against a specified pattern.
+ *
+ * @param {string} password - The password to validate.
+ * @returns {boolean} - True if the password matches the pattern,
+ * false if it does not or if validation has not been attempted.
+ */
+const validatePassword = (password) => {
+  try {
+    UserDetailsInputService.validatePasswordPattern(password);
+  } catch (error) {
+    statusMessage.value = error.message;
+    return false;
   }
+  return true;
 };
 
-const isMobileView = computed(() => {
-  return window.innerWidth <= 760;
+/**
+ * Validates the input of all the input fields.
+ *
+ * @returns {boolean} True if all fields are successfully validated, false otherwise.
+ */
+const isFormValid = computed(() => {
+  return (
+      validateEmail(input.value.email) &&
+      validateName(input.value.firstname) &&
+      validateName(input.value.lastname) &&
+      validatePassword(input.value.password)
+  );
 });
 
+/**
+ * Checks whether all input-fields are filled out.
+ *
+ * @returns {boolean} True if all fields contain non-empty strings, false otherwise.
+ */
 const isFormFilledOut = computed(() => {
-  return input.value.email.trim() && input.value.firstname.trim() && input.value.password.trim();
+  return (
+      input.value.email.trim() &&
+      input.value.firstname.trim() &&
+      input.value.lastname.trim() &&
+      input.value.password.trim()
+  );
 });
 
+/**
+ * Handles the registration process upon clicking the register button. It validates the form and,
+ * if valid, proceeds to send a registration token.
+ *
+ * @returns {Promise<void>} - A promise that resolves when the registration attempt has concluded.
+ */
 const handleRegisterClick = async () => {
-  if (input.value.email.trim() && input.value.firstname.trim()
-  && input.value.lastname.trim() && input.value.password.trim()) {
+  if (isFormValid.value) {
+    isSubmitting.value = true;
     try {
-      const response = await UserService.register(input.value.email, 
-      input.value.firstname, input.value.lastname, input.value.password);
-
-      console.log('Register successful:', response);
-      statusMessage.value = "Register successful!";
+      const responseToken = await EmailService.sendRegisterToken(input.value.email);
+      timerDeadline.value = new Date(responseToken.expirationTimestamp)
+      isSubmitted.value = true;
+      statusMessage.value = ""
+      isSubmitting.value = false;
     } catch (error) {
-      console.error("Register failed", error);
-      statusMessage.value = "Register failed. Please check your credentials.";
+      handleError(error);
     }
-  } else {
-    statusMessage.value = "Please fill out both email and password.";
   }
 };
 
-onMounted(() => {
+/**
+ * Sends user information for registration once email verification is complete.
+ *
+ * @param {string} emailVerificationCode - The verification code to confirm email ownership.
+ */
+const sendVerificationAndUserInfo = async (emailVerificationCode) => {
+  try {
+    await userStore.dispatch('registerUser', {
+      email: input.value.email,
+      firstName: input.value.firstname,
+      lastName: input.value.lastname,
+      password: input.value.password,
+      emailVerificationCode
+    });
+
+    await router.push("/userDetails");
+  } catch (error) {
+    handleError(error);
+  }
+};
 
-  window.addEventListener("resize", handleResize);
-});
+/**
+ * Triggers email validation when the email input field loses focus.
+ *
+ * @returns {Promise<void>} - A promise that resolves after the email has been validated.
+ */
+async function handleEmailBlur(){
+  await validateEmail(input.value.email)
+}
 
-onUnmounted(() => {
-  window.removeEventListener("resize", handleResize);
-});
+/**
+ * Handles errors and displays appropriate messages to the user.
+ *
+ * @param {Error} error - The error object.
+ */
+function handleError(error) {
+  if (axios.isAxiosError(error) && error.response) {
+    switch (error.response.status) {
+      case 400:
+        statusMessage.value = "Ugyldig verifiseringskode.";
+        break;
+      case 409:
+        statusMessage.value = "E-postadresse er allerede i bruk."
+        break;
+      case 500:
+        statusMessage.value = "Intern serverfeil. Vennligst prøv igjen senere.";
+        break;
+      default:
+        statusMessage.value = error.response.data.errorMessage;
+        break;
+    }
+  } else {
+    console.error('Cannot connect to server.', error);
+    statusMessage.value = 'Kan ikke koble til serveren. Vennligst prøv igjen senere.';
+  }
+}
 
 </script>
+
 <template>
-  <div class="container register">
+  <div class="register-container" v-if="!isSubmitted">
     <div class="center-element-container">
-      <div
-        class="left-info"
-        v-if="!isMobileView">
+      <div class="left-info">
+        <img src="@/assets/img/pigrich.png" alt="Home"/>
       </div>
 
       <div class="form-container">
         <div class="navigation">
-          <span
-            @click="$emit('toggleView', true)"
-            :class="{
+          <h5
+              tabindex="0"
+              role="tab"
+              @keydown.enter="$emit('toggleView', true)"
+              @click="$emit('toggleView', true)"
+              :class="{
               onFocusedLink: isLoginActive,
               defaultLink: !isLoginActive,
             }"
-          >Log In</span
-          >
-          <span
-            @click="$emit('toggleView', false)"
-            :class="{
+          >Logg inn</h5>
+          <h5
+              tabindex="0"
+              role="tab"
+              @keydown.enter="$emit('toggleView', false)"
+              @click="$emit('toggleView', false)"
+              :class="{
               onFocusedLink: !isLoginActive,
-              defaultLink: isLoginActive,
-            }"
-          >Sign Up</span
-          >
+              defaultLink: isLoginActive}"
+          >Registrer bruker</h5>
         </div>
         <form
-          ref="form"
-          class="form-element-container"
-          @submit.prevent="handleRegisterClick"
+            ref="form"
+            class="form-element-container"
+            @submit.prevent="handleRegisterClick"
         >
+          <label for="email-input-filed">E-postadresse</label>
           <input
-            class="form-element"
-            for="email"
-            type="email"
-            required
-            v-model="input.email"
-            placeholder="E-mail"
+              id="email-input-filed"
+              class="form-element"
+              for="email"
+              type="email"
+              required
+              v-model="input.email"
+              placeholder="eksempel@mail.no"
+              @blur="handleEmailBlur"
           />
+          <label for="firstname-input-filed">Fornavn</label>
           <input
-            class="form-element"
-            for="firstname"
-            type="text"
-            required
-            v-model="input.firstname"
-            placeholder="firstname"
+              id="firstname-input-filed"
+              class="form-element"
+              for="firstname"
+              type="text"
+              required
+              v-model="input.firstname"
+              placeholder="fornavn"
           />
+          <label for="lastname-input-filed">Etternavn</label>
           <input
-            class="form-element"
-            for="lastname"
-            type="text"
-            required
-            v-model="input.lastname"
-            placeholder="lastname"
+              id="lastname-input-filed"
+              class="form-element"
+              for="lastname"
+              type="text"
+              required
+              v-model="input.lastname"
+              placeholder="etternavn"
           />
+          <label for="password-input-filed">Passord</label>
           <input
-            class="form-element"
-            for="password"
-            required
-            v-model="input.password"
-            type="Password"
-            placeholder="password"
+              id="password-input-filed"
+              class="form-element"
+              for="password"
+              required
+              v-model="input.password"
+              type="Password"
+              placeholder="passord"
           />
-          <button
-            type="submit"
-            @click="handleRegisterClick"
-            :disabled="!isFormFilledOut || isSubmitting"
-            :class="{ 'is-loading': isSubmitting }"
-          >
-            Continue
+          <button type="submit" data-cy="submit-button" @click.prevent="handleRegisterClick" :disabled="!isFormFilledOut">
+            Fortsett
           </button>
-          <p>{{ statusMessage }}</p>
+          <spinning-wheel-component :visible="isSubmitting"/>
+          <p class="status-message">{{ statusMessage }}</p>
         </form>
       </div>
     </div>
   </div>
+  <div v-else>
+    <verification-view
+        @verification-submit="sendVerificationAndUserInfo"
+        @go-back="isSubmitted = false"
+        :email="input.email"
+        :timer-deadline="timerDeadline"
+        :error-message="statusMessage"
+    >
+    </verification-view>
+  </div>
+
 </template>
 
 <style scoped>
-.container {
-  height: 100vh;
+.register-container {
   display: flex;
-  flex-direction: row;
   justify-content: center;
-  box-sizing: border-box;
-  align-items: center
+  align-items: center;
+  height: 100vh;
+  width: 100vw;
+  padding: 5px;
+}
+
+.onFocusedLink {
+  color: var(--black-text);
+}
+
+.defaultLink {
+  color: var(--grey-text);
+  text-decoration: none;
+}
+
+.defaultLink:hover, .onFocusedLink:hover {
+  text-decoration: underline;
+}
+
+.defaultLink, .onFocusedLink, .navigation span {
+  cursor: pointer;
 }
 
 .center-element-container {
   display: flex;
-  margin: 5%;
   width: 100%;
-  min-height: 300px;
-  max-height: 350px;
+  min-height: 420px;
   flex-wrap: nowrap;
   flex-direction: row;
+  justify-content: center;
+  box-sizing: border-box;
 }
 
 .left-info {
-  padding: 20px;
-  background-color: var(--dark-color);
-  flex: 1;
-  border-radius: 8px 0 0 8px;
+  min-width: 350px;
+  max-width: 350px;
+  min-height: 280px;
   display: flex;
+  flex: 1;
   flex-direction: column;
   justify-content: center;
   align-items: center;
   text-align: center;
+  padding: 20px;
+  background-color: var(--dark-color);
+  border-radius: var(--border-radius-general) 0 0 var(--border-radius-general);
 }
 
 .form-container {
@@ -168,13 +348,13 @@ onUnmounted(() => {
   flex-direction: column;
   justify-content: space-between;
   padding: 20px;
-  min-width: 300px;
+  min-width: 350px;
   max-width: 350px;
   min-height: 280px;
-  max-height: 450px;
-  border-radius: 0 8px 8px 0;
+  border-radius: 0 var(--border-radius-general) var(--border-radius-general) 0;
   box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
   background-color: var(--accent-color);
+  box-sizing: border-box;
 }
 
 .form-element-container {
@@ -196,30 +376,6 @@ onUnmounted(() => {
   outline: none;
 }
 
-button {
-  background-color: var(--middle-color);
-  color: var(--white-text);
-  border: none;
-  cursor: pointer;
-  width: 40%;
-  height: 40px;
-  border-radius: 10px;
-  transition: transform 0.3s ease;
-  box-shadow: 2px 2px 4px 0 rgba(0, 0, 0, 0.25);
-}
-
-button:hover {
-  background-color: var(--dark-color);
-  transform: translateY(-3px);
-}
-
-button:disabled {
-  background-color: var(--accent-color-dark);
-  transform: translateY(0);
-  cursor: not-allowed;
-  color: var(--grey-text);
-}
-
 option:hover {
   background-color: var(--light-color);
 }
@@ -231,7 +387,7 @@ button {
   width: 100%;
   padding: 10px;
   margin-bottom: 10px;
-  border-radius: 4px;
+  border-radius: var(--border-radius-general);
   border: 1px solid #ccc;
   box-sizing: border-box;
 }
@@ -245,51 +401,29 @@ button {
 }
 
 .navigation span {
-  cursor: pointer;
   font-size: 1em;
   padding: 3px;
   transition: color 0.3s, text-decoration 0.3s;
   text-align: center;
 }
 
-.onFocusedLink {
-  color: var(--black-text);
-  text-decoration: underline;
-}
-
-.defaultLink {
-  color: var(--grey-text);
-  text-decoration: none;
-}
-
 select, .register option {
   font-weight: normal !important;
 }
 
-.is-loading {
-  cursor: progress;
+img {
+  filter: drop-shadow(0px 0px 10px black);
+  width: 100%;
 }
 
-@keyframes spinner {
-  to {
-    transform: rotate(360deg);
-  }
+label {
+  align-self: flex-start;
+  padding: 2px;
+  margin-left: 3px;
 }
 
-.is-loading::after {
-  content: '';
-  box-sizing: border-box;
-  position: absolute;
-  top: 50%;
-  left: 50%;
-  width: 50px;
-  height: 50px;
-  margin-top: -25px;
-  margin-left: -25px;
-  border-radius: 50%;
-  border: 4px solid var(--accent-color-dark);
-  border-top-color: var(--dark-color);
-  animation: spinner .6s linear infinite;
+.status-message {
+  margin-top: 3%;
 }
 
 @media screen and (max-width: 919px) {
@@ -308,9 +442,57 @@ select, .register option {
   }
 
   .form-container {
-    width: 90%;
+    width: 100%;
+    height: auto;
+    border-radius: var(--border-radius-general);
+  }
+}
+
+
+@media screen and (max-width: 400px) {
+  .center-element-container {
+    width: 100%;
+    flex-direction: column;
+  }
+
+  .form-container, .left-info {
+    width: 100%;
+    min-width: auto;
+    max-width: none;
+    margin: 0 auto;
+    padding: 15px;
+    border-radius: var(--border-radius-general);
+    justify-content: flex-start;
+  }
+
+  .form-element-container {
+    margin-top: 5%;
+  }
+
+  .form-element, select, input, textarea, button {
+    margin-bottom: 5px;
+  }
+
+  label {
+    margin-left: 0;
+  }
+
+  .router-link {
+    margin-top: 5%;
+  }
+
+  .status-message {
+    margin-top: 10px;
+  }
+
+  img {
+    width: 80%;
+    margin: 0 auto;
+  }
+
+  button {
     height: auto;
-    border-radius: 8px;
+    padding: 8px 16px;
   }
 }
 </style>
diff --git a/src/components/news/NewsMain.vue b/src/components/news/NewsMain.vue
index 73b25ebaea17bc86a07c5c797ff8a9d8b19e4fa6..fe38644f4919c2f54c424f5bd28ee4873c1a1e83 100644
--- a/src/components/news/NewsMain.vue
+++ b/src/components/news/NewsMain.vue
@@ -1,123 +1,264 @@
 <script setup>
+import {onMounted, ref, computed} from 'vue';
+import NewsService from '@/services/internal/NewsService';
+import BadgeService from '@/services/internal/BadgeService';
+import axios from "axios";
+
+
+const newsItems = ref([]);
+const filterCategory = ref('');
+const categories = ref([]);
+let page = 0;
+const pageSize = 20;
+const errorMessage = ref('');
+const newsBadge = ref(null);
+
+/**
+ * Fetches news items and creates a badge
+ */
+onMounted(async () => {
+  try {
+    newsBadge.value = await BadgeService.createBadge("EDUCATION");
+  } catch (error) {
+    console.error('Failed to create news badge:', error);
+  }
+
+  try {
+    const data = await NewsService.getNews(page, pageSize);
+    newsItems.value = data.filter(item => item.title && item.articleUrl && item.category);
+    categories.value = [...new Set(newsItems.value.map(item => item.category))];
+  } catch (error) {
+    handleError(error)
+  }
+});
+
+/**
+ * Handles errors when fetching news items
+ * @param error - Error object
+ */
+const handleError = (error) => {
+  if (axios.isAxiosError(error) && error.response) {
+    errorMessage.value = 'Det oppstod en feil under henting av nyheter.';
+  } else {
+    console.error('Cannot connect to server.', error);
+    errorMessage.value = 'Kan ikke koble til serveren. Vennligst prøv igjen senere.';
+  }
+};
+
+/**
+ * Load more news items
+ */
+async function loadNewsItems() {
+  try {
+    errorMessage.value = '';
+    page++;
+    await NewsService.getNews(page, pageSize).then(data => {
+      newsItems.value = [...newsItems.value, ...data];
+    });
+  } catch (error) {
+    handleError(error)
+  }
+}
+
+/**
+ * Filters out invalid news items, and checks that they belong to the selected category
+ * @type {ComputedRef<UnwrapRefSimple<*>[]>} - List of news items
+ */
+const filteredNews = computed(() => {
+  return newsItems.value.filter(item =>
+      (filterCategory.value === '' || item.category === filterCategory.value) &&
+      item.title && item.articleUrl && item.category && item.imageUrl
+  );
+});
 
 </script>
 
 <template>
   <div class="news-container">
-    <h1 class="news-header">Nyheter</h1>
-    <div class="news-item">
-      <div class="news-content">
-        <h2 class="news-title">Tittel</h2>
-        <p class="news-info">info info info info info info info info info info info info info info info info</p>
-      </div>
-      <div class="news-image">Bilde</div>
-    </div>
 
-    <div class="news-item">
-      <div class="news-content">
-        <h2 class="news-title">Tittel</h2>
-        <p class="news-info">info info info info info info info info info info info info info info info info</p>
-      </div>
-      <div class="news-image">Bilde</div>
+    <div class="news-header">
+      <h1>Dagens Nyheter</h1>
+      <h4>Hentet fra dn.no</h4>
+      <p class="error-text" v-if="errorMessage" data-cy="error-text">{{ errorMessage }}</p>
     </div>
 
-    <div class="news-item">
-      <div class="news-content">
-        <h2 class="news-title">Tittel</h2>
-        <p class="news-info">info info info info info info info info info info info info info info info info</p>
-      </div>
-      <div class="news-image">Bilde</div>
-    </div>
+    <label for="setting-category">Kategorier:</label>
+    <select id="setting-category" class="dropdown" data-cy="category-dropdown" v-model="filterCategory">
+      <option value="">Alle kategorier</option>
+      <option v-for="category in categories" :key="category" :value="category">{{ category }}</option>
+    </select>
 
-    <div class="news-item">
-      <div class="news-content">
-        <h2 class="news-title">Tittel</h2>
-        <p class="news-info">info info info info info info info info info info info info info info info info</p>
-      </div>
-      <div class="news-image">Bilde</div>
+    <div v-for="(item, index) in filteredNews" :key="index" class="main-container" data-cy="news-container">
+      <a class="news-item" :href="item.articleUrl" target="_blank" data-cy="news-link">
+        <div class="news-content-container">
+          <p class="news-info" data-cy="news-info">{{ item.category }}</p>
+          <h2 class="news-title" data-cy="news-title">{{ item.title }}</h2>
+        </div>
+        <div class="news-image-container">
+          <img :src="item.imageUrl" alt="News Image">
+        </div>
+      </a>
     </div>
 
-    <div class="news-item">
-      <div class="news-content">
-        <h2 class="news-title">Tittel</h2>
-        <p class="news-info">info info info info info info info info info info info info info info info info</p>
-      </div>
-      <div class="news-image">Bilde</div>
-    </div>
+    <button role="button" tabindex="0" class="load-button" @click="loadNewsItems">Last inn mer...</button>
 
-    <div class="news-item">
-      <div class="news-content">
-        <h2 class="news-title">Tittel</h2>
-        <p class="news-info">info info info info info info info info info info info info info info info info</p>
-      </div>
-      <div class="news-image">Bilde</div>
-    </div>
   </div>
+
 </template>
 
 <style scoped>
+.load-button {
+  width: 60%;
+  color: var(--white-text);
+  background-color: var(--dark-color);
+  border-radius: var(--border-radius-general);
+  cursor: pointer;
+}
+
+.load-button:focus,
+.load-button:hover {
+  background-color: #172434;
+}
+
+
+.main-container {
+  width: 100%;
+}
+
+.news-item {
+  display: flex;
+  background-color: var(--dark-color);
+  border-radius: var(--border-radius-general);
+  padding: 1.5%;
+  margin: 1em 0;
+  flex-direction: row;
+  gap: 20px;
+  box-sizing: border-box;
+}
+
+.news-item:hover {
+  background-color: #223142;
+}
+
+.news-item:focus {
+  background-color: #223142;
+}
+
+
+.news-content-container {
+  display: flex;
+  flex-direction: column;
+  justify-content: flex-start;
+  align-items: flex-start;
+  flex: 0 0 60%;
+}
+
+.news-image-container {
+  width: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: flex-end;
+  box-sizing: border-box;
+  overflow: hidden;
+  border-radius: var(--border-radius-general)
+}
+
+.news-image-container img {
+  object-fit: contain;
+  max-height: 100%;
+  height: 100%;
+  width: auto;
+  border-radius: var(--border-radius-general);
+}
+
+a {
+  text-decoration: none;
+  border-radius: var(--border-radius-general);
+}
+
+.error-text {
+  color: var(--error-text);
+}
+
+.dropdown {
+  margin-bottom: 20px;
+}
+
 .news-container {
   width: 100%;
-  max-height: 100vh;
-  overflow-y: auto;
   display: flex;
   flex-direction: column;
   align-items: center;
-  padding: 5%;
+  padding: 2.5%;
   box-sizing: border-box;
   text-align: center;
-  scrollbar-width: none; /* For Firefox */
   justify-content: flex-start;
 }
 
-.news-container::-webkit-scrollbar {
-  display: none;
-}
-
 .news-header {
-  font-size: 2em;
-  margin-bottom: 0.5em;
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+  margin-bottom: 20px;
 }
 
-.news-item {
-  background-color: var(--accent-color);
-  border-radius: 8px;
-  padding: 5%;
-  margin-bottom: 1em;
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
+.news-info {
+  color: var(--white-text);
+  background-color: #010127;
+  padding: 5px 10px 5px 10px;
+  border-radius: var(--border-radius-general);
 }
 
 .news-title {
-  font-size: 1.2em;
-  margin-bottom: 10px;
+  font-size: 2em;
+  margin-top: 1em;
+  margin-bottom: 1em;
   justify-content: left;
   align-items: flex-start;
+  color: var(--white-text);
 }
 
-.news-info {
-  color: var(--grey-text);
+.news-title a {
+  color: var(--white-text);
+  text-decoration: none;
 }
 
-.news-image {
-  border: 2px solid var(--dark-color);
-  border-radius: 4px;
-  padding: 5%;
-  width: 80px;
-  text-align: center;
-  margin: 5%;
+.news-title a:hover {
+  text-decoration: underline;
 }
 
-
-
 @media screen and (max-width: 600px) {
   .news-item {
     flex-direction: column;
+    align-items: stretch;
+    min-height: auto;
   }
 
-  .news-image {
-    margin-top: 10px;
+  .news-content-container,
+  .news-image-container {
+    width: 100%;
   }
+
+  .news-image-container {
+    order: -1;
+    margin-bottom: 1em;
+  }
+
+  .news-info {
+    align-self: center;
+  }
+
+  .news-image-container img {
+    max-width: 100%;
+    height: auto;
+    object-fit: contain;
+  }
+
+  .news-content-container {
+    overflow: auto;
+    text-overflow: ellipsis;
+  }
+
+
 }
-</style>
\ No newline at end of file
+</style>
diff --git a/src/components/other/SpinningWheelComponent.vue b/src/components/other/SpinningWheelComponent.vue
new file mode 100644
index 0000000000000000000000000000000000000000..49a0b440cf7d0dc6fbd77db76d034ea4426a2a6e
--- /dev/null
+++ b/src/components/other/SpinningWheelComponent.vue
@@ -0,0 +1,34 @@
+<template>
+  <div class="spinner" v-if="visible"></div>
+</template>
+
+<script setup>
+import { defineProps } from 'vue';
+
+defineProps({
+  visible: Boolean
+});
+</script>
+
+<style scoped>
+.spinner {
+  box-sizing: border-box;
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  width: 50px;
+  height: 50px;
+  margin-top: -25px;
+  margin-left: -25px;
+  border-radius: 50%;
+  border: 4px solid var(--accent-color-dark);
+  border-top-color: var(--dark-color);
+  animation: spinner 0.6s linear infinite;
+}
+
+@keyframes spinner {
+  to {
+    transform: rotate(360deg);
+  }
+}
+</style>
diff --git a/src/components/profile/AccountsComponent.vue b/src/components/profile/AccountsComponent.vue
deleted file mode 100644
index 05dbf2e52cee2bdce9fd987f508a6d1e0a8b2f25..0000000000000000000000000000000000000000
--- a/src/components/profile/AccountsComponent.vue
+++ /dev/null
@@ -1,92 +0,0 @@
-<script setup>
-</script>
-
-<template>
-    <div class="account-container">
-        <h1>Kontoer</h1>
-        <div class="total-container">
-            <h2>Totalt: 13 000kr</h2>
-            <button class="add-account-button">Legg til konto eller kort</button>
-        </div>
-        <div class="account-item">
-            <div class="account-details">
-                <h3>BRUKSKONTO</h3>
-                <p>9999 99 99999</p>
-            </div>
-            <div class="account-balance">
-                <span>6500 kr</span>
-            </div>
-        </div>
-        <div class="account-item">
-            <div class="account-details">
-                <h3>SPAREKONTO</h3>
-                <p>9999 99 99999</p>
-            </div>
-            <div class="account-balance">
-                <span>6500 kr</span>
-            </div>
-        </div>
-        <!-- Repeat for each account... -->
-    </div>
-</template>
-
-<style scoped>
-.account-container {
-    margin: 2rem;
-}
-
-h1 {
-    text-align: center;
-    margin: 2rem;
-}
-
-.total-container {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    border-radius: 8px;
-}
-
-.total-container h2 {
-    color: var(--dark-color);
-    font-weight: bold;
-}
-
-.account-item {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    margin-bottom: 1rem;
-    padding: 1rem;
-    background-color: #D9D9D9;
-    border-radius: 8px;
-}
-
-.account-details {
-    display: flex;
-    flex-direction: column;
-    gap: 0.5rem;
-}
-
-.account-details h3 {
-    margin: 0;
-    color: var(--black-text);
-    font-size: 1rem;
-}
-
-.account-details p {
-    margin: 0;
-    color: var(--dark-color);
-    font-size: 0.8rem;
-}
-
-.account-balance {
-    font-size: 1.1rem;
-}
-
-.add-account-button {
-    padding: 0.5rem 1rem;
-    color: var(--dark-color);
-    cursor: pointer;
-}
-</style>
\ No newline at end of file
diff --git a/src/components/profile/BadgesComponent.vue b/src/components/profile/BadgesComponent.vue
index 54561f8378a12d881d29c9b58ae36cc1b24e97d1..80d0c496dd816d34f2e57969af0e189b40105a56 100644
--- a/src/components/profile/BadgesComponent.vue
+++ b/src/components/profile/BadgesComponent.vue
@@ -1,78 +1,218 @@
+<script setup>
+import {ref, onMounted, computed} from 'vue';
+import BadgeService from '@/services/internal/BadgeService';
+import {useStore} from 'vuex';
+
+const achievements = ref([]);
+const badges = ref([]);
+const totalBadges = ref(0);
+const store = useStore();
+const isAuthenticated = computed(() => store.state.user.isAuthenticated);
+
+const categoryTranslation = {
+    'SAVING_STREAK': 'Sparestreak',
+    'AMOUNT_SAVED': 'Beløp spart',
+    'NUMBER_OF_CHALLENGES_COMPLETED': 'Antall utfordringer utført',
+    'NUMBER_OF_SAVING_GOALS_ACHIEVED': 'Antall sparemål oppnådd',
+    'EDUCATION': 'Utdanning'
+};
+
+onMounted(() => {
+    if (!isAuthenticated.value) {
+        return;
+    }
+    getSharedBadge();
+    getAchievements();
+    getBadges();
+    linkBadgesToAchievements();
+});
+
+/**
+ * Fetches all possible achievements from the backend.
+ */
+async function getAchievements() {
+    try {
+        achievements.value = await BadgeService.getAllBadges();
+    } catch (error) {
+        console.error(error);
+    }
+}
+
+/**
+ * Fetches all achieved badges from the backend.
+ */
+async function getBadges() {
+    try {
+        badges.value = await BadgeService.getBadges(15, 0);
+        totalBadges.value = badges.value.length;
+    } catch (error) {
+        console.error(error);
+    }
+}
+
+/**
+ * Checks if the user has completed any shared saving goals with another user.
+ */
+ async function getSharedBadge() {
+    try {
+        await BadgeService.createBadge('NUMBER_OF_SAVING_GOALS_ACHIEVED'); 
+    } catch (error) {
+        console.error(error);
+    } 
+}
+
+/**
+ * Finds the achievement with the given category.
+ * @param {string} category The category of the achievement.
+ * @returns The achievement with the given category.
+ */
+const findAchievements = (category) => {
+    return achievements.value.find(achievement => achievement.category === category);
+}
+
+/**
+ * Links badges to the corresponding achievements.
+ */
+const linkBadgesToAchievements = () => {
+    badges.value.forEach(badge => {
+        const achievement = findAchievements(badge.achievement);
+        if (achievement) {
+            badge.linkedAchievements = achievement;
+        } else {
+            console.log('No achievement found for badge:', badge);
+        }
+    })
+}
+
+/**
+ * Checks if the threshold for the given category and index is achieved.
+ * @param category - The category of the badge.
+ * @param index - The index of the threshold.
+ */
+const isThresholdAchieved = (category, index) => {
+    const achievement = findAchievements(category);
+    if (achievement) {
+        const badgeWithCategory = badges.value.filter(b => b.achievement === category);
+        for (const badge of badgeWithCategory) {
+            if (badge.threshold >= achievement.thresholds[index]) {
+                return true;
+            }
+        }
+    }
+    return false;
+    
+}
+
+/**
+ * Gets the date the badge was achieved.
+ * @param {string} category The category of the badge.
+ * @returns The date the badge was achieved or an empty string if badge not achieved.
+ */
+const getDateAchieved = (category) => {
+    const badge = badges.value.find(badge => badge.achievement === category);
+    if (badge) {
+        return new Date(badge.achievementDate).toLocaleDateString();
+    }
+    return '';
+};
+
+</script>
+
 <template>
     <div class="badges-container">
         <h1>Badges</h1>
-        <div class="total-count">Totalt: 4</div>
+        <div class="total-count">Totalt: {{ totalBadges }}</div>
         <div class="badges-grid">
-            <div class="badge">
-                <img src="@/assets/img/badgeIcon.png" alt="ET Ã…RS STREAK" />
-                <p class="badge-name">ET Ã…RS STREAK</p>
+            <div class="badge" v-for="achievement in achievements" :key="achievement.id">   
+                <h3>{{ categoryTranslation[achievement.category] }}</h3>
+                <p>{{ achievement.description }}</p>
+                <div class="threshold" v-for="(threshold, index) in achievement.thresholds" :key="index">
+                    <img src="@/assets/img/badgeIcon.png" :class="{ 'greyed-out': !isThresholdAchieved(achievement.category, index) }" alt="Badge image">
+                    <template v-if="isThresholdAchieved(achievement.category, index)">
+                        <h6>Dato oppnådd: {{ getDateAchieved(achievement.category) }}</h6>
+                        <p>{{ threshold }}</p>
+                    </template>
+                    <template v-else>
+                        <p>{{ threshold }}</p>
+                    </template>
+                </div>             
             </div>
-
-            <div class="badge">
-                <img src="@/assets/img/badgeIcon.png" alt="OneAndDone" />
-                <p class="badge-name">OneAndDone</p>
-                <span class="badge-count">2/10</span>
-            </div>
-
-            <div class="badge">
-                <img src="@/assets/img/badgeIcon.png" alt="BUDSJETTBEIST" />
-                <p class="badge-name">BUDSJETTBEIST</p>
-            </div>
-            <!-- Repeat for other badges... -->
         </div>
     </div>
 </template>
 
-<script setup>
-</script>
-
 <style scoped>
-.badges-container {
-    margin: 2rem;
-}
-
 h1 {
-    text-align: center;
+  margin-top: 20px;
+}
+.badges-container {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    overflow: auto;  
 }
 
 .total-count {
-    font-size: 1.2rem;
-    margin-bottom: 1rem;
     color: var(--dark-color);
 }
 
 .badges-grid {
-    display: grid;
-    grid-template-columns: 33% 33% 33%;
-    gap: 1rem;
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: center;
+    flex-direction: row;
 }
 
 .badge {
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    text-align: center;
-    max-height: 300px;
+    max-width: 200px;
     border: 1px solid #ddd;
-    border-radius: 10px;
+    border-radius: var(--border-radius-general);
+    margin: 0.2rem;
     padding: 1rem;
-    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+    text-align: center;
+}
+
+.badge h3 {
+    height: 4em;
+    border-bottom: 1px solid #ddd;
+    text-align: center;
+    align-content: center;
+}
+
+.badge p {
+    height: 4em;
 }
 
 .badge img {
-    max-width: 100%;
-    max-height: 200px;
+    width: 100%; 
+    max-height: 100px;  
+    object-fit: contain;
+}
+
+.greyed-out {
+    filter: grayscale(100%);
+}
+
+.threshold {
+    align-items: center;
+    justify-content: center;
+    margin: 0.5rem;
 }
 
 .badge-name {
-    color: var(--black-text);
-    font-size: 1rem;
-    text-overflow: ellipsis; 
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    overflow: hidden; 
     margin-bottom: 0.5rem;
 }
 
 .badge-count {
     color: var(--dark-color);
-    font-size: 0.9rem;
+}
+
+@media screen and (max-width: 800px) {
+    .badges-grid {
+        width: 100vw;
+    }
 }
 </style>
\ No newline at end of file
diff --git a/src/components/profile/BankStatementsComponent.vue b/src/components/profile/BankStatementsComponent.vue
new file mode 100644
index 0000000000000000000000000000000000000000..38ca5542643ffe1e3a3e7e67ea792e42d20521cd
--- /dev/null
+++ b/src/components/profile/BankStatementsComponent.vue
@@ -0,0 +1,401 @@
+<script setup>
+import {ref, onMounted, watch} from 'vue';
+import BankStatementService from '@/services/internal/BankStatementService';
+import axios from "axios";
+
+const bankStatements = ref([]);
+const loading = ref(false);
+const errorMessage = ref('');
+let selectedBank = ref('');
+const selectedFile = ref(null);
+
+/**
+ * Fetches bank statements from backend on component mount.
+ */
+onMounted(async () => {
+  selectedBank.value = '';
+  try {
+    await fetchBankStatements();
+  } catch (error) {
+    handleError(error, `Fikk ikke hentet kontoutskrifter`);
+  }
+});
+
+/**
+ * Watches for changes in selectedBank and selectedFile and resets the error message if both are set.
+ */
+watch([selectedBank, selectedFile], () => {
+  if (selectedBank.value !== "Velg bank" && selectedFile.value) {
+    errorMessage.value = '';
+  }
+});
+
+/**
+ * Fetches bank statements from backend
+ */
+const fetchBankStatements = async () => {
+  loading.value = true;
+  try {
+    bankStatements.value = await BankStatementService.retrieveBankStatements();
+  } catch (error) {
+    handleError(error, `Fikk ikke hentet kontoutskrifter`);
+  }
+  loading.value = false;
+};
+
+/**
+ * Handles file selection event and uploads the file to backend.
+ * @param {Event} event - The file selection event.
+ */
+const handleFileSelection = async (event) => {
+  const file = event.target.files[0];
+  if (!selectedBank.value || selectedBank.value === "") {
+    event.target.value = null;
+    selectedFile.value = null;
+    errorMessage.value = 'Du må velge en bank før du laster opp en fil. Vennligst prøv igjen.';
+    return;
+  }
+
+  if (file && file.type === 'application/pdf') {
+    selectedFile.value = file;
+    errorMessage.value = '';
+    await uploadBankStatement();
+  } else {
+    event.target.value = null;
+    selectedFile.value = null;
+    errorMessage.value = 'Vennligst last opp en gyldig PDF-fil.';
+  }
+};
+
+/**
+ * Uploads the selected file to backend. If successful, analyzes the file.
+ */
+const uploadBankStatement = async () => {
+  if (!selectedFile.value) {
+    errorMessage.value = 'Ingen fil valgt for opplasting.';
+    return;
+  }
+  if (!selectedBank.value) {
+    errorMessage.value = 'Ingen bank valgt for opplasting.';
+    return;
+  }
+
+  loading.value = true;
+  try {
+    const formData = new FormData();
+    formData.append('file', selectedFile.value);
+    const statementId = await BankStatementService.addBankStatement(formData, selectedBank.value);
+    errorMessage.value = 'Kontoutskrift lastet opp. Behandler...';
+    await analyzeBankStatement(statementId);
+    await fetchBankStatements();
+    selectedFile.value = null;
+  } catch (error) {
+    handleError(error, `Feil ved opplasting av kontoutskrift.`);
+  } finally {
+    loading.value = false;
+    selectedFile.value = null;
+  }
+};
+
+/**
+ * Analyzes the bank statement with the given ID.
+ * @param {number} statementId - The ID of the bank statement to analyze.
+ */
+const analyzeBankStatement = async (statementId) => {
+  try {
+    await BankStatementService.analyzeBankStatement(statementId);
+    errorMessage.value = 'Kontoutskrift lastet opp og analysert suksessfullt.';
+  } catch (error) {
+    handleError(error, `Filen ble lastet opp, men det var feil under analysen`);
+    selectedFile.value = null;
+  }
+};
+
+/**
+ * Deletes the bank statement with the given ID.
+ * @param {number} id - The ID of the bank statement to delete.
+ */
+const deleteBankStatement = async (id) => {
+  try {
+    await BankStatementService.deleteBankStatement(id);
+    errorMessage.value = 'Kontoutskrift slettet.';
+    await fetchBankStatements();
+  } catch (error) {
+    handleError(error, `Kunne ikke slette kontoutskrift`);
+  }
+};
+
+/**
+ * Reanalyzes the bank statement with the given ID.
+ *
+ * @param {number} statementId - The ID of the bank statement to reanalyze.
+ */
+const reanalyzeBankStatement = async (statementId) => {
+  try {
+    loading.value = true;
+    errorMessage.value = 'Analyserer kontoutskrift på nytt...';
+    await analyzeBankStatement(statementId);
+  } catch (error) {
+    handleError(error, `Feil ved reanalyse av kontoutskrift`);
+  } finally {
+    loading.value = false;
+    await fetchBankStatements();
+  }
+};
+
+/**
+ * Handles errors and displays appropriate messages to the user.
+ *
+ * @param {Error} error - The error object.
+ * @param {String} message - The error message.
+ */
+function handleError(error, message) {
+  if (axios.isAxiosError(error) && error.response) {
+    errorMessage.value = message;
+  } else {
+    console.error('Cannot connect to server.', error);
+    errorMessage.value = 'Kan ikke koble til serveren. Vennligst prøv igjen senere.';
+  }
+}
+
+</script>
+
+<template>
+  <div class="main-container-bankStatements">
+    <h1>Kontoutskrifter</h1>
+    <div class="bank-statements-container">
+      <div class="bankStatement-input-container">
+        <h4>Legg til kontoutskrift</h4>
+
+        <select class="dropdown" v-model="selectedBank" data-cy="bank-select">
+          <option value="">Velg bank</option>
+          <option value="DNB">DNB</option>
+          <option value="HANDELSBANKEN">HANDELSBANKEN</option>
+          <option value="SPAREBANK1">SPAREBANK1</option>
+          <option value="ANNET">ANNET</option>
+        </select>
+
+        <label class="button">
+          Velg fil
+          <input type="file" @change="handleFileSelection" accept="application/pdf" multiple style="display: none;"
+                 data-cy="file-input">
+        </label>
+      </div>
+
+      <div v-if="errorMessage" class="error" data-cy="error-message">{{ errorMessage }}</div>
+
+      <div v-if="loading">Laster...</div>
+      <div class="scrollable-table">
+        <table class="statements-table">
+          <thead>
+          <tr>
+            <th>Kontonummer</th>
+            <th>Dato</th>
+            <th>Analyse</th>
+            <th>Slett</th>
+          </tr>
+          </thead>
+
+          <tbody>
+          <tr v-for="statement in bankStatements" :key="statement.id">
+            <td>{{ statement.accountNumber }}</td>
+            <td>{{ statement.timestamp }}</td>
+            <td v-if="statement.analysisIsPresent">Fullført</td>
+            <td v-else>
+              <button @click="reanalyzeBankStatement(statement.id)" class="reanalyze-button">Reanalyser</button>
+            </td>
+            <td>
+              <button @click="deleteBankStatement(statement.id)" class="delete-button">Slett</button>
+            </td>
+          </tr>
+          </tbody>
+        </table>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.error {
+  color: var(--error-text);
+  font-size: 14px;
+  text-align: center;
+  padding: 10px;
+  border-radius: var(--border-radius-general);
+  margin: 10px 0 0 10px;
+}
+
+button {
+  width: 100%;
+  height: 40px;
+}
+
+.dropdown {
+  height: 40px;
+  background-color: var(--accent-color-dark);
+}
+
+h4 {
+  display: flex;
+  justify-content: center;
+  align-content: center;
+  text-align: left;
+  text-justify: distribute-center-last;
+  padding: 0;
+}
+
+.input-buttons-container {
+  display: flex;
+  flex-direction: row;
+  width: 50%;
+  justify-content: space-evenly;
+  gap: 10px;
+}
+
+.main-container-bankStatements {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  flex-direction: column;
+  padding: 2.5%;
+  height: 100vh;
+  padding-top: 40px;
+}
+
+.bankStatement-input-container {
+  background-color: var(--accent-color);
+  border-radius: var(--border-radius-general);
+  padding: 2.5%;
+  display: flex;
+  align-items: center;
+  align-content: center;
+  justify-content: center;
+  gap: 10px;
+  margin: 20px 0 20px 0;
+}
+
+.bank-statements-container {
+  text-align: left;
+  height: 100%;
+  width: 100%;
+}
+
+.statements-table {
+  width: 100%;
+  table-layout: fixed;
+}
+
+.statements-table th,
+.statements-table td {
+  padding: 12px;
+  text-align: left;
+  border: 1px solid #ddd;
+  word-wrap: break-word;
+  overflow-wrap: break-word;
+}
+
+.statements-table th {
+  background-color: #f4f4f4;
+  color: #333;
+}
+
+.statements-table td {
+  background-color: #fff;
+}
+
+.statements-table tr:nth-child(even) {
+  background-color: #f9f9f9;
+}
+
+.statements-table th {
+  background-color: var(--dark-color);
+  color: white;
+}
+
+.delete-button {
+  background-color: var(--error-text);
+  color: white;
+  border: none;
+  padding: 4px 8px;
+  border-radius: var(--border-radius-general);
+  cursor: pointer;
+}
+
+.delete-button:hover {
+  background-color: var(--error-text);
+}
+
+.button {
+  background-color: var(--middle-color);
+  color: var(--white-text);
+  cursor: pointer;
+  width: 40%;
+  height: 40px;
+  border-radius: var(--border-radius-general);
+  transition: transform 0.3s ease, background-color 0.3s ease;
+  box-shadow: 2px 2px 4px 0 rgba(0, 0, 0, 0.25);
+  outline: none;
+  border-style: none;
+  text-align: center;
+  padding: 10px;
+}
+
+.button:hover {
+  background-color: var(--dark-color);
+  transform: translateY(-2px);
+}
+
+.button:disabled {
+  background-color: var(--light-color);
+  transform: translateY(0);
+  cursor: not-allowed;
+  color: var(--black-text);
+  opacity: 0.6;
+}
+
+.scrollable-table {
+  max-height: 75%;
+  overflow-y: auto;
+  width: 100%;
+  margin-bottom: 20px;
+}
+
+.button, .dropdown, h4 {
+  width: 100%;
+}
+
+@media screen and (max-width: 800px) {
+  .main-container-bankStatements {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    flex-direction: column;
+    padding: 2.5%;
+    height: 80vh;
+  }
+  .statements-table th:nth-child(2),
+  .statements-table td:nth-child(2) {
+    display: none;
+  }
+}
+
+@media screen and (max-width: 500px) {
+  .bankStatement-input-container h4 {
+    display: none;
+  }
+
+  .bankStatement-input-container {
+    flex-direction: column;
+    align-items: center;
+    padding: 10px;
+  }
+
+  .statements-table th:last-child, .statements-table td:last-child {
+    width: 100px;
+  }
+}
+
+.statements-table td:nth-child(1) {
+  word-break: break-all;
+}
+</style>
diff --git a/src/components/profile/MyProfileComponent.vue b/src/components/profile/MyProfileComponent.vue
index 0a10b9a7ce42e129523564759fd3d3784a7a9428..df88c83ea456c4baf75a8056a9d7c4d9658df512 100644
--- a/src/components/profile/MyProfileComponent.vue
+++ b/src/components/profile/MyProfileComponent.vue
@@ -1,9 +1,569 @@
 <script setup>
+import {ref, computed} from 'vue';
+import {useStore} from 'vuex';
+import UserDetailsService from '@/services/internal/UserDetailsService';
+import UserDetailsValidationService from '@/services/internal/UserDetailsInputService';
+import ChangePasswordDTO from '@/models/user/ChangePasswordDTO';
+import EmailService from "@/services/internal/EmailService";
+import VerificationView from "@/views/VerificationView.vue";
+import router from "@/router";
+import SpinningWheelComponent from "@/components/other/SpinningWheelComponent.vue";
+
+const store = useStore();
+const originalFirstName = computed(() => store.state.user.firstName);
+const originalLastName = computed(() => store.state.user.lastName);
+const originalEmail = computed(() => store.state.user.email);
+const originalIncome = computed(() => store.state.user.income);
+const originalSavingPercentage = computed(() => store.state.user.savingPercentage);
+const originalLivingStatus = computed(() => store.state.user.livingStatus);
+
+const firstName = ref(originalFirstName.value);
+const lastName = ref(originalLastName.value);
+const income = ref(originalIncome.value);
+const savingPercentage = ref(originalSavingPercentage.value)
+const livingStatus = ref(originalLivingStatus.value);
+const oldPassword = ref('');
+const newPassword = ref('');
+const confirmPassword = ref('');
+
+const statusMessages = ref({
+  userInfo: '',
+  changePassword: '',
+  deleteUser: ''
+});
+
+const editMode = ref(false);
+
+const deleteConfirmation = ref(false);
+const userDeletionInProgress = ref(false);
+const deletionConfirmed = ref(false);
+
+const timerDeadline = ref(null);
+
+/**
+ * Toggles the edit mode for user details
+ */
+async function toggleEdit() {
+  if (editMode.value) {
+    await saveAllChanges();
+  }
+  editMode.value = !editMode.value;
+  if (!editMode.value) {
+    firstName.value = originalFirstName.value;
+    lastName.value = originalLastName.value;
+    income.value = originalIncome.value;
+    savingPercentage.value = originalSavingPercentage.value;
+    livingStatus.value = originalLivingStatus.value;
+  }
+}
+
+/**
+ * Update first name if changed and not empty.
+ */
+async function updateFirstName() {
+  if (!firstName.value.trim()) {
+    statusMessages.value.userInfo = 'Fornavn kan ikke være tomt.';
+    return;
+  }
+  if (firstName.value.trim() !== originalFirstName.value) {
+    try {
+      await store.dispatch('changeFirstName', firstName.value.trim());
+      statusMessages.value.userInfo = 'Fornavn oppdatert.';
+    } catch (error) {
+      console.error("Failed to update first name.", error);
+      statusMessages.value.userInfo = error.response.data.errorMessage;
+    }
+  }
+}
+
+/**
+ * Update last name if changed and not empty.
+ */
+async function updateLastName() {
+  if (!lastName.value.trim()) {
+    statusMessages.value.userInfo = 'Etternavn kan ikke være tomt.';
+    return;
+  }
+  if (lastName.value.trim() !== originalLastName.value) {
+    try {
+      await store.dispatch('changeLastName', lastName.value.trim());
+      statusMessages.value.userInfo = 'Etternavn oppdatert.';
+    } catch (error) {
+      console.error("Failed to update last name.", error);
+      statusMessages.value.userInfo = error.response.data.errorMessage;
+    }
+  }
+}
+
+/**
+ * Update income if changed.
+ */
+async function updateIncome() {
+  if (!income.value) {
+    statusMessages.value.userInfo = 'Inntekt kan ikke være tom.';
+    return;
+  }
+  if (income.value !== originalIncome.value) {
+    try {
+      await store.dispatch('changeIncome', income.value);
+      statusMessages.value.userInfo = 'Inntekt oppdatert.';
+    } catch (error) {
+      console.error("Failed to update income.", error);
+      statusMessages.value.userInfo = error.response.data.errorMessage;
+    }
+  }
+}
+
+/**
+ * Update saving percentage if changed.
+ */
+async function updateSavingPercentage() {
+  if (!savingPercentage.value) {
+    statusMessages.value.userInfo = 'Spareprosent kan ikke være tom.';
+    return;
+  }
+  if (savingPercentage.value !== originalSavingPercentage.value) {
+    try {
+      await store.dispatch('changeSavingPercentage', savingPercentage.value);
+      statusMessages.value.userInfo = 'Spareprosent oppdatert.';
+    } catch (error) {
+      console.error("Failed to update income.", error);
+      statusMessages.value.userInfo = error.response.data.errorMessage;
+    }
+  }
+}
+
+/**
+ * Update living status if changed.
+ */
+async function updateLivingStatus() {
+  if (livingStatus.value !== originalLivingStatus.value) {
+    try {
+      await store.dispatch('changeLivingStatus', livingStatus.value);
+      statusMessages.value.userInfo = 'Bosituasjon oppdatert.';
+    } catch (error) {
+      console.error("Failed to update living status.", error);
+      statusMessages.value.userInfo = error.response.data.errorMessage;
+    }
+  }
+}
+
+/**
+ * Saves all changes to the user details
+ */
+async function saveAllChanges() {
+  await updateFirstName();
+  await updateLastName();
+  await updateIncome();
+  await updateSavingPercentage()
+  await updateLivingStatus();
+}
+
+/**
+ * Saves the new password for the user
+ */
+async function saveNewPassword() {
+  if (!canSavePassword()) {
+    return;
+  }
+
+  try {
+    const changePasswordDTO = new ChangePasswordDTO(oldPassword.value, newPassword.value);
+    await UserDetailsService.changePassword(changePasswordDTO);
+    oldPassword.value = '';
+    newPassword.value = '';
+    confirmPassword.value = '';
+    statusMessages.value.changePassword = 'Passord endret.';
+  } catch (error) {
+    statusMessages.value.changePassword = 'Failed to change password. ' + error.response.data.errorMessage;
+  }
+}
+
+/**
+ * Checks if the password can be saved
+ */
+function canSavePassword() {
+  try {
+    UserDetailsValidationService.validateEditPassword(newPassword.value, confirmPassword.value, oldPassword.value);
+    return true;
+  } catch (err) {
+    statusMessages.value.changePassword = err.message;
+    return false;
+  }
+}
+
+/**
+ * Initiates the user deletion confirmation process.
+ */
+function confirmDelete() {
+  deleteConfirmation.value = true;
+}
+
+/**
+ * Cancels the user deletion confirmation process.
+ */
+function cancelDelete() {
+  deleteConfirmation.value = false;
+}
+
+/**
+ * Handles the user deletion confirmation
+ */
+async function handleDeleteConfirmation() {
+
+  deletionConfirmed.value = true;
+
+  try {
+    const emailCodeExpirationDto = await EmailService.sendRegisterToken(originalEmail.value);
+    timerDeadline.value = new Date(emailCodeExpirationDto.expirationTimestamp);
+    userDeletionInProgress.value = true;
+  } catch (error) {
+    statusMessages.value.deleteUser = error.response.data.errorMessage;
+  }
+
+  deleteConfirmation.value = false;
+  deletionConfirmed.value = false;
+}
+
+/**
+ * Handles the user deletion process
+ */
+async function handleDeleteUser(emailVerificationCode) {
+  try {
+    await UserDetailsService.deleteUser(emailVerificationCode);
+    await store.dispatch("logout");
+    await router.push('/login');
+  } catch (error) {
+    statusMessages.value.deleteUser = 'Failed to delete user. ' + error.response.data.errorMessage;
+  }
+}
 
 </script>
 
 <template>
-  <div>
+  <div class="my-profile-main-container" v-if="!userDeletionInProgress">
+
     <h1>Min Profil</h1>
+
+    <div class="my-profile-container">
+
+      <div class="user-info">
+
+        <div class="title-with-button">
+          <h2>Bruker informasjon</h2>
+          <div class="action-buttons">
+            <button @click="toggleEdit" class="edit-button" data-cy="edit-button">{{
+                editMode ? 'Ferdig' : 'Rediger'
+              }}
+            </button>
+            <button @click="confirmDelete" class="delete-button">Slett bruker</button>
+          </div>
+        </div>
+
+        <div class="field-row">
+          <label for="firstName">Fornavn:</label>
+          <div class="input-icon-container">
+            <input type="text" id="firstName" v-model="firstName" :disabled="!editMode">
+          </div>
+        </div>
+
+        <div class="field-row">
+          <label for="lastName">Etternavn:</label>
+          <div class="input-icon-container">
+            <input type="text" id="lastName" v-model="lastName" :disabled="!editMode">
+          </div>
+        </div>
+
+        <div class="field-row">
+          <label for="email">Email:</label>
+          <div class="input-icon-container">
+            <input type="email" id="email" v-model="originalEmail" disabled>
+          </div>
+        </div>
+
+        <div class="field-row">
+          <label for="income">Inntekt (kr)</label>
+          <div class="input-icon-container">
+            <input type="number" id="income" v-model="income" :disabled="!editMode">
+          </div>
+        </div>
+
+        <div class="field-row">
+          <label for="savingPercentage">Prosent (%) av inntekt å spare</label>
+          <div class="input-icon-container">
+            <input type="number" id="savingPercentage" v-model="savingPercentage" :disabled="!editMode">
+          </div>
+        </div>
+
+        <div class="field-row" id="last-field-row-element">
+          <label for="livingStatus">Bosituasjon:</label>
+          <select id="livingStatus" v-model="livingStatus" :disabled="!editMode">
+            <option value="0">Annet</option>
+            <option value="1">Bor alene</option>
+            <option value="2">Par uten barn</option>
+            <option value="3">Par med barn</option>
+          </select>
+        </div>
+
+        <p class="status-message" v-if="statusMessages.userInfo" data-cy="user-status-message">
+          {{ statusMessages.userInfo }}</p>
+
+      </div>
+
+      <form @submit.prevent="saveNewPassword" class="password-form">
+
+        <h2>Endre passord</h2>
+
+        <div class="field-row">
+          <label for="oldPassword">Gammelt passord:</label>
+          <input type="password" id="oldPassword" v-model="oldPassword" data-cy="oldPassword-input">
+        </div>
+
+        <div class="field-row">
+          <label for="newPassword">Nytt passord:</label>
+          <input type="password" id="newPassword" v-model="newPassword">
+        </div>
+
+        <div class="field-row">
+          <label for="confirmPassword">Bekreft nytt passord:</label>
+          <input type="password" id="confirmPassword" v-model="confirmPassword">
+        </div>
+
+        <button class="form-button" type="submit" data-cy="edit-password-button">Lagre passord</button>
+
+        <p class="status-message" v-if="statusMessages.changePassword" data-cy="password-status-message">
+          {{ statusMessages.changePassword }}</p>
+
+      </form>
+
+    </div>
+
+    <div v-if="deleteConfirmation" class="delete-confirmation-box">
+      <div class="confirmation-content">
+        <p>Er du sikker på at du vil slette brukeren?</p>
+        <button @click="cancelDelete" class="cancel-delete-button">Avbryt</button>
+        <button @click="handleDeleteConfirmation" class="confirm-delete-button">Slett</button>
+      </div>
+    </div>
+
+    <spinning-wheel-component :visible="deletionConfirmed"></spinning-wheel-component>
+
+  </div>
+
+  <div v-else>
+    <verification-view
+        @verification-submit="handleDeleteUser"
+        @go-back="userDeletionInProgress = false"
+        :email="originalEmail"
+        :timer-deadline="timerDeadline"
+        :error-message="statusMessages.deleteUser"
+    >
+    </verification-view>
   </div>
+
 </template>
+
+<style scoped>
+h2 {
+  margin-bottom: 15px;
+}
+
+button {
+  padding: 5px 10px;
+  color: white;
+  border: none;
+  border-radius: var(--border-radius-general);
+  transition: background-color 0.3s ease;
+}
+
+.my-profile-main-container {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  min-height: 100vh;
+  padding: 2.5%;
+  box-sizing: border-box;
+  overflow-y: auto;
+}
+
+
+.my-profile-container {
+  text-align: left;
+  width: 100%;
+  max-height: 85vh;
+  overflow-y: auto;
+  padding: 20px;
+  box-sizing: border-box;
+  margin-bottom: 20px;
+}
+
+
+.title-with-button {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 15px;
+}
+
+.action-buttons {
+  display: flex;
+  gap: 20px;
+}
+
+.edit-button {
+  background-color: var(--middle-color);
+}
+
+.edit-button:hover {
+  background-color: var(--dark-color);
+}
+
+.delete-button {
+  background-color: #af0000;
+}
+
+.delete-button:hover {
+  background-color: #6c0101;
+}
+
+.field-row {
+  margin-bottom: 20px;
+}
+
+.field-row label {
+  display: block;
+}
+
+.input-icon-container {
+  display: flex;
+  align-items: center;
+}
+
+.field-row input {
+  width: 100%;
+  padding: 8px;
+  box-sizing: border-box;
+}
+
+.field-row button.check-button {
+  width: auto;
+  height: auto;
+  border: none;
+  cursor: pointer;
+  margin-left: 10px;
+}
+
+.field-row select {
+  padding: 8px;
+  box-sizing: border-box;
+}
+
+.user-info,
+.password-form {
+  background-color: #f2f2f2;
+  padding: 20px;
+  border-radius: var(--border-radius-general);
+}
+
+.user-info {
+  margin-bottom: 20px;
+}
+
+#last-field-row-element {
+  margin: 0;
+}
+
+.my-profile-container::-webkit-scrollbar-track {
+  background-color: transparent;
+}
+
+.my-profile-container::-webkit-scrollbar-thumb {
+  background-color: var(--middle-color);
+  border-radius: var(--border-radius-general);
+  border: 3px solid transparent;
+}
+
+.my-profile-container::-webkit-scrollbar {
+  width: 5px;
+}
+
+.my-profile-container::-webkit-scrollbar-thumb:hover {
+  background-color: var(--dark-color);
+}
+
+.form-button {
+  background-color: var(--middle-color);
+}
+
+.form-button:hover {
+  background-color: var(--dark-color);
+}
+
+.status-message {
+  margin-top: 2%;
+  font-weight: bold;
+}
+
+.delete-confirmation-box {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.5);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.confirmation-content {
+  background-color: white;
+  padding: 20px;
+  border-radius: var(--border-radius-general);
+  text-align: center;
+}
+
+.confirmation-content button {
+  border-radius: var(--border-radius-general);
+  padding: 5px 10px;
+  border: none;
+  margin-top: 3%;
+}
+
+.confirm-delete-button {
+  margin-left: 3%;
+  background-color: darkred;
+  color: white;
+}
+
+.confirm-delete-button:hover {
+  background-color: #5f0101;
+}
+
+.cancel-delete-button {
+  background-color: var(--middle-color);
+}
+
+.cancel-delete-button:hover {
+  background-color: var(--dark-color);
+}
+
+@media screen and (max-width: 650px) {
+  .action-buttons {
+    flex-direction: row;
+  }
+
+  button {
+    width: 100%
+  }
+
+  .title-with-button {
+    flex-direction: column;
+  }
+}
+
+</style>
+
diff --git a/src/components/profile/PersonalInfoComponent.vue b/src/components/profile/PersonalInfoComponent.vue
deleted file mode 100644
index dfce17d63219d978d02146a33634c0ec3b95d091..0000000000000000000000000000000000000000
--- a/src/components/profile/PersonalInfoComponent.vue
+++ /dev/null
@@ -1,9 +0,0 @@
-<script setup>
-
-</script>
-
-<template>
-  <div>
-    <h1>Personlige opplysninger</h1>
-  </div>
-</template>
diff --git a/src/components/profile/ProfileBar.vue b/src/components/profile/ProfileBar.vue
index 663c5c76c18e8a0ae2dafa05a47e9adc2c508bc8..9b5fd6938898721bd287a8f19533f19aa13c9e63 100644
--- a/src/components/profile/ProfileBar.vue
+++ b/src/components/profile/ProfileBar.vue
@@ -1,55 +1,104 @@
 <script setup>
-import { defineEmits } from 'vue';
+import {defineEmits} from 'vue';
 
 const emit = defineEmits(['select']);
 
 function emitSelect(componentName) {
-    emit('select', componentName);
+  emit('select', componentName);
 }
 </script>
 
 <template>
-    <div class="profile-bar">
-        <ul>
-            <li class="menu-item" @click="emitSelect('MyProfile')">Min profil</li>
-            <li class="menu-item" @click="emitSelect('PersonalInfo')">Personlige opplysninger</li>
-            <li class="menu-item" @click="emitSelect('Settings')">Innstillinger</li>
-            <li class="menu-item" @click="emitSelect('Accounts')">Kontoer</li>
-            <li class="menu-item" @click="emitSelect('Badges')">Badges</li>
-        </ul>
+  <div class="profile-bar">
+    <div class="profile-menu">
+      <ul>
+        <li role="button" @keydown.enter="emitSelect('MyProfile')"
+            tabindex="0" class="menu-item" @click="emitSelect('MyProfile')" data-cy="my-profile-link">
+          Min profil
+        <img src="@/assets/img/user.png" alt="">
+        </li>
+        <li role="button" @keydown.enter="emitSelect('BankStatements')"
+            tabindex="0" class="menu-item" @click="emitSelect('BankStatements')" data-cy="bank-statements-link">
+          Kontoutskrifter
+          <img src="@/assets/img/printer.png" alt="">
+        </li>
+        <li  role="button" @keydown.enter="emitSelect('Settings')"
+            tabindex="0" class="menu-item" @click="emitSelect('Settings')" data-cy="settings-link">
+          Innstillinger
+          <img src="@/assets/img/settings.png" alt="">
+        </li>
+        <li role="button" @keydown.enter="emitSelect('Badges')"
+            tabindex="0" class="menu-item" @click="emitSelect('Badges')" data-cy="badges-link">
+          Badges
+          <img src="@/assets/img/newBadge.png" alt="">
+        </li>
+      </ul>
     </div>
+  </div>
 </template>
 
 <style scoped>
 .profile-bar {
-    background-color: #304C6C;
-    border-radius: 10px;
-    max-width: 80%;
-    margin: auto;
-    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+  display: flex;
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
+  height: 100vh;
+}
+
+.profile-menu {
+  background: linear-gradient(var(--middle-color), var(--dark-color));
+  width: 100%;
+  height: 100vh;
+  margin: auto;
+  text-align: center;
+  display: flex;
+  box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
 }
 
 .profile-bar ul {
-    list-style: none;
-    padding: 0;
-    margin: 0;
+  list-style: none;
+  padding: 0;
+  margin: 0;
+}
+
+.profile-menu ul {
+  display: flex;
+  align-items: center;
+  flex-direction: column;
+  width: 100%;
+  padding-top: 30%;
 }
 
 .menu-item {
-    background-color: #ffffff;
-    border: none;
-    border-radius: 5px;
-    padding: 1em 1.5em;
-    margin: 1em;
-    width: calc(100% - 2em);
-    box-sizing: border-box;
-    text-align: center;
-    font-size: 1em;
-    cursor: pointer;
-    transition: background-color 0.3s;
-}
-
-.menu-item:hover {
-    background-color: #e0e0e0;
+  background-color: #ffffff;
+  border: none;
+  border-radius: var(--border-radius-general);
+  padding: 1em 1.5em;
+  margin: 1em;
+  width: 80%;
+  box-sizing: border-box;
+  text-align: center;
+  cursor: pointer;
+  transition: background-color 0.3s, transform 0.3s;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+  height: 70px;
+  justify-content: center;
+  align-items: center;
+  display: flex;
+  flex-wrap: wrap;
+  white-space: normal;
+  overflow: hidden;
+}
+
+.menu-item:hover,
+.menu-item.active {
+  background-color: var(--light-color);
+  transform: translateY(-2px);
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
+}
+
+img {
+  max-height: 80%;
+  max-width: 80%;
+  margin-left: 5%;
 }
 </style>
\ No newline at end of file
diff --git a/src/components/profile/ProfileDropdown.vue b/src/components/profile/ProfileDropdown.vue
new file mode 100644
index 0000000000000000000000000000000000000000..eba4082c7f72cd5335ecadb702e9425a0b24ab3e
--- /dev/null
+++ b/src/components/profile/ProfileDropdown.vue
@@ -0,0 +1,110 @@
+<script setup>
+import { ref, defineEmits } from 'vue';
+
+const emit = defineEmits(['select']);
+
+const isOpen = ref(false);
+const currentPage = ref('Min profil');
+
+const pageTitles = {
+  MyProfile: 'Min profil',
+  BankStatements: 'Kontoutskrifter',
+  Settings: 'Innstillinger',
+  Badges: 'Badges'
+};
+
+function emitSelect(componentName) {
+    if (currentPage.value !== pageTitles[componentName] || currentPage.value === 'Min profil') {
+        currentPage.value = pageTitles[componentName] || componentName;
+        emit('select', componentName);
+    }
+    isOpen.value = false;
+}
+
+function toggleDropdown() {
+  isOpen.value = !isOpen.value;
+}
+</script>
+
+<template>
+  <div class="profile-dropdown">
+    <button class="dropdown-toggle" @click="toggleDropdown">
+      {{ currentPage }} <span class="caret"></span>
+    </button>
+    <ul v-if="isOpen">
+      <li class="menu-item" @click="emitSelect('MyProfile')" data-cy="my-profile-link">
+        Min profil
+        <img src="@/assets/img/user.png" alt="">
+      </li>
+      <li class="menu-item" @click="emitSelect('BankStatements')" data-cy="bank-statements-link">
+        Kontoutskrifter
+        <img src="@/assets/img/printer.png" alt="">
+      </li>
+      <li class="menu-item" @click="emitSelect('Settings')" data-cy="settings-link">
+        Innstillinger
+        <img src="@/assets/img/settings.png" alt="">
+      </li>
+      <li class="menu-item" @click="emitSelect('Badges')" data-cy="badges-link">
+        Badges
+        <img src="@/assets/img/newBadge.png" alt="">
+      </li>
+    </ul>
+  </div>
+</template>
+
+<style scoped>
+img {
+  max-height: 20px;
+  width: auto;
+  vertical-align: middle;
+}
+
+.profile-dropdown {
+  border-radius: var(--border-radius-general);
+  max-width: 80%;
+  margin: auto;
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+  cursor: pointer;
+  display: inline-block;
+  position: relative;
+}
+
+.dropdown-toggle {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  background-color: var(--dark-color);
+  color: var(--white-text);
+  border: none;
+  padding: 0.5em 1em;
+  border-radius: var(--border-radius-general);
+  cursor: pointer;
+  transition: background-color 0.3s;
+  width: 100%;
+  text-align: left;
+}
+
+.profile-dropdown ul {
+  background-color: #e0e0e0;
+  list-style: none;
+  padding: 0;
+  margin: 0;
+  width: 100%;
+  display: block;
+  position: absolute;
+}
+
+.menu-item {
+  border: none;
+  box-sizing: border-box;
+  text-align: center;
+  padding: 0.7em;
+  border: 1px solid #c0c0c0;
+}
+
+.caret {
+  border-left: 5px solid transparent;
+  border-right: 5px solid transparent;
+  border-top: 5px solid #000;
+}
+</style>
diff --git a/src/components/profile/ProfileMain.vue b/src/components/profile/ProfileMain.vue
index c1a586895d6b4589039225fc6438faaa73778106..4cf24e1616438275c3ff788822844af7f5422bbd 100644
--- a/src/components/profile/ProfileMain.vue
+++ b/src/components/profile/ProfileMain.vue
@@ -2,8 +2,7 @@
 import { computed } from 'vue';
 import { defineProps } from 'vue';
 import MyProfile from '@/components/profile/MyProfileComponent.vue';
-import PersonalInfo from '@/components/profile/PersonalInfoComponent.vue';
-import Accounts from '@/components/profile/AccountsComponent.vue';
+import BankStatements from '@/components/profile/BankStatementsComponent.vue';
 import Badges from '@/components/profile/BadgesComponent.vue';
 import Settings from '@/components/profile/SettingsComponent.vue';
 
@@ -15,10 +14,8 @@ const currentComponent = computed(() => {
   switch (props.component) {
     case 'MyProfile':
       return MyProfile;
-    case 'PersonalInfo':
-      return PersonalInfo;
-    case 'Accounts':
-      return Accounts;
+    case 'BankStatements':
+      return BankStatements;
     case 'Badges':
       return Badges;
     case 'Settings':
@@ -30,7 +27,5 @@ const currentComponent = computed(() => {
 </script>
 
 <template>
-  <div>
     <component :is="currentComponent" />
-  </div>
 </template>
\ No newline at end of file
diff --git a/src/components/profile/SettingsComponent.vue b/src/components/profile/SettingsComponent.vue
index b255e05dde6e58266c847b18afd41169da9dec58..9f8d2ec4e7fe2e56f4d1fb406329e1156726c77f 100644
--- a/src/components/profile/SettingsComponent.vue
+++ b/src/components/profile/SettingsComponent.vue
@@ -1,24 +1,16 @@
 <script setup>
-//todo lag også egne knapper som brukes på tvers av hele nettsiden, i steden for å lage knapper per side
-//todo bruk noe form for sessionstorage så stilene blir satt globalt, og blir lagret etter bruker.
-
-import DarkLightMode from "@/components/profile/settingsOptionComponents/DarkLightMode.vue";
 import FontSize from "@/components/profile/settingsOptionComponents/FontSize.vue";
 import FontFamily from "@/components/profile/settingsOptionComponents/FontFamily.vue";
-import ChangeLanguage from "@/components/profile/settingsOptionComponents/ChangeLanguage.vue";
-
+import ChangeCookiePrivilege from "@/components/profile/settingsOptionComponents/ChangeCookiePrivilege.vue";
+import BorderRadius from "@/components/profile/settingsOptionComponents/BorderRadius.vue";
 </script>
 
 <template>
-  <div class="main-container">
-    <h1>Settings</h1>
+  <div class="settings-main-container">
+    <h1>Innstillinger</h1>
 
     <div class="category">
-      <h2>Design and layout:</h2>
-
-      <div class="setting-item">
-        <DarkLightMode/>
-      </div>
+      <h2>Design og layout:</h2>
 
       <div class="setting-item">
         <FontSize/>
@@ -29,73 +21,61 @@ import ChangeLanguage from "@/components/profile/settingsOptionComponents/Change
       </div>
 
       <div class="setting-item">
-        <ChangeLanguage/>
-      </div>
-
-      <div class="setting-item">
-        <h4>Roundness</h4>
-      </div>
-
-      <div class="setting-item">
-        <h4>Infobar filter (evt senere)</h4>
+        <BorderRadius/>
       </div>
 
       <div class="setting-item">
-        <h4>color scheme (evt senere)</h4>
+        <ChangeCookiePrivilege/>
       </div>
     </div>
-
-    <div class="category">
-      <h2>Account:</h2>
-      <h4>Change password</h4>
-      <h4>Change name(first and/or last)</h4>
-      <h4>Change mail</h4>
-      <h4>Notification</h4>
-      <h4>Activity</h4>
-    </div>
-
-    <div class="category">
-      <h2>Resources</h2>
-      <h4>User manual</h4>
-      <h4>Upload bankutskrift</h4>
-      <h4>Download oppsummering</h4>
-    </div>
   </div>
 </template>
 
 <style scoped>
-@import "@/assets/css/dropBox.css";
-
-/*Scrollbar*/
-.main-container::-webkit-scrollbar {
+h1 {
+  display: flex;
+  justify-content: center;
+  align-content: center;
+  text-align: center;
+  margin-bottom: 20px;
+}
+.settings-main-container::-webkit-scrollbar {
   display: none;
 }
 
-/*Hoved midterste skjermen*/
-.main-container {
+.settings-main-container {
   width: 100%;
   max-height: 100vh;
   overflow-y: auto;
   padding: 5% 5% 20px 5%;
   box-sizing: border-box;
-  scrollbar-width: none; /* Firefox */
+  scrollbar-width: none;
+  justify-content: center;
 }
 
-/*Kategori boksene*/
 .category {
-  background-color: var(--accent-color-dark);
-  border-radius: 10px;
+  background-color: var(--accent-color);
+  border-radius: var(--border-radius-general);
   margin-bottom: 20px;
   padding: 20px;
   text-align: left;
+  display: flex;
+  flex-direction: column;
 }
 
 .setting-item {
   display: flex;
+  padding-top: 10px;
   flex-direction: row;
   align-items: center;
   width: 100%;
 }
 
-
-</style>
+@media (max-width: 600px) {
+  .category {
+    justify-content: center;
+    align-items: center;
+    text-align: center;
+  }
+}
+</style>
\ No newline at end of file
diff --git a/src/components/profile/settingsOptionComponents/BorderRadius.vue b/src/components/profile/settingsOptionComponents/BorderRadius.vue
new file mode 100644
index 0000000000000000000000000000000000000000..41ce261b161de1b856373eba02b78d12dfa6a1eb
--- /dev/null
+++ b/src/components/profile/settingsOptionComponents/BorderRadius.vue
@@ -0,0 +1,79 @@
+<script setup>
+import {onMounted, ref, watch} from 'vue';
+import CookieService from "@/services/internal/CookieService";
+
+const radiusOption = ref('border-medium');
+const currentBorderRadius = ref('border-medium');
+
+onMounted(() => {
+  const borderRadiusCookie = CookieService.getCookieWithConsent('borderRadius');
+  if (borderRadiusCookie) {
+    document.documentElement.classList.add(borderRadiusCookie);
+    currentBorderRadius.value = borderRadiusCookie;
+  }
+});
+
+/**
+ * Watches for changes in the selected border radius option and updates the DOM and cookie accordingly.
+ *
+ * @function
+ * @param {string} newValue - The new selected border radius option.
+ * @param {string} oldValue - The previous selected border radius option.
+ */
+watch(radiusOption, (newValue, oldValue) => {
+  if (oldValue) {
+    document.documentElement.classList.remove(oldValue);
+  }
+  if (newValue) {
+    document.documentElement.classList.add(newValue);
+    CookieService.setCookieWithConsent('borderRadius', newValue, 7);
+    currentBorderRadius.value = newValue;
+  }
+}, { immediate: true });
+</script>
+
+<template>
+  <div class="border-radius-main-container">
+    <label for="setting-border-styles">Ramme Stil</label>
+    <select id="setting-border-styles" v-model="radiusOption" class="dropdown">
+      <option value="border-none">Firkantet</option>
+      <option value="border-small">Rund Liten</option>
+      <option value="border-medium">Rund Medium</option>
+      <option value="border-large">Rund Stor</option>
+    </select>
+  </div>
+</template>
+
+<style scoped>
+.border-radius-main-container {
+  display: flex;
+  justify-content: space-between;
+  padding: 0;
+  width: 100%;
+}
+
+label {
+  font-size: 1.5em;
+  margin: 0;
+  padding: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.dropdown {
+  background-color: var(--accent-color-dark);
+}
+
+.dropdown:hover {
+  background-color: var(--light-color);
+}
+
+@media (max-width: 600px) {
+  .border-radius-main-container {
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+  }
+}
+</style>
diff --git a/src/components/profile/settingsOptionComponents/ChangeCookiePrivilege.vue b/src/components/profile/settingsOptionComponents/ChangeCookiePrivilege.vue
new file mode 100644
index 0000000000000000000000000000000000000000..3a5db08228b58ede2e00b5c15fcf07967a2d99a2
--- /dev/null
+++ b/src/components/profile/settingsOptionComponents/ChangeCookiePrivilege.vue
@@ -0,0 +1,76 @@
+<script setup>
+import {onMounted, ref, watch} from 'vue';
+import CookieService from "@/services/internal/CookieService";
+
+const selectedCookieSetting = ref('');
+
+/**
+ * Watches for changes in the selected cookie setting and updates the DOM and cookie accordingly.
+ *
+ * @param {string} newValue - The new selected cookie setting.
+ * @param {string} oldValue - The previous selected cookie setting.
+ */
+watch(selectedCookieSetting, (newValue, oldValue) => {
+  if (oldValue) {
+    document.documentElement.classList.remove(oldValue);
+    document.documentElement.classList.add(newValue);
+  }
+  if (newValue) {
+    document.documentElement.classList.add(newValue);
+    CookieService.setCookie('cookiesAccepted', newValue, 7);
+  }});
+
+/**
+ * Sets the initial value for the selected cookie setting based on the existing cookie when the component is mounted.
+ */
+onMounted(() => {
+  if (CookieService.getCookie('cookiesAccepted')) {
+    selectedCookieSetting.value = CookieService.getCookie('cookiesAccepted');
+  }
+});
+
+</script>
+
+<template>
+  <div class="change-cookies-main-container">
+    <label for="setting-cookie">Informasjonskapsler</label>
+    <select id="setting-cookie" class="dropdown" v-model="selectedCookieSetting">
+      <option value='true'>Aksepter</option>
+      <option value='false'>Avslå</option>
+    </select>
+  </div>
+</template>
+
+<style scoped>
+.change-cookies-main-container {
+  display: flex;
+  justify-content: space-between;
+  padding: 0;
+  width: 100%;
+}
+
+label {
+  font-size: 1.5em;
+  margin: 0;
+  padding: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.dropdown {
+  background-color: var(--accent-color-dark);
+}
+
+.dropdown:hover {
+  background-color: var(--light-color);
+}
+
+@media (max-width: 600px) {
+  .change-cookies-main-container {
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+  }
+}
+</style>
\ No newline at end of file
diff --git a/src/components/profile/settingsOptionComponents/ChangeLanguage.vue b/src/components/profile/settingsOptionComponents/ChangeLanguage.vue
deleted file mode 100644
index ea695534a678889d2a8a87fc04d5085a6b6949e2..0000000000000000000000000000000000000000
--- a/src/components/profile/settingsOptionComponents/ChangeLanguage.vue
+++ /dev/null
@@ -1,44 +0,0 @@
-<script setup>
-import { ref } from 'vue';
-
-const selectedLanguage = ref('en');
-
-/*function changeLanguage() {
-  console.log(`Language changed to : ${selectedLanguage.value}`);
-}
-
-//todo fiks så språket fakltisk endres senere*/
-</script>
-
-<template>
-  <div class="language-main-container">
-    <h4>Language</h4>
-    <select class="dropdown" v-model="selectedLanguage">
-      <option value="en">English</option>
-      <option value="no">Norsk</option>
-      <option value="fr">Français</option>
-      <option value="de">Deutsch</option>
-      <option value="srb">Srbski</option>
-      <option value="esp">Español</option>
-    </select>
-  </div>
-</template>
-
-<style scoped>
-.language-main-container {
-  display: flex;
-  justify-content: space-between;
-  padding: 0;
-  width: 100%;
-}
-
-h4 {
-  margin: 0;
-  padding: 0;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
-
-
-</style>
\ No newline at end of file
diff --git a/src/components/profile/settingsOptionComponents/DarkLightMode.vue b/src/components/profile/settingsOptionComponents/DarkLightMode.vue
deleted file mode 100644
index 846b1d46ca57bc153036ea4c2757bf0a6be3061b..0000000000000000000000000000000000000000
--- a/src/components/profile/settingsOptionComponents/DarkLightMode.vue
+++ /dev/null
@@ -1,107 +0,0 @@
-<script setup>
-//todo legg også til at når man bytter fra light til darkmode og tilbake, and tekstfargene også endres. Altså light skal ha sort skrift og motsatt.
-//todo sol og måne, samt dott som snurrer ved toggle
-import {ref, watch} from 'vue';
-
-const isDarkMode = ref(false); //todo Denne må lagres på bruker, slik at endring en gang, lagres
-
-watch(isDarkMode, (newValue) => {
-  const rootClass = newValue ? 'dark-theme' : 'white-theme';
-  document.documentElement.className = rootClass;
-});
-</script>
-
-<template>
-  <div class="dark-light-main-container">
-    <h4>Dark/light mode</h4>
-    <label class="switch">
-      <input type="checkbox" id="dark-light-mode" v-model="isDarkMode">
-      <span class="slider round"></span>
-    </label>
-  </div>
-</template>
-
-<style scoped>
-.dark-light-main-container {
-  display: flex;
-  justify-content: space-between;
-  padding: 0;
-  width: 100%;
-}
-
-h4 {
-  margin: 0;
-  padding: 0;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
-
-.switch {
-  position: relative;
-  display: inline-block;
-  width: 60px;
-  height: 34px;
-  margin: 5%;
-}
-
-.switch input {
-  opacity: 0;
-  width: 0;
-  height: 0;
-}
-
-/*Bakgrunn slider ikke valgt*/
-.slider {
-  position: absolute;
-  cursor: pointer;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  background-color: var(--accent-color);
-  -webkit-transition: .4s;
-  transition: .4s;
-}
-
-/*selve ballen som beveger seg, todo fiks denne mer senere*/
-.slider:before {
-  position: absolute;
-  content: "";
-  height: 26px;
-  width: 26px;
-  left: 4px;
-  bottom: 4px;
-  background-color: var(--white-general);
-  -webkit-transition: .4s;
-  transition: .4s;
-  border-width: 1px;
-  border-style: solid;
-  border-color: darkred;
-}
-
-/*Bakgrunn slider valgt*/
-input:checked + .slider {
-  background-color: var(--dark-color);
-}
-
-/*Rammen rundt radio button*/
-input:focus + .slider {
-  box-shadow: 0 0 3px var(--dark-color);
-}
-
-input:checked + .slider:before {
-  -webkit-transform: translateX(26px);
-  -ms-transform: translateX(26px);
-  transform: translateX(26px);
-}
-
-/* Rounded sliders */
-.slider.round {
-  border-radius: 34px;
-}
-
-.slider.round:before {
-  border-radius: 50%;
-}
-</style>
\ No newline at end of file
diff --git a/src/components/profile/settingsOptionComponents/FontFamily.vue b/src/components/profile/settingsOptionComponents/FontFamily.vue
index d47a0f26b65a896008148580b7eb941db5ad7222..19d91078b09edc7f668775a6ff3cd297a7ec39df 100644
--- a/src/components/profile/settingsOptionComponents/FontFamily.vue
+++ b/src/components/profile/settingsOptionComponents/FontFamily.vue
@@ -1,20 +1,42 @@
 <script setup>
-import {ref, watch} from 'vue';
+import {onMounted, ref, watch} from 'vue';
+import CookieService from "@/services/internal/CookieService";
 
 const currentFontFamily = ref('font1');
 
+/**
+ * Initializes the selected font family from the cookie when the component is mounted.
+ */
+onMounted(() => {
+  const fontFamilyCookie = CookieService.getCookieWithConsent('fontFamily');
+  if (fontFamilyCookie) {
+    currentFontFamily.value = fontFamilyCookie;
+  }
+});
+
+/**
+ * Watches for changes in the selected font family and updates the DOM and cookie accordingly.
+ *
+ * @param {string} newValue - The new selected font family.
+ * @param {string} oldValue - The previous selected font family.
+ */
 watch(currentFontFamily, (newValue, oldValue) => {
-  document.documentElement.classList.remove(oldValue);
-  document.documentElement.classList.add(newValue);
-  console.log(currentFontFamily)
+  if (oldValue) {
+    document.documentElement.classList.remove(oldValue);
+    document.documentElement.classList.add(newValue);
+  }
+  if (newValue) {
+    document.documentElement.classList.add(newValue);
+    CookieService.setCookieWithConsent('fontFamily', newValue, 7);
+  }
 });
 
 </script>
 
 <template>
-  <div class="font-size-main-container">
-    <h4>Font Style</h4>
-    <select v-model="currentFontFamily" class="dropdown">
+  <div class="font-style-main-container">
+    <label for="setting-font-size">Font Stil</label>
+    <select id="setting-font-size" v-model="currentFontFamily" class="dropdown">
       <option value="font1">Arial</option>
       <option value="font2">Times New Roman</option>
       <option value="font3">Courier New</option>
@@ -26,7 +48,7 @@ watch(currentFontFamily, (newValue, oldValue) => {
 </template>
 
 <style scoped>
-.font-size-main-container {
+.font-style-main-container {
   display: flex;
   justify-content: space-between;
   padding: 0;
@@ -41,5 +63,28 @@ h4 {
   justify-content: center;
 }
 
+label {
+  font-size: 1.5em;
+  margin: 0;
+  padding: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.dropdown {
+  background-color: var(--accent-color-dark);
+}
 
+.dropdown:hover {
+  background-color: var(--light-color);
+}
+
+@media (max-width: 600px) {
+  .font-style-main-container {
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+  }
+}
 </style>
diff --git a/src/components/profile/settingsOptionComponents/FontSize.vue b/src/components/profile/settingsOptionComponents/FontSize.vue
index 55c46b79d8bde7d2e2e4eab27e548eb48cb7230f..62aae3318e70b7494d6cd5a80c472690f7c6f5ea 100644
--- a/src/components/profile/settingsOptionComponents/FontSize.vue
+++ b/src/components/profile/settingsOptionComponents/FontSize.vue
@@ -1,23 +1,45 @@
 <script setup>
-//todo fiks bug at dersom man velger skrifttype og deretter størrelse, så forsvinner valget av skrifttype.
-import {ref, watch} from 'vue';
+import {onMounted, ref, watch} from 'vue';
+import CookieService from "@/services/internal/CookieService";
 
 const currentFontSize = ref('font-size-medium');
 
-watch(currentFontSize, (newValue) => {
-  document.documentElement.className = newValue;
+/**
+ * Initializes the selected font size from the cookie when the component is mounted.
+ */
+onMounted(() => {
+  const fontSizeCookie = CookieService.getCookieWithConsent('fontSize');
+  if (fontSizeCookie) {
+    currentFontSize.value = fontSizeCookie;
+  }
+});
+
+/**
+ * Watches for changes in the selected font size and updates the DOM and cookie accordingly.
+ *
+ * @param {string} newValue - The new selected font size.
+ * @param {string} oldValue - The previous selected font size.
+ */
+watch(currentFontSize, (newValue, oldValue) => {
+  if (oldValue) {
+    document.documentElement.classList.remove(oldValue);
+    document.documentElement.classList.add(newValue);
+  }
+  if (newValue) {
+    document.documentElement.classList.add(newValue);
+    CookieService.setCookieWithConsent('fontSize', newValue, 7);
+  }
 });
-</script>
 
+</script>
 <template>
   <div class="font-size-main-container">
-    <h4>Font Size</h4>
-    <select v-model="currentFontSize" class="dropdown">
-      <option value="font-size-extra-small">Extra Small</option>
-      <option value="font-size-small">Small</option>
+    <label for="setting-header" class="setting-header">Font Størrelse</label>
+
+    <select id="setting-header"  v-model="currentFontSize" class="dropdown">
+      <option value="font-size-small">Liten</option>
       <option value="font-size-medium">Medium</option>
-      <option value="font-size-large">Large</option>
-      <option value="font-size-extra-large">Extra Large</option>
+      <option value="font-size-large">Stor</option>
     </select>
   </div>
 </template>
@@ -38,4 +60,28 @@ h4 {
   justify-content: center;
 }
 
+label {
+  font-size: 1.5em;
+  margin: 0;
+  padding: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.dropdown {
+  background-color: var(--accent-color-dark);
+}
+
+.dropdown:hover {
+  background-color: var(--light-color);
+}
+
+@media (max-width: 600px) {
+  .font-size-main-container {
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+  }
+}
 </style>
diff --git a/src/components/transactions/TransactionsMain.vue b/src/components/transactions/TransactionsMain.vue
index 5f13d20aa7524a3d77ab58e7d68dc1ffe1b4e22d..d656325813bc1492625adf6cca085ae76e423fab 100644
--- a/src/components/transactions/TransactionsMain.vue
+++ b/src/components/transactions/TransactionsMain.vue
@@ -1,73 +1,498 @@
 <script setup>
+import {computed, onMounted, ref, watch} from 'vue';
+import BankStatementService from "@/services/internal/BankStatementService";
+import TransactionService from "@/services/internal/TransactionService";
+import axios from "axios";
 
+const transactions = ref([]);
+const accountNames = ref([]);
+const sortDropdown = ref('');
+const searchQuery = ref('');
+const sortDirectionTable = ref(1);
+const dropdownSortDirection = ref(1);
+const editMode = ref(false);
+const errorMessage = ref('');
+const selectedMonth = ref(null);
+const editedBankStatementIds = ref([]);
+const selectedAccount = ref('');
+
+/**
+ * Check if the selected month has changed, and update the transactions if it has.
+ */
+watch(selectedMonth, async () => {
+  await updateTransactions();
+}, {immediate: true});
+
+const categoryTranslations = {
+  'FOOD': {name: 'Mat', code: '01'},
+  'ALCOHOL_AND_TOBACCO': {name: 'Alkohol og tobakk', code: '02'},
+  'CLOTHING_AND_SHOES': {name: 'Klær og sko', code: '03'},
+  'HOUSING_AND_ELECTRICITY': {name: 'Bolig og elektrisitet', code: '04'},
+  'FURNITURE': {name: 'Møbler', code: '05'},
+  'HEALTH': {name: 'Helse', code: '06'},
+  'TRANSPORT': {name: 'Transport', code: '07'},
+  'COMMUNICATION': {name: 'Kommunikasjon', code: '08'},
+  'LEISURE_SPORT_AND_CULTURE': {name: 'Fritid, sport og kultur', code: '09'},
+  'EDUCATION': {name: 'Utdanning', code: '10'},
+  'EATING_OUT': {name: 'Spise ute', code: '11'},
+  'INSURANCE': {name: 'Forsikring', code: '12'},
+  'OTHER': {name: 'Annet', code: '13'}
+};
+
+
+onMounted(async () => {
+  setDefaultMonthToPrevious()
+});
+
+/**
+ * Sets the default month to the current month.
+ */
+function setDefaultMonthToPrevious() {
+  try {
+    const date = new Date();
+    const year = date.getFullYear();
+    let month = date.getMonth(); //1 month behind current
+    if (month < 10) {
+      month = '0' + month;
+    }
+    selectedMonth.value = `${year}-${month}-01`;
+  } catch (error) {
+    errorMessage.value = "Klarte ikke finne transaksjoner for forrige måned"
+  }
+}
+
+/**
+ * Updates the transactions array with the transactions of the selected account.
+ * @returns {Promise<void>}
+ */
+async function updateTransactions() {
+  try {
+    if (!selectedMonth.value) {
+      return
+    }
+    const splitDate = selectedMonth.value.split("-");
+    const month = splitDate[1];
+    const year = splitDate[0];
+    await getAllBankStatements(month, year)
+  } catch (error) {
+    errorMessage.value = "Klarte ikke å hente transaksjoner, prøv igjen senere"
+  }
+}
+
+/**
+ * Fetches all bank statements for the selected month and year.
+ * @param month The month to fetch bank statements for.
+ * @param year The year to fetch bank statements for.
+ * @returns {Promise<void>} A promise that resolves when the bank statements have been fetched.
+ */
+async function getAllBankStatements(month, year) {
+  transactions.value = [];
+  try {
+    errorMessage.value = '';
+    const bankStatements = await BankStatementService.retrieveBankStatements(month, year);
+    console.log("received: ", bankStatements.length)
+
+    for (let statement of bankStatements) {
+
+      if (!accountNames.value.includes(statement.accountName)) {
+        accountNames.value.push(statement.accountName);
+      }
+      for (let transaction of statement.transactions) {
+        transaction.visibleDate = getPrettyDateFormat(transaction, statement.timestamp);
+        transaction.statementId = statement.id;
+        transaction.accountName = statement.accountName;
+        transactions.value.push(transaction)
+      }
+    }
+  } catch (error) {
+    if (axios.isAxiosError(error) && error.response) {
+      errorMessage.value = "Klarte ikke å hente bankutskrifter, prøv igjen senere."
+    } else {
+      console.error('Cannot connect to server.', error);
+      errorMessage.value = 'Kan ikke koble til serveren. Vennligst prøv igjen senere.';
+    }
+  }
+}
+
+/**
+ * Returns a pretty date format for the transaction.
+ * @param transaction The transaction to get the date from.
+ * @param yearMonth The year and month of the transaction.
+ * @returns {string} The pretty date format.
+ */
+function getPrettyDateFormat(transaction, yearMonth) {
+  try {
+    const splitYear = yearMonth.split("-");
+    const year = splitYear[0];
+    const month = splitYear[1];
+    const dateOfMonth = transaction.date.slice(-2);
+    return `${dateOfMonth}.${month}.${year}`;
+  } catch (err) {
+    console.error("Invalid data format.", err);
+    return "Invalid date format"
+  }
+}
+
+/**
+ * Fetches the account numbers from the backend and adds them to the accountNames array.
+ */
+function switchAccountName(accountNumber) {
+  if (accountNumber) {
+    selectedAccount.value = accountNumber;
+    updateTransactions()
+  }
+}
+
+/**
+ * Filters the transactions based on partial search in all queries.
+ */
+const filteredTransactions = computed(() => {
+  return transactions.value.filter((transaction) => {
+        try {
+          if (selectedAccount.value && !(selectedAccount.value === transaction.accountName)) {
+            return false;
+          }
+
+          const searchInLowerCase = searchQuery.value.toLowerCase().replace(/\s/g, '');
+
+          let dateMatch = false;
+          const transactionDate = new Date(transaction.timestamp);
+          const day = transactionDate.getDate().toString();
+          const month = (transactionDate.getMonth() + 1).toString();
+          const year = transactionDate.getFullYear().toString();
+          if (searchInLowerCase === day || searchInLowerCase === month || searchInLowerCase === year.padStart(2, '0') || searchInLowerCase === year) {
+            dateMatch = true;
+          }
+
+          const descriptionMatch = transaction.description.toLowerCase().replace(/\s/g, '').includes(searchInLowerCase);
+          const categoryMatch = transaction.category && categoryTranslations[transaction.category].name.toLowerCase().replace(/\s/g, '').startsWith(searchInLowerCase);
+
+          const amountMatch = transaction.amount.toString().replace('.', '').includes(searchInLowerCase.replace('.', ''));
+
+          return dateMatch || descriptionMatch || categoryMatch || amountMatch;
+        } catch (error) {
+          console.error = "Failed to filter all transactions" + error;
+          return false;
+        }
+      }
+  ).sort((a, b) => {
+    if (!sortDropdown.value) return 0;
+    if (a[sortDropdown.value] < b[sortDropdown.value]) return -1 * sortDirectionTable.value;
+    if (a[sortDropdown.value] > b[sortDropdown.value]) return 1 * sortDirectionTable.value;
+    return 0;
+  });
+});
+
+/**
+ * Sorts the transactions based on the selected column.
+ * @param column The column to sort by.
+ */
+function sortByForTable(column) {
+  if (sortDropdown.value === column) {
+    sortDirectionTable.value *= -1;
+  } else {
+    sortDropdown.value = column;
+    sortDirectionTable.value = 1;
+  }
+}
+
+function sortByForDropdown(column, fromDropdown = false) {
+  if (sortDropdown.value === column) {
+    dropdownSortDirection.value *= -1;
+  } else {
+    sortDropdown.value = column;
+    dropdownSortDirection.value = fromDropdown ? 1 : -1;
+  }
+}
+
+/**
+ * Toggles the edit mode.
+ */
+function toggleEditMode() {
+  editMode.value = !editMode.value;
+  if (!editMode.value) {
+    handleFinishedEditing();
+  }
+}
+
+/**
+ * Handles the change of category for a transaction.
+ * @param transaction The transaction to change the category for.
+ */
+function handleCategoryChange(transaction) {
+  transaction.hasBeenEdited = true;
+  if (!editedBankStatementIds.value.includes(transaction.statementId)) {
+    editedBankStatementIds.value.push(transaction.statementId);
+  }
+}
+
+/**
+ * Handles the finished editing of the transactions.
+ * @returns {Promise<void>}
+ */
+async function handleFinishedEditing() {
+  for (const transaction of transactions.value) {
+    try {
+      if (transaction.hasBeenEdited) {
+        await TransactionService.updateTransaction(transaction);
+      }
+    } catch (error) {
+      if (axios.isAxiosError(error) && error.response) {
+        errorMessage.value = "Det var problemer med å oppdatere transaksjonene.";
+      } else {
+        console.error('Cannot connect to server.', error);
+        errorMessage.value = 'Kan ikke koble til serveren. Vennligst prøv igjen senere.';
+      }
+    }
+  }
+
+  try {
+    errorMessage.value = "Analyserer kontoutskriftene";
+    await reAnalyseBankStatements(editedBankStatementIds.value)
+    errorMessage.value = "Kontoutskriftene ble analysert"
+  } catch (error) {
+    if (axios.isAxiosError(error) && error.response) {
+      errorMessage.value = "Det var problemer med å analysere kontoutskriftene";
+    } else {
+      console.error('Cannot connect to server.', error);
+      errorMessage.value = 'Kan ikke koble til serveren. Vennligst prøv igjen senere.';
+    }
+  }
+  clearEdited()
+}
+
+/**
+ * Re-analyzes the bank statements with the given statement ids.
+ * @param statementIds The statement ids to re-analyze.
+ * @returns {Promise<void>} A promise that resolves when the bank statements have been re-analyzed.
+ */
+async function reAnalyseBankStatements(statementIds) {
+  for (const statementId of statementIds) {
+    errorMessage.value = "Re-analyserer statement:" + statementId;
+    await BankStatementService.analyzeBankStatement(statementId, true, false)
+  }
+  await updateTransactions()
+  errorMessage.value = "Kontoutskriftene ble re-analysert"
+}
+
+/**
+ * Clears the edited flag for all transactions.
+ */
+function clearEdited() {
+  transactions.value.forEach(transaction => {
+    transaction.hasBeenEdited = false;
+  })
+}
 </script>
 
 <template>
-  <div class="header">
-    <h1>Transactions</h1>
-  </div>
-  <div class="top-container">
-    <h3>Siste bevegelser</h3>
-      <div>
-        <h5>Søk i kontoutskrift:</h5>
-        <input id="transaction-search" type="text" placeholder="Søk her">
-      </div>
-  </div>
-  <div class="container">
-    <div class="left-column">
-      <div v-for="n in 10" :key="n" class="item1">transaksjon</div>
+  <div class="transactions-main-container">
+    <div class="header">
+      <h1>Transaksjoner</h1>
     </div>
-    <div class="right-column">
-      <div v-for="n in 10" :key="n" class="item2">-1000</div>
+    <div class="error-container">
+      <p v-if="errorMessage">
+        {{ errorMessage }}
+      </p>
+    </div>
+    <div class="top-container">
+      <input v-model="searchQuery" type="text" placeholder="Søk her" class="transaction-search">
+      <input type="date" placeholder="Velg måned" v-model="selectedMonth" id="selected-month"
+             class="date-picker dropdown">
+      <select class="dropdown" v-model="sortDropdown" @change="sortByForDropdown(sortDropdown, true)">
+        <option value="" disabled selected>Sorter</option>
+        <option value="date">Dato</option>
+        <option value="category">Kategori</option>
+        <option value="description">Beskrivelse</option>
+        <option value="amount">Sum</option>
+      </select>
+      <select class="dropdown" v-model="selectedAccount" @change="switchAccountName(selectedAccount)">
+        <option disabled value="">Filtrer etter konto</option>
+        <option v-for="account in accountNames" :key="account" :value="account">{{ account }}</option>
+      </select>
+      <button @click="toggleEditMode">{{ editMode ? 'Ferdig' : 'Rediger' }}</button>
+    </div>
+
+    <div class="scrollable-container">
+      <table class="table-container">
+        <thead>
+        <tr>
+          <th tabindex="0" @click="sortByForTable('date')" @keydown.enter="sortByForTable('date')">Dato</th>
+          <th tabindex="0" @click="sortByForTable('category')" @keydown.enter="sortByForTable('category')">Kategori</th>
+          <th tabindex="0" @click="sortByForTable('description')" @keydown.enter="sortByForTable('description')">
+            Beskrivelse
+          </th>
+          <th tabindex="0" @click="sortByForTable('amount')" @keydown.enter="sortByForTable('amount')">Sum</th>
+        </tr>
+        </thead>
+        <tbody v-if="transactions">
+        <tr v-for="(transaction, index) in filteredTransactions" :key="index">
+          <td>{{ transaction.visibleDate }}</td>
+          <td>
+            <template v-if="!editMode">
+              {{
+                categoryTranslations[transaction.category]?.name ? categoryTranslations[transaction.category].name
+                    : "Ikke kategorisert"
+              }}
+            </template>
+            <template v-else>
+              <select v-model="transaction.category" @change="handleCategoryChange(transaction)">
+                <option value="">Ikke kategorisert</option>
+                <option v-for="(category, key) in categoryTranslations"
+                        :key="key"
+                        :value="key">
+                  {{ category.name }}
+                </option>
+              </select>
+            </template>
+          </td>
+          <td>{{ transaction.description }}</td>
+          <td>{{ transaction.isIncoming ? '+' : '-' }}{{ transaction.amount }} kr</td>
+        </tr>
+        </tbody>
+
+      </table>
+      <div v-if="!transactions.length">
+        <p v-if="selectedMonth">
+          Ingen tilgjengelige transaksjoner under denne kontoen for
+          {{ selectedMonth.split('-')[1] + '/' + selectedMonth.split('-')[0] }}
+        </p>
+        <p v-else>
+          Venligst velg en måned for å se transaksjoner
+        </p>
+      </div>
+
     </div>
   </div>
 </template>
 
 <style scoped>
-h1 {
-  color: var(--dark-color);
-  text-align: center;
-  padding-bottom: 1em;
+::-webkit-scrollbar {
+  width: 10px;
 }
-h3 {
-  color: var(--middle-color);
+
+::-webkit-scrollbar-thumb {
+  background: #888;
 }
-.container {
-  color: var(--light-color);
-  display: grid;
-  grid-template-columns: 1fr 1fr;
-  width: 95%;
-  border: 1px solid black;
-  border-radius: 2%;
+
+::-webkit-scrollbar-thumb:hover {
+  background: #555;
+}
+
+.transactions-main-container {
+  width: 100%;
+  height: 100vh;
+  padding: 20px
+}
+
+.scrollable-container {
+  overflow-y: auto;
+  max-height: 80vh;
+  width: 100%;
+  margin-top: 20px;
+}
+
+.error-container {
+  color: var(--error-text);
+  margin: 1%;
+}
+
+.header {
+  height: 90px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 2%;
+}
+
+.date-picker {
+  padding: 8px;
+  border-radius: var(--border-radius-general);
+  border: none;
 }
+
+.table-container {
+  width: 100%;
+  margin-top: 0;
+  border-collapse: collapse;
+  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
+}
+
 .top-container {
-  display: grid;
+  display: flex;
+  flex-direction: row;
   grid-template-columns: 1fr 1fr;
-  padding: 5%;
   justify-content: space-between;
   align-items: center;
+  width: 100%;
+  gap: 15px;
+}
+
+.transaction-search {
+  border-radius: var(--border-radius-general);
+  padding: 8px;
+  border: none;
+  outline: none;
+  transition: all 0.3s ease;
+  background-color: var(--accent-color);
+}
+
+.transaction-search:hover {
+  background-color: var(--accent-color-dark);
+}
+
+.transaction-search:focus {
+  box-shadow: 0 0 8px var(--dark-color);
+}
+
+th,
+td {
+  padding: 12px 15px;
+  text-align: left;
+  border-bottom: 1px solid var(--accent-color);
+}
+
+th {
+  background-color: var(--middle-color);
+  color: white;
+  cursor: pointer;
+}
+
+th:hover {
+  background-color: var(--dark-color);
 }
 
-.item1 {
-  border-bottom: 1px solid black;
-  grid-column: 1;
-  padding-top: 0.5em;
-  padding-left: 5%;
+tbody tr:hover {
+  background-color: var(--accent-color);
 }
 
-.item2 {
-  border-bottom: 1px solid black;
-  grid-column: 2;
-  padding-top: 0.5em;
-  text-align: end;
-  padding-right: 5%;
+.table-container th {
+  background-color: var(--dark-color);
+  color: white;
+  position: sticky;
+  top: 0;
+  z-index: 10;
 }
 
-#transaction-search {
-  width: 90%;
-  padding: 0.3em;
-  border-radius: 5px;
-  border: 1px solid black;
+.transaction-search, .date-picker, .dropdown, button {
+  width: 100%;
 }
 
-</style>
\ No newline at end of file
+@media screen and (max-width: 1300px) {
+  .top-container {
+    flex-direction: column;
+  }
+}
+
+@media screen and (max-width: 650px) {
+  .table-container th:nth-child(3), .table-container td:nth-child(3) {
+    display: none;
+  }
+}
+
+@media screen and (max-width: 440px) {
+  .table-container th:nth-child(3), .table-container td:nth-child(3) {
+    display: flex;
+  }
+}
+</style>
diff --git a/src/components/userDetails/ImportFiles.vue b/src/components/userDetails/ImportFiles.vue
new file mode 100644
index 0000000000000000000000000000000000000000..427f9760cc66c6d3a257994e5510c8299e93ea2f
--- /dev/null
+++ b/src/components/userDetails/ImportFiles.vue
@@ -0,0 +1,231 @@
+<script setup>
+import {ref} from 'vue';
+import BankStatementService from '@/services/internal/BankStatementService';
+import {useRouter} from 'vue-router';
+import axios from "axios";
+
+const router = useRouter();
+const errorMessage = ref('');
+
+const selectedFile = ref(null);
+const isUploadValid = ref(false);
+const selectedBank = ref('');
+
+const handleFileSelection = async (event) => {
+  const file = event.target.files[0];
+  if (!selectedBank.value || selectedBank.value === "") {
+    event.target.value = null;
+    selectedFile.value = null;
+    errorMessage.value = 'Du må velge en bank før du laster opp en fil. Vennligst prøv igjen.';
+    return;
+  }
+
+  if (file && file.type === 'application/pdf') {
+    selectedFile.value = file;
+    errorMessage.value = '';
+    await uploadBankStatement();
+  } else {
+    event.target.value = null;
+    selectedFile.value = null;
+    errorMessage.value = 'Vennligst last opp en gyldig PDF-fil.';
+  }
+};
+
+const uploadBankStatement = async () => {
+  let statementId;
+  if (!selectedFile.value || !selectedBank.value) {
+    errorMessage.value = 'Vennligst velg en bank og last opp en fil.';
+    return;
+  }
+  try {
+    errorMessage.value = 'Vi analyserer kontoutskriften din...';
+    const formData = new FormData();
+    formData.append('file', selectedFile.value);
+    statementId = await BankStatementService.addBankStatement(formData, selectedBank.value);
+    selectedFile.value = null;
+    isUploadValid.value = true;
+    errorMessage.value = 'Kontoutskrift lastet opp. Analyserer..';
+  } catch (error) {
+    if (axios.isAxiosError(error) && error.response) {
+      errorMessage.value = `Fikk ikke lastet opp kontoutsrift.`;
+    } else {
+      console.error('Cannot connect to server.', error);
+      errorMessage.value = 'Kan ikke koble til serveren. Vennligst prøv igjen senere.';
+    }
+    return;
+  }
+  try {
+    await BankStatementService.analyzeBankStatement(statementId);
+    errorMessage.value = 'Kontoutskrift bestod analysen. Gjerne velg en ny fil.';
+  } catch (error) {
+    if (axios.isAxiosError(error) && error.response) {
+      errorMessage.value = `Kontoutskrift ble lastet opp men analysen feilet. Gjerne last opp en ny fil.`;
+    } else {
+      console.error('Cannot connect to server.', error);
+      errorMessage.value = 'Kan ikke koble til serveren. Vennligst prøv igjen senere.';
+    }
+  }
+};
+
+const handleNext = async () => {
+  if (isUploadValid.value) {
+    await router.push('/');
+  } else {
+    errorMessage.value = 'Vennligst last opp minst en fil.';
+  }
+};
+</script>
+
+<template>
+  <div class="importFiles-main-container">
+    <div class="importFiles-content-container">
+      <h2>Kontoutskrifter</h2>
+      <p>Legg til kontoutskrifter fra de siste månedene. Vi anbefaler minst 3.</p>
+
+      <label style="align-self: flex-start" for="pickBank" >Velg bank :</label>
+      <div class="input-buttons-container">
+        <select class="full-width-input dropdown" id="pickBank" v-model="selectedBank">
+          <option disabled value="">Velg bank</option>
+          <option value="DNB">DNB</option>
+          <option value="HANDELSBANKEN">HANDELSBANKEN</option>
+          <option value="SPAREBANK1">SPAREBANK1</option>
+          <option value="ANNET">ANNET</option>
+        </select>
+        <label class="upload-button full-width-input">
+          Velg fil
+          <input
+              tabindex="0"
+              role="button"
+              @keydown.enter="handleFileSelection"
+              type="file"
+              @change="handleFileSelection"
+              accept="application/pdf"
+              multiple>
+        </label>
+      </div>
+
+      <div v-if="errorMessage" class="error">{{ errorMessage }}</div>
+      <div class="buttons-container">
+        <button class="backButton" @click="$emit('back')">GÃ¥ tilbake</button>
+        <button @click="handleNext">Neste</button>
+      </div>
+    </div>
+  </div>
+</template>
+
+
+<style scoped>
+.upload-container {
+  display: flex;
+  flex-direction: row;
+  padding: 10px;
+  width: 100%;
+  justify-content: center;
+  align-items: center;
+  gap: 10px;
+}
+
+input {
+  display: none;
+}
+
+.upload-button, .dropdown {
+  width: 100%;
+  margin-bottom: 10px;
+}
+
+.dropdown {
+  background-color: var(--accent-color-dark);
+}
+
+p {
+  text-align: center;
+  color: var(--dark-color);
+  font-size: 2vw;
+}
+
+.buttons-container {
+  display: flex;
+  flex-direction: row;
+  width: 100%;
+  gap: 10px;
+}
+
+button {
+  width: 100%;
+}
+
+.importFiles-main-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  height: 100%;
+  box-sizing: border-box;
+  padding: 2.5%;
+}
+
+.importFiles-content-container {
+  width: 80%;
+  background-color: var(--accent-color);
+  box-shadow: 2px 2px 4px 0 rgba(0, 0, 0, 0.25);
+  border-radius: var(--border-radius-general);
+  padding: 2.5%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 20px;
+}
+
+.error {
+  color: var(--error-text);
+  font-size: 14px;
+  text-align: center;
+  padding: 10px;
+  border-radius: var(--border-radius-general);
+  margin-top: 10px;
+}
+
+.upload-button {
+  background-color: var(--middle-color);
+  color: white;
+  border: none;
+  padding: 10px 20px;
+  border-radius: var(--border-radius-general);
+  cursor: pointer;
+  box-shadow: 0 2px 4px rgba(0,0,0,0.2);
+  transition: background-color 0.3s ease;
+  display: inline-block;
+}
+
+
+.input-buttons-container {
+  display: flex;
+  flex-direction: row;
+  width: 100%;
+  justify-content: space-between;
+  gap: 10px;
+}
+
+.full-width-input {
+  flex: 1;
+}
+
+.dropdown, .upload-button {
+  width: 100%;
+  margin-bottom: 10px;
+}
+
+.backButton {
+  background-color: var(--grey-general);
+  color: white;
+  width: 100%;
+}
+
+
+.backButton:hover {
+  background-color: var(--dark-color);
+  color: white;
+}
+</style>
diff --git a/src/components/userDetails/SelectHousehold.vue b/src/components/userDetails/SelectHousehold.vue
new file mode 100644
index 0000000000000000000000000000000000000000..bddc249a417f590d06e0173e9d5dc23c9dfaeb4c
--- /dev/null
+++ b/src/components/userDetails/SelectHousehold.vue
@@ -0,0 +1,280 @@
+<script setup>
+import {ref, onMounted} from 'vue';
+import {defineEmits} from 'vue';
+import AddAdditionalUserDetailsDTO from "@/models/user/AddAdditionalUserDetailsDTO";
+import UserDetailsService from "@/services/internal/UserDetailsService";
+import axios from "axios";
+
+const emit = defineEmits(['next']);
+
+const selectedOption = ref('');
+const errorMessage = ref('');
+
+const checkAndSetStoredValue = () => {
+  const storedHouseholdValue = sessionStorage.getItem('householdValue');
+  if (storedHouseholdValue) {
+    selectedOption.value = storedHouseholdValue;
+  }
+};
+
+onMounted(checkAndSetStoredValue);
+
+const handleSelect = (value) => {
+  selectedOption.value = value;
+  errorMessage.value = '';
+};
+
+const isSelected = (option) => {
+  return selectedOption.value === option;
+};
+
+const updateUserDetails = async () => {
+  try {
+    const income = parseInt(sessionStorage.getItem('incomeValue'));
+    const savingPercentage = parseInt(sessionStorage.getItem('savingPercentage'))
+    const livingStatus = parseInt(sessionStorage.getItem('householdValue'));
+    const addAdditionalUserDetailsDTO = new AddAdditionalUserDetailsDTO(income, savingPercentage, livingStatus);
+    await UserDetailsService.addAdditionalUserDetails(addAdditionalUserDetailsDTO);
+  } catch (error) {
+    if (axios.isAxiosError(error) && error.response) {
+      switch (error.response.status) {
+        case 400:
+          errorMessage.value = "Ugyldig inndata. Vennligst oppgi gyldige data.";
+          break;
+        case 500:
+          errorMessage.value = "Intern serverfeil. Vennligst prøv igjen senere.";
+          break;
+        default:
+          errorMessage.value = error.response.data.errorMessage;
+          break;
+      }
+    } else {
+      console.error('Cannot connect to server.', error);
+      errorMessage.value = 'Kan ikke koble til serveren. Vennligst prøv igjen senere.';
+    }
+  }
+};
+
+const handleNext = async () => {
+
+  if (selectedOption.value !== '') {
+    sessionStorage.setItem('householdValue', parseInt(selectedOption.value));
+    emit('next');
+    await updateUserDetails();
+  } else {
+    errorMessage.value = 'Vennligst velg en bosituasjon før du går videre.';
+  }
+};
+
+</script>
+
+<template>
+  <div class="household-main-container">
+    <div class="household-content-container">
+      <h2>Bosituasjon</h2>
+      <div class="options-container">
+        <div
+            class="option"
+            :class="{ 'selected': isSelected('1') }"
+            tabindex="0"
+            role="tab"
+            @keydown.enter="handleSelect('1')"
+            @click="handleSelect('1')">
+          <img src="@/assets/img/userDetails/livingalone.webp" alt="Bor alene">
+          <p>Bor alene</p>
+        </div>
+        <div
+            class="option"
+            :class="{ 'selected': isSelected('2') }"
+            tabindex="0"
+            role="tab"
+            @keydown.enter="handleSelect('2')"
+            @click="handleSelect('2')">
+          <img src="@/assets/img/userDetails/couplewithoutkids.webp" alt="Par uten barn">
+          <p>Par uten barn</p>
+        </div>
+        <div
+            class="option"
+            :class="{ 'selected': isSelected('3') }"
+            tabindex="0"
+            role="tab"
+            @keydown.enter="handleSelect('3')"
+            @click="handleSelect('3')">
+          <img src="@/assets/img/userDetails/couplewithkids.webp" alt="Par med barn">
+          <p>Par med barn</p>
+        </div>
+        <div
+            class="option"
+            :class="{ 'selected': isSelected('0') }"
+            tabindex="0"
+            role="tab"
+            @keydown.enter="handleSelect('0')"
+            @click="handleSelect('0')">
+          <img src="@/assets/img/userDetails/other.webp" alt="Annet">
+          <p>Annet</p>
+        </div>
+      </div>
+
+
+      <div v-if="errorMessage" class="error-message">{{ errorMessage }}</div>
+      <div class="buttons-container">
+        <button
+            id="backButton"
+            tabindex="0"
+            role="tab"
+            @keydown.enter="$emit('back')"
+            @click="$emit('back')">
+          GÃ¥ tilbake
+        </button>
+        <button
+            tabindex="0"
+            role="tab"
+            @keydown.enter="handleNext"
+            @click="handleNext"
+        >Neste
+        </button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.error-message {
+  margin-top: 5%;
+}
+
+#backButton {
+  background-color: dimgrey;
+}
+
+.buttons-container {
+  display: flex;
+  flex-direction: row;
+  width: 100%;
+  gap: 10px;
+  margin-top: 30px;
+}
+
+button {
+  width: 100%;
+}
+
+.household-main-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  height: 100%;
+  box-sizing: border-box;
+  padding: 2.5%;
+}
+
+.household-content-container {
+  width: 90%;
+  max-width: 800px;
+  background-color: var(--accent-color);
+  box-shadow: 2px 2px 4px 0 rgba(0, 0, 0, 0.25);
+  border-radius: var(--border-radius-general);
+  padding: 2.5%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.option {
+  cursor: pointer;
+  text-align: center;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  transition: box-shadow 0.3s ease, transform 0.3s ease;
+  padding: 2.5%;
+  width: 100%;
+  border-radius: var(--border-radius-general);
+  box-sizing: border-box;
+}
+
+option:hover, option:focus, .option.selected {
+  box-shadow: 0 0 15px var(--black-general);
+  transform: scale(1.05);
+  margin: 0;
+}
+
+
+.options-container {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 5%;
+  padding: 2.5%;
+  width: 100%;
+  box-sizing: border-box;
+}
+
+.option img {
+  max-width: 80%;
+  max-height: 80%;
+  height: auto;
+  border-radius: var(--border-radius-general);
+  margin-top: 5%;
+}
+
+.option p {
+  font-size: 3vw;
+  margin-top: 8px;
+  color: var(--black-text);
+}
+
+.error-message {
+  color: var(--error-text);
+  text-align: center;
+}
+
+@media (min-width: 620px) {
+  .household-main-container {
+    align-items: center;
+    justify-content: center;
+    padding: 5%;
+  }
+
+  .household-content-container {
+    max-width: 80%;
+    padding: 2%;
+  }
+
+  .options-container {
+    grid-template-columns: repeat(4, 1fr);
+  }
+
+  .option {
+    max-width: 95%;
+  }
+
+  .option img {
+    max-height: 200px;
+  }
+
+  .option p {
+    font-size: 1.5em;
+  }
+
+  .buttons-container {
+    flex-direction: row;
+    gap: 20px;
+  }
+}
+
+@media (max-width: 400px) {
+  .options-container {
+    grid-template-columns: 1fr; /* Change to one column */
+  }
+  .option {
+    max-width: 100%; /* Use the full width */
+    margin-bottom: 10px; /* Add some space between the options */
+  }
+}
+
+
+</style>
+
diff --git a/src/components/userDetails/SelectIncome.vue b/src/components/userDetails/SelectIncome.vue
new file mode 100644
index 0000000000000000000000000000000000000000..5d13448e78e3ca5302b2633f158c13321b2ed829
--- /dev/null
+++ b/src/components/userDetails/SelectIncome.vue
@@ -0,0 +1,208 @@
+<script setup>
+import {ref, computed, onMounted} from 'vue';
+import {defineEmits} from 'vue';
+
+const emit = defineEmits(['next']);
+
+const rawIncome = ref('');
+const formattedIncome = ref('');
+const savingPercentage = ref('');
+
+const errorMessage = ref('');
+
+const checkAndSetStoredValue = () => {
+  const storedIncome = sessionStorage.getItem('incomeValue');
+  if (storedIncome) {
+    rawIncome.value = storedIncome;
+    formattedIncome.value = formatNumber(storedIncome);
+  }
+
+  const storedSavingPercentage = sessionStorage.getItem('savingPercentage');
+  if (storedSavingPercentage) {
+    savingPercentage.value = storedSavingPercentage;
+  }
+
+  const storedConsumption = sessionStorage.getItem('calculatedConsumption');
+  if (storedConsumption) {
+    calculatedConsumption.value = storedConsumption;
+  }
+};
+
+onMounted(checkAndSetStoredValue);
+
+const handleInput = (value) => {
+  let numericValue = value.replace(/\D/g, '');
+  formattedIncome.value = formatNumber(numericValue);
+  rawIncome.value = numericValue;
+};
+
+const formatNumber = (num) => {
+  return num.replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
+};
+
+const isInputValid = () => {
+  return rawIncome.value.trim() !== '' &&
+      savingPercentage.value !== "" &&
+      calculatedConsumption.value >= 0;
+};
+
+const handleNext = () => {
+  if (isInputValid()) {
+    errorMessage.value = '';
+    sessionStorage.setItem('incomeValue', rawIncome.value);
+    sessionStorage.setItem('savingPercentage', savingPercentage.value);
+    sessionStorage.setItem('calculatedConsumption', calculatedConsumption.value);
+    emit('next');
+  } else {
+    errorMessage.value = "Vennligst fyll ut netto inntekt og prosentandel du ønsker å spare.";
+  }
+};
+
+const validatePercentage = () => {
+  if (savingPercentage.value > 100) {
+    savingPercentage.value = 100;
+  } else if (savingPercentage.value < 0) {
+    savingPercentage.value = 0;
+  }
+};
+
+const calculatedConsumption = computed(() => {
+  const income = parseInt(rawIncome.value || '0', 10);
+  const savings = parseInt(savingPercentage.value || '0', 10);
+  return Math.floor((1 - savings / 100) * income);
+});
+
+</script>
+
+<template>
+  <div class="income-main-container">
+    <div class="income-content-container">
+      <h2>Beregning av månedlige utgifter</h2>
+      <p>Dette skjemaet vil regne ut hvor mye av inntekten din du har til forbruk, etter skatt og sparing</p>
+
+      <div class="input-group">
+        <label for="income">MÃ¥nedlig inntekt etter skatt:</label>
+        <input type="text" id="income" v-model="formattedIncome" @input="handleInput($event.target.value)"
+               placeholder="10 000 kr"/>
+      </div>
+
+      <div class="input-group horizontal">
+        <div class="saving">
+          <label for="savingPercentage">Spareprosent:</label>
+          <input type="number"
+                 id="savingPercentage"
+                 v-model="savingPercentage"
+                 @input="validatePercentage"
+                 min="0" max="100" step="1"
+                 placeholder="eks. 35%">
+        </div>
+
+        <div class="consumption">
+          <label for="consumption">Beregnet forbruks sum:</label>
+          <input type="number" id="consumption" :value="calculatedConsumption" readonly placeholder="3500 kr"/>
+        </div>
+      </div>
+
+      <button @click="handleNext">Videre</button>
+      <div v-if="errorMessage" class="error-message">{{ errorMessage }}</div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+label {
+  padding-left: 1%;
+}
+
+.income-main-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 2.5%;
+  width: 100%;
+  box-sizing: border-box;
+  height: 100%;
+}
+
+.income-content-container {
+  box-shadow: 2px 2px 4px 0 rgba(0, 0, 0, 0.25);
+  width: 80%;
+  max-width: 800px;
+  min-height: 400px;
+  background-color: var(--accent-color);
+  border-radius: var(--border-radius-general);
+  padding: 2.5%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.input-group {
+  width: 100%;
+  margin-bottom: 20px;
+  box-sizing: border-box;
+}
+
+.horizontal {
+  display: flex;
+  width: 100%;
+  gap: 20px;
+}
+
+h2, p {
+  text-align: center;
+  width: 80%;
+  margin-bottom: 20px;
+}
+
+p {
+  color: var(--dark-color);
+}
+
+input {
+  width: 100%;
+  height: 50px;
+  border-radius: var(--border-radius-general);
+  border: none;
+  padding: 10px;
+  box-sizing: border-box;
+  margin-top: 8px;
+}
+
+.error-message {
+  color: var(--error-text);
+  font-size: 14px;
+  margin-top: 10px;
+}
+
+.saving {
+  display: flex;
+  flex-direction: column;
+  width: 30%;
+}
+
+.consumption {
+  width: 70%;
+}
+
+
+@media (max-width: 800px) {
+  .horizontal {
+    flex-direction: column;
+    gap: 0;
+  }
+
+  .saving, .consumption {
+    width: 100%;
+  }
+
+  .saving {
+    margin-bottom: 20px;
+  }
+
+  .income-content-container {
+    width: 95%;
+  }
+}
+</style>
diff --git a/src/models/errors/ErrorResponseDTO.js b/src/models/errors/ErrorResponseDTO.js
new file mode 100644
index 0000000000000000000000000000000000000000..92be8b781b31a4265e5526e445460a09a68a8d42
--- /dev/null
+++ b/src/models/errors/ErrorResponseDTO.js
@@ -0,0 +1,15 @@
+/**
+ * Data transfer object for error responses.
+ */
+class ErrorResponseDTO {
+
+  /**
+   * Constructs an instance of ErrorResponseDTO.
+   * @param {string} errorMessage - The error message.
+   */
+  constructor(errorMessage) {
+    this.errorMessage = errorMessage;
+  }
+}
+
+export default new ErrorResponseDTO();
\ No newline at end of file
diff --git a/src/models/transaction/TransactionDTO.js b/src/models/transaction/TransactionDTO.js
new file mode 100644
index 0000000000000000000000000000000000000000..6d6ba54789bb2c5c96e9ffb9a2a62e075a9ebca2
--- /dev/null
+++ b/src/models/transaction/TransactionDTO.js
@@ -0,0 +1,25 @@
+/**
+ * Data transfer object for representing a transaction.
+ */
+class TransactionDTO {
+
+    /**
+     * Constructs an instance of TransactionDTO.
+     * @param {number} id - The ID of the transaction.
+     * @param {Date} date - The date of the transaction.
+     * @param {string} description - The description of the transaction.
+     * @param {number} amount - The amount of the transaction.
+     * @param {boolean} isIncoming - Indicates whether the transaction is incoming (true) or outgoing (false).
+     * @param {string} category - The category of the transaction.
+     */
+    constructor(id, date, description, amount, isIncoming, category) {
+        this.id = id;
+        this.date = date;
+        this.description = description;
+        this.amount = amount;
+        this.isIncoming = isIncoming;
+        this.category = category;
+    }
+}
+
+export default TransactionDTO;
\ No newline at end of file
diff --git a/src/models/user/AddAdditionalUserDetailsDTO.js b/src/models/user/AddAdditionalUserDetailsDTO.js
new file mode 100644
index 0000000000000000000000000000000000000000..436b20b82d7fa52cdb6407fea25b6bfb8702cf85
--- /dev/null
+++ b/src/models/user/AddAdditionalUserDetailsDTO.js
@@ -0,0 +1,20 @@
+
+/**
+ * Data transfer object for adding additional user details.
+ */
+class AddAdditionalUserDetailsDTO {
+
+  /**
+   * Constructs an instance of AddAdditionalUserDetailsDTO.
+   * @param {number} income - The user's income.
+   * @param {number} savingPercentage - The percentage of income saved by the user.
+   * @param {string} livingStatus - The user's living status.
+   */
+  constructor(income, savingPercentage, livingStatus) {
+    this.income = income;
+    this.savingPercentage = savingPercentage;
+    this.livingStatus = livingStatus;
+  }
+}
+
+export default AddAdditionalUserDetailsDTO;
\ No newline at end of file
diff --git a/src/models/user/ChangeFirstNameDTO.js b/src/models/user/ChangeFirstNameDTO.js
new file mode 100644
index 0000000000000000000000000000000000000000..81cafd53e2a4762fe9a317a91e593522f3727f98
--- /dev/null
+++ b/src/models/user/ChangeFirstNameDTO.js
@@ -0,0 +1,14 @@
+/**
+ * Data transfer object for changing the first name of a user.
+ */
+class ChangeFirstNameDTO {
+  /**
+   * Constructs an instance of ChangeFirstNameDTO.
+   * @param {string} newFirstName - The new first name of the user.
+   */
+  constructor(newFirstName) {
+    this.newFirstName = newFirstName;
+  }
+}
+
+export default ChangeFirstNameDTO;
\ No newline at end of file
diff --git a/src/models/user/ChangeIncomeDTO.js b/src/models/user/ChangeIncomeDTO.js
new file mode 100644
index 0000000000000000000000000000000000000000..6748b5b6e4f4426ca0450a69e5180f4dc2b17655
--- /dev/null
+++ b/src/models/user/ChangeIncomeDTO.js
@@ -0,0 +1,14 @@
+/**
+ * Data transfer object for changing the income of a user.
+ */
+class ChangeIncomeDTO {
+  /**
+   * Constructs an instance of ChangeIncomeDTO.
+   * @param {number} newIncome - The new income of the user.
+   */
+  constructor(newIncome) {
+    this.newIncome = newIncome;
+  }
+}
+
+export default ChangeIncomeDTO;
\ No newline at end of file
diff --git a/src/models/user/ChangeLastNameDTO.js b/src/models/user/ChangeLastNameDTO.js
new file mode 100644
index 0000000000000000000000000000000000000000..0003b37ec306185894f380d117dcf77aab8dfb2b
--- /dev/null
+++ b/src/models/user/ChangeLastNameDTO.js
@@ -0,0 +1,14 @@
+/**
+ * Data transfer object for changing the last name of a user.
+ */
+class ChangeLastNameDTO {
+    /**
+     * Constructs an instance of ChangeLastNameDTO.
+     * @param {string} newLastName - The new last name of the user.
+     */
+    constructor(newLastName) {
+        this.newLastName = newLastName;
+    }
+}
+
+export default ChangeLastNameDTO;
diff --git a/src/models/user/ChangeLivingStatusDTO.js b/src/models/user/ChangeLivingStatusDTO.js
new file mode 100644
index 0000000000000000000000000000000000000000..3b8ef905fad8f448cdeb95106c58975c10317316
--- /dev/null
+++ b/src/models/user/ChangeLivingStatusDTO.js
@@ -0,0 +1,15 @@
+/**
+ * Data transfer object for changing the living status of a user.
+ */
+class ChangeLivingStatusDTO {
+
+  /**
+   * Constructs an instance of ChangeLivingStatusDTO.
+   * @param {string} newLivingStatus - The new living status of the user.
+   */
+  constructor(newLivingStatus) {
+      this.newLivingStatus = newLivingStatus;
+    }
+  }
+  
+  export default ChangeLivingStatusDTO;
\ No newline at end of file
diff --git a/src/models/user/ChangePasswordDTO.js b/src/models/user/ChangePasswordDTO.js
new file mode 100644
index 0000000000000000000000000000000000000000..ce021028351a67daf408b9db785bab29d233b722
--- /dev/null
+++ b/src/models/user/ChangePasswordDTO.js
@@ -0,0 +1,17 @@
+/**
+ * Data transfer object for changing the password of a user.
+ */
+class ChangePasswordDTO {
+
+  /**
+   * Constructs an instance of ChangePasswordDTO.
+   * @param {string} oldPassword - The old password of the user.
+   * @param {string} newPassword - The new password of the user.
+   */
+  constructor(oldPassword, newPassword) {
+    this.oldPassword = oldPassword;
+    this.newPassword = newPassword;
+  }
+}
+
+export default ChangePasswordDTO;
\ No newline at end of file
diff --git a/src/models/user/ChangeSavingPercentageDTO.js b/src/models/user/ChangeSavingPercentageDTO.js
new file mode 100644
index 0000000000000000000000000000000000000000..45a516ec11f75ce02618bb8fc32f9a142212cd80
--- /dev/null
+++ b/src/models/user/ChangeSavingPercentageDTO.js
@@ -0,0 +1,15 @@
+/**
+ * Data transfer object for changing the saving percentage of a user.
+ */
+class ChangeSavingPercentageDTO {
+
+  /**
+   * Constructs an instance of ChangeSavingPercentageDTO.
+   * @param {number} newSavingPercentage - The new saving percentage of the user.
+   */
+  constructor(newSavingPercentage) {
+    this.newSavingPercentage = newSavingPercentage;
+  }
+}
+
+export default ChangeSavingPercentageDTO;
\ No newline at end of file
diff --git a/src/models/user/EmailCodeRequestDto.js b/src/models/user/EmailCodeRequestDto.js
new file mode 100644
index 0000000000000000000000000000000000000000..7cab716ce2c596ba535e2ed83502509b776000f8
--- /dev/null
+++ b/src/models/user/EmailCodeRequestDto.js
@@ -0,0 +1,19 @@
+/**
+ * Data transfer object for sending email verification code.
+ */
+class EmailCodeRequestDto {
+  email;
+  verificationCode;
+
+  /**
+   * Constructs an instance of EmailCodeRequestDto.
+   * @param {string} email - The email address to send the verification code.
+   * @param {string} verificationCode - The verification code.
+   */
+  constructor(email, verificationCode) {
+    this.email = email;
+    this.verificationCode = verificationCode;
+  }
+}
+
+export default EmailCodeRequestDto;
\ No newline at end of file
diff --git a/src/models/user/EmailExistDTO.js b/src/models/user/EmailExistDTO.js
new file mode 100644
index 0000000000000000000000000000000000000000..a69f2c25d58fa68bb0a17b103c0a91ce09c89116
--- /dev/null
+++ b/src/models/user/EmailExistDTO.js
@@ -0,0 +1,15 @@
+/**
+ * Data transfer object for checking if an email exists.
+ */
+class EmailExistDTO {
+
+  /**
+   * Constructs an instance of EmailExistDTO.
+   * @param {string} email - The email address to check for existence.
+   */
+  constructor(email) {
+    this.email = email;
+  }
+}
+
+export default EmailExistDTO;
\ No newline at end of file
diff --git a/src/models/user/LoginRequestDTO.js b/src/models/user/LoginRequestDTO.js
new file mode 100644
index 0000000000000000000000000000000000000000..011cf5f86d1fb6629851bba0f664b6639fccd6e0
--- /dev/null
+++ b/src/models/user/LoginRequestDTO.js
@@ -0,0 +1,17 @@
+/**
+ * Data transfer object for user login request.
+ */
+class LoginRequestDTO {
+
+  /**
+   * Constructs an instance of LoginRequestDTO.
+   * @param {string} email - The email address of the user.
+   * @param {string} password - The password of the user.
+   */
+  constructor(email, password) {
+    this.email = email;
+    this.password = password;
+  }
+}
+
+export default LoginRequestDTO;
\ No newline at end of file
diff --git a/src/models/user/LoginResponseDTO.js b/src/models/user/LoginResponseDTO.js
new file mode 100644
index 0000000000000000000000000000000000000000..3e1c1e03006f199f7dfae3204f69cf80bc2c44c7
--- /dev/null
+++ b/src/models/user/LoginResponseDTO.js
@@ -0,0 +1,16 @@
+
+/**
+ * Data transfer object for user login response.
+ */
+class LoginResponseDTO {
+
+    /**
+     * Constructs an instance of LoginResponseDTO.
+     * @param {string} token - The authentication token.
+     */
+    constructor(token) {
+        this.token = token;
+    }
+}
+
+export default LoginResponseDTO;
\ No newline at end of file
diff --git a/src/models/user/RegisterRequestDTO.js b/src/models/user/RegisterRequestDTO.js
new file mode 100644
index 0000000000000000000000000000000000000000..88432243f1e9b014c072cab4513ef3f43a2b9f9b
--- /dev/null
+++ b/src/models/user/RegisterRequestDTO.js
@@ -0,0 +1,23 @@
+/**
+ * Data transfer object for registering a new user.
+ */
+class RegisterUserDTO {
+
+  /**
+   * Constructs an instance of RegisterUserDTO.
+   * @param {string} email - The email address of the user.
+   * @param {string} firstName - The first name of the user.
+   * @param {string} lastName - The last name of the user.
+   * @param {string} password - The password of the user.
+   * @param {string} emailVerificationCode - The email verification code.
+   */
+  constructor(email, firstName, lastName, password, emailVerificationCode) {
+    this.email = email;
+    this.firstName = firstName;
+    this.lastName = lastName;
+    this.password = password;
+    this.emailVerificationCode = emailVerificationCode;
+  }
+}
+
+export default RegisterUserDTO;
\ No newline at end of file
diff --git a/src/models/user/ResetPasswordDTO.js b/src/models/user/ResetPasswordDTO.js
new file mode 100644
index 0000000000000000000000000000000000000000..752ae2cb6e24877bad0b39f59858a7d97ed2b762
--- /dev/null
+++ b/src/models/user/ResetPasswordDTO.js
@@ -0,0 +1,22 @@
+/**
+ * Data Transfer Object (DTO) containing information required for resetting a user's password.
+ *
+ * @author Ramitn Samavat
+ */
+class ResetPasswordDTO {
+
+  /**
+   * Constructs a new ResetPasswordDTO instance.
+   *
+   * @param {string} email The email address of the user.
+   * @param {string} emailVerificationCode The verification code sent to the user's email.
+   * @param {string} newPassword The new password to be set for the user's account.
+   */
+  constructor(email, emailVerificationCode, newPassword) {
+    this.email = email;
+    this.emailVerificationCode = emailVerificationCode;
+    this.newPassword = newPassword;
+  }
+}
+
+export default ResetPasswordDTO;
\ No newline at end of file
diff --git a/src/router/index.js b/src/router/index.js
index c177d52732d8603812fc46bd426449651e174315..ad45263f23eb00f18f1b4d50bb8b9461d880fcd1 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -5,40 +5,67 @@ const routes = [
     path: "/",
     name: "home",
     component: () => import("../views/HomeView.vue"),
+    meta: { requiresAuth: false , showSidebar: true, hideStats: false}
+  },
+  {
+    path: "/verification",
+    name: "verification",
+    component: () => import("../views/VerificationView.vue"),
+    meta: { requiresAuth: false, showSidebar: true, hideStats: false}
   },
   {
     path: "/budget",
     name: "budget",
     component: () => import("../views/BudgetView.vue"),
-    meta: { requiresAuth: true },
+    meta: { requiresAuth: true, showSidebar: true, hideStats: false}
   },
   {
     path: "/goals",
     name: "goals",
     component: () => import("../views/GoalsView.vue"),
-    meta: { requiresAuth: true },
+    meta: { requiresAuth: true, showSidebar: true, hideStats: false}
   },
   {
     path: "/news",
     name: "news",
     component: () => import("../views/NewsView.vue"),
+    meta: { requiresAuth: false, showSidebar: true, hideStats: false }
   },
   {
-    path: "/profile",
+    path: "/profile/:component?",
     name: "profile",
     component: () => import("../views/ProfileView.vue"),
-    meta: { requiresAuth: true },
+    meta: { requiresAuth: true, showSidebar: false, hideStats: true }
   },
   {
     path: "/transactions",
     name: "transactions",
     component: () => import("../views/TransactionsView.vue"),
-    meta: { requiresAuth: true },
+    meta: { requiresAuth: true, showSidebar: true, hideStats: false }
   },
   {
     path: "/login",
     name: "login",
     component: () => import("../views/LoginView.vue"),
+    meta: { requiresAuth: false, showSidebar: false, hideStats: true}
+  },
+  {
+    path: "/userDetails",
+    name: "userdetails",
+    component: () => import("../views/UserDetailsView.vue"),
+    meta: { requiresAuth: true, showSidebar: false, hideStats: true}
+  },
+  {
+    path: "/contact",
+    name: "contact",
+    component: () => import("../views/ContactView.vue"),
+    meta: { requiresAuth: false, showSidebar: false, hideStats: true}
+  },
+  {
+    path: "/forgot-password",
+    name: "forgot-password",
+    component: () => import("../views/ForgotPasswordView.vue"),
+    meta: { requiresAuth: false, showSidebar: false, hideStats: true}
   },
 ]
 
@@ -51,9 +78,19 @@ router.beforeEach((to, from, next) => {
   const requiresAuth = to.matched.some((record) => record.meta.requiresAuth);
   const authToken = sessionStorage.getItem("authToken");
 
-  if (requiresAuth && !authToken) {
-    sessionStorage.setItem("redirectAfterLogin", to.fullPath);
-    next({ name: "login" });
+  //alert(to.fullPath)
+
+  if (requiresAuth) {
+    if (authToken) {
+      next();
+    } else {
+      next(
+          {
+            name: "login",
+            query: { redirect: to.fullPath }, // store the route the user was trying to access
+          }
+      );
+    }
   } else {
     next();
   }
diff --git a/src/services/error/GoalErrorService.js b/src/services/error/GoalErrorService.js
new file mode 100644
index 0000000000000000000000000000000000000000..47f52596e02ebc22deb5699af29fea6f88f27eed
--- /dev/null
+++ b/src/services/error/GoalErrorService.js
@@ -0,0 +1,338 @@
+import axios from "axios";
+
+/**
+ * Service class for handling errors related to goals.
+ */
+class GoalErrorService {
+
+  /**
+   * Handles errors during goal contribution fetching, displaying appropriate messages to the user.
+   * @param {Error} error - The error object from goal contribution fetching attempt.
+   * @returns {string} Error message.
+   */
+  async handleErrorGetGoalContribution(error) {
+    if (axios.isAxiosError(error) && error.response) {
+      switch (error.response.status) {
+        case 500:
+          return 'Intern serverfeil. Vennligst prøv igjen senere.';
+        case 400:
+        case 404:
+          return 'Error: Finner ikke sparemål.';
+        default:
+          return error.response.data.errorMessage;
+      }
+    } else {
+      console.error('Cannot connect to server.', error);
+      return 'Kan ikke koble til serveren. Vennligst prøv igjen senere.';
+    }
+  }
+
+  /**
+   * Handles errors during progress update, displaying appropriate messages to the user.
+   * @param {Error} error - The error object from progress update attempt.
+   * @returns {string} Error message.
+   */
+  async handleErrorUpdateProgress(error) {
+    if (axios.isAxiosError(error) && error.response) {
+      switch (error.response.status) {
+        case 500:
+          return 'Intern serverfeil. Vennligst prøv igjen senere.';
+        case 400:
+        case 404:
+          return 'Error: Finner ikke utfordring.';
+        default:
+          return error.response.data.errorMessage;
+      }
+    } else {
+      console.error('Cannot connect to server.', error);
+      return 'Kan ikke koble til serveren. Vennligst prøv igjen senere.';
+    }
+  }
+
+  /**
+   * Handles errors during challenge addition, displaying appropriate messages to the user.
+   * @param {Error} error - The error object from challenge addition attempt.
+   * @returns {string} Error message.
+   */
+  async handleErrorAddChallenge(error) {
+    if (axios.isAxiosError(error) && error.response) {
+      switch (error.response.status) {
+        case 500:
+          return 'Intern serverfeil. Vennligst prøv igjen senere.';
+        case 400:
+        case 404:
+          return 'Error: utfordring kan ikke lages med gitte verdier.';
+        default:
+          return error.response.data.errorMessage;
+      }
+    } else {
+      console.error('Cannot connect to server.', error);
+      return 'Kan ikke koble til serveren. Vennligst prøv igjen senere.';
+    }
+  }
+
+  /**
+   * Handles errors during badge creation, displaying appropriate messages to the user.
+   * @param {Error} error - The error object from badge creation attempt.
+   * @returns {string} Error message.
+   */
+  async handleErrorCreateBadge(error) {
+    if (axios.isAxiosError(error) && error.response) {
+      switch (error.response.status) {
+        case 500:
+          return 'Intern serverfeil. Vennligst prøv igjen senere.';
+        case 400:
+        case 404:
+          return 'Error: Type på trofe er ikke gjenkjent.';
+        default:
+          return error.response.data.errorMessage;
+      }
+    } else {
+      console.error('Cannot connect to server.', error);
+      return 'Kan ikke koble til serveren. Vennligst prøv igjen senere.';
+    }
+  }
+
+  /**
+   * Handles errors during individual challenge fetching, displaying appropriate messages to the user.
+   * @param {Error} error - The error object from individual challenge fetching attempt.
+   * @returns {string} Error message.
+   */
+  async handleErrorGetChallenge(error) {
+    if (axios.isAxiosError(error) && error.response) {
+      switch (error.response.status) {
+        case 500:
+          return 'Intern serverfeil. Vennligst prøv igjen senere.';
+        case 400:
+        case 404:
+          return 'Error: Finner ikke utfordring.';
+        default:
+          return error.response.data.errorMessage;
+      }
+    } else {
+      console.error('Cannot connect to server.', error);
+      return 'Kan ikke koble til serveren. Vennligst prøv igjen senere.';
+    }
+  }
+
+  /**
+   * Handles errors during multiple challenge fetching, displaying appropriate messages to the user.
+   * @param {Error} error - The error object from multiple challenge fetching attempt.
+   * @returns {string} Error message.
+   */
+  async handleErrorGetChallenges(error) {
+    if (axios.isAxiosError(error) && error.response) {
+      switch (error.response.status) {
+        case 500:
+          return 'Intern serverfeil. Vennligst prøv igjen senere.';
+        case 400:
+        case 404:
+          return 'Error: Finner ingen utfordringer knyttet til bruker.';
+        default:
+          return error.response.data.errorMessage;
+      }
+    } else {
+      console.error('Cannot connect to server.', error);
+      return 'Kan ikke koble til serveren. Vennligst prøv igjen senere.';
+    }
+  }
+
+  /**
+   * Handles errors during amount saved fetching, displaying appropriate messages to the user.
+   * @param {Error} error - The error object from amount saved fetching attempt.
+   * @returns {string} Error message.
+   */
+  async handleErrorGetAmountSaved(error) {
+    if (axios.isAxiosError(error) && error.response) {
+      switch (error.response.status) {
+        case 500:
+          return 'Intern serverfeil. Vennligst prøv igjen senere.';
+        case 400:
+        case 404:
+          return 'Error: Mengde spart på sparemål er for tiden ikke tiljengelig.';
+        default:
+          return error.response.data.errorMessage;
+      }
+    } else {
+      console.error('Cannot connect to server.', error);
+      return 'Kan ikke koble til serveren. Vennligst prøv igjen senere.';
+    }
+  }
+
+  /**
+   * Handles errors during individual goal fetching, displaying appropriate messages to the user.
+   * @param {Error} error - The error object from individual goal fetching attempt.
+   * @returns {string} Error message.
+   */
+  async handleErrorGetGoal(error) {
+    if (axios.isAxiosError(error) && error.response) {
+      switch (error.response.status) {
+        case 500:
+          return 'Intern serverfeil. Vennligst prøv igjen senere.';
+        case 400:
+        case 404:
+          return 'Error: Finner ikke sparemål.';
+        default:
+          return error.response.data.errorMessage;
+      }
+    } else {
+      console.error('Cannot connect to server.', error);
+      return 'Kan ikke koble til serveren. Vennligst prøv igjen senere.';
+    }
+  }
+
+  /**
+   * Handles errors during multiple goals fetching, displaying appropriate messages to the user.
+   * @param {Error} error - The error object from multiple goals fetching attempt.
+   * @returns {string} Error message.
+   */
+  async handleErrorGetGoals(error) {
+    if (axios.isAxiosError(error) && error.response) {
+      switch (error.response.status) {
+        case 500:
+          return 'Intern serverfeil. Vennligst prøv igjen senere.';
+        case 400:
+        case 404:
+          return 'Error: Finner ingen sparemål under bruker.';
+        default:
+          return error.response.data.errorMessage;
+      }
+    } else {
+      console.error('Cannot connect to server.', error);
+      return 'Kan ikke koble til serveren. Vennligst prøv igjen senere.';
+    }
+  }
+
+  /**
+   * Handles errors during goal joining, displaying appropriate messages to the user.
+   * @param {Error} error - The error object from goal joining attempt.
+   * @returns {string} Error message.
+   */
+  async handleErrorJoinGoal(error) {
+    if (axios.isAxiosError(error) && error.response) {
+      switch (error.response.status) {
+        case 500:
+          return 'Intern serverfeil. Vennligst prøv igjen senere.';
+        case 400:
+        case 404:
+          return 'Error: Kode eksisterer ikke.';
+        default:
+          return error.response.data.errorMessage;
+      }
+    } else {
+      console.error('Cannot connect to server.', error);
+      return 'Kan ikke koble til serveren. Vennligst prøv igjen senere.';
+    }
+  }
+
+  /**
+   * Handles errors during challenge joining, displaying appropriate messages to the user.
+   * @param {Error} error - The error object from challenge joining attempt.
+   * @returns {string} Error message.
+   */
+  async handleErrorJoinChallenge(error) {
+    if (axios.isAxiosError(error) && error.response) {
+      switch (error.response.status) {
+        case 500:
+          return 'Intern serverfeil. Vennligst prøv igjen senere.';
+        case 400:
+        case 404:
+          return 'Error: Kode eksisterer ikke.';
+        default:
+          return error.response.data.errorMessage;
+      }
+    } else {
+      console.error('Cannot connect to server.', error);
+      return 'Kan ikke koble til serveren. Vennligst prøv igjen senere.';
+    }
+  }
+
+  /**
+   * Handles errors during contribution addition, displaying appropriate messages to the user.
+   * @param {Error} error - The error object from contribution addition attempt.
+   * @returns {string} Error message.
+   */
+  async handleErrorAddContribution(error) {
+    if (axios.isAxiosError(error) && error.response) {
+      switch (error.response.status) {
+        case 500:
+          return 'Intern serverfeil. Vennligst prøv igjen senere.';
+        case 400:
+        case 404:
+          return 'Error: Ugyldig mengde.';
+        default:
+          return error.response.data.errorMessage;
+      }
+    } else {
+      console.error('Cannot connect to server.', error);
+      return 'Kan ikke koble til serveren. Vennligst prøv igjen senere.';
+    }
+  }
+
+  /**
+   * Handles errors during challenge code sending, displaying appropriate messages to the user.
+   * @param {Error} error - The error object from challenge code sending attempt.
+   * @returns {string} Error message.
+   */
+  async handleErrorSendChallengeCode(error) {
+    if (axios.isAxiosError(error) && error.response) {
+      switch (error.response.status) {
+        case 500:
+          return 'Intern serverfeil. Vennligst prøv igjen senere.';
+        case 400:
+        case 404:
+          return 'Error: Email serveren er nede for øyeblikket';
+        default:
+          return error.response.data.errorMessage;
+      }
+    } else {
+      console.error('Cannot connect to server.', error);
+      return 'Kan ikke koble til serveren. Vennligst prøv igjen senere.';
+    }
+  }
+
+  /**
+   * Handles errors during friend challenge fetching, displaying appropriate messages to the user.
+   * @param {Error} error - The error object from friend challenge fetching attempt.
+   * @returns {string} Error message.
+   */
+  async handleErrorGetFriendChallenge(error) {
+    if (axios.isAxiosError(error) && error.response) {
+      switch (error.response.status) {
+        case 500:
+          return 'Intern serverfeil. Vennligst prøv igjen senere.';
+        case 400:
+        case 404:
+          return 'Error: Finner ingen brukere knyttet til denne utfordringen.';
+        default:
+          return error.response.data.errorMessage;
+      }
+    } else {
+      console.error('Cannot connect to server.', error);
+      return 'Kan ikke koble til serveren. Vennligst prøv igjen senere.';
+    }
+  }
+
+  /**
+   * Handles errors during automatic challenge fetching, displaying appropriate messages to the user.
+   * @param {Error} error - The error object from automatic challenge fetching attempt.
+   * @returns {string} Error message.
+   */
+  async handleErrorGetAutomaticChallenge(error) {
+    if (axios.isAxiosError(error)) {
+      switch (error.response.status) {
+        case 400:
+        case 404:
+        case 500:
+          return 'Error: Finner ingen analyse tilknyttet bruker. GÃ¥ innom din profil og reanalyser kontoutskrifter';
+        default:
+          return error.response.data.errorMessage;
+      }
+    } else {
+      console.error('Cannot connect to server.', error);
+      return 'Kan ikke koble til serveren. Vennligst prøv igjen senere.';
+    }
+  }
+}
+
+export default new GoalErrorService();
\ No newline at end of file
diff --git a/src/services/external/StockService.js b/src/services/external/StockService.js
deleted file mode 100644
index 55433daa0a79c2b5d0d185bea6db600dbc30541e..0000000000000000000000000000000000000000
--- a/src/services/external/StockService.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import axios from "axios";
-
-class StockService {
-
-    async fetchStockInfo(searchQuery) {
-      try {
-        // Ensure searchQuery is a string and trim any extra whitespace
-        if (typeof searchQuery !== 'string' || !searchQuery.trim()) {
-          throw new Error('Search query must be a non-empty string');
-        }
-        const symbol = searchQuery.trim().toUpperCase();
-        const response = await axios.get(`https://finnhub.io/api/v1/quote`, {
-          params: {
-            symbol: symbol,
-            token: 'cof4tfhr01qj17o79vqgcof4tfhr01qj17o79vr0'  // Ensure your token is securely managed
-          }
-        });
-        if (response.data) {
-          return {
-            name: symbol,
-            ticker: symbol,
-            price: response.data.c,  // 'c' stands for current price
-            change: response.data.d,  // 'd' stands for daily change in price
-            changePercent: response.data.dp  // 'dp' stands for daily percentage change
-          };
-        }
-        return null;
-      } catch (error) {
-        console.error('Error fetching stock data:', error);
-        return null;
-      }
-    }
-}
-
-export default new StockService();
diff --git a/src/services/internal/AccountService.js b/src/services/internal/AccountService.js
index bc8a38962e2a67e80e5833d6f64d879c3a7603ea..c8f178d83698b14c2673ef648337d3524f50cda7 100644
--- a/src/services/internal/AccountService.js
+++ b/src/services/internal/AccountService.js
@@ -1,23 +1,39 @@
 import axios from "axios";
+import UserService from "./UserService";
 
+/**
+ * Service class for managing user accounts.
+ */
 class AccountService {
   baseURL = "http://localhost:8080/account";
 
+  /**
+   * Constructs an instance of AccountService and sets the authentication header using the user's session token.
+   */
+  constructor() {
+    UserService.setAxiosAuthHeader(sessionStorage.getItem("authToken"));
+  }
+
+  /**
+   * Adds a new account with the provided account number.
+   * @param {string} accountNumber - The account number to be added.
+   * @returns {Promise<Object>} The response data from the server.
+   * @throws {Error} If an error occurs during the request.
+   */
   async addAccount(accountNumber) {
     try {
       let formData = new FormData();
       formData.append("accountNumber", accountNumber);
 
       const response = await axios.post(
-        `${this.baseURL}/addAccount`,
-        formData,
-        {
+          `${this.baseURL}/addAccount`,
+          formData,
+          {
             headers: {
-                'Content-Type': 'application/json'
+              'Content-Type': 'application/json'
             }
-        }
+          }
       );
-      console.log(response.data);
       return response.data;
     } catch (error) {
       console.error("Error adding account");
@@ -25,16 +41,20 @@ class AccountService {
     }
   }
 
+  /**
+   * Retrieves all accounts associated with the user.
+   * @returns {Promise<Array>} An array of account objects.
+   * @throws {Error} If an error occurs during the request.
+   */
   async getAccounts() {
     try {
       const response = await axios.get(`${this.baseURL}/getAccounts`);
-      console.log(response.data);
       return response.data;
     } catch (error) {
+      console.error("Failed to retrieve accounts.", error);
       throw error;
     }
   }
-
-
 }
+
 export default new AccountService();
\ No newline at end of file
diff --git a/src/services/internal/AutomaticChallengeService.js b/src/services/internal/AutomaticChallengeService.js
new file mode 100644
index 0000000000000000000000000000000000000000..4fcaaa53990c7a1189eb5dc6efbbffe0084b9f7f
--- /dev/null
+++ b/src/services/internal/AutomaticChallengeService.js
@@ -0,0 +1,34 @@
+import axios from "axios";
+import UserService from "./UserService";
+
+/**
+ * Service class for managing automatic challenges.
+ */
+class AutomaticChallengeService {
+
+  baseURL = "http://localhost:8080/api/v1/random-challenges";
+
+  /**
+   * Constructs an instance of AutomaticChallengeService and sets the authentication header using the user's session token.
+   */
+  constructor() {
+    UserService.setAxiosAuthHeader(sessionStorage.getItem("authToken"));
+  }
+
+  /**
+   * Retrieves a random challenge from the server.
+   * @returns {Promise} The response data containing the random challenge.
+   * @throws {Error} If an error occurs during the request.
+   */
+  async getChallenge() {
+    try{
+      const response = await axios.get(
+        `${this.baseURL}/`);
+      return response.data;
+    } catch (error) {
+      console.error("Error getting goal.", error);
+      throw error;
+    }
+  }
+}
+export default new AutomaticChallengeService();
\ No newline at end of file
diff --git a/src/services/internal/BadgeService.js b/src/services/internal/BadgeService.js
new file mode 100644
index 0000000000000000000000000000000000000000..3715cec6492c0743809ca23f81a94a3a7ec2e7ab
--- /dev/null
+++ b/src/services/internal/BadgeService.js
@@ -0,0 +1,87 @@
+import axios, { HttpStatusCode } from "axios";
+import UserService from "./UserService";
+
+/**
+ * Service class for managing badges.
+ */
+class BadgeService {
+
+    /**
+     * Constructor for BadgeService.
+     */
+    baseURL = "http://localhost:8080/api/v1/badge-manager";
+
+    constructor() {
+        UserService.setAxiosAuthHeader(sessionStorage.getItem("authToken"));
+    }
+
+    /**
+     * Retrieves all possible badges.
+     *
+     * @returns {Promise} A promise that resolves to an array of possible badges.
+     */
+    async getAllBadges() {
+        try {
+            const response = await axios.get(`${this.baseURL}/possible-badges`);
+            return response.data;
+        } catch (error) {
+            console.error("Error fetching possible achievements.", error);
+            throw error;
+        }
+    }
+
+    /**
+     * Retrieves badges with pagination support.
+     *
+     * @param {number} pageSize - The number of items per page.
+     * @param {number} page - The page number.
+     * @returns {Promise} A promise that resolves to an object containing badges data.
+     */
+    async getBadges(pageSize, page) {
+        try {
+            const response = await axios.get(
+                `${this.baseURL}/badges`, {
+                    params: {
+                        page,
+                        pageSize,
+                    },
+                    headers: {
+                        'Content-Type': 'application/json'
+                    }
+                });
+            return response.data;
+        } catch (error) {
+            console.error("Error fetching badges.", error);
+            throw error;
+        }
+    }
+
+    /**
+     * Creates a new badge.
+     *
+     * @param {string} category - The category of the badge.
+     * @returns {Promise<Object>} A promise that resolves to the created badge.
+     */
+    async createBadge(category) {
+        try {
+            const response = await axios.get(
+                `http://localhost:8080/api/v1/achievement-manager/stats/${category}`, {
+                    category,
+                }, {
+                    headers: {
+                        'Content-Type': 'application/json'
+                    }
+                });
+            if (response.status === HttpStatusCode.Created) {
+                sessionStorage.setItem('showBanner', true);
+            }
+            return response.data;
+        } catch (error) {
+            console.error("Error adding badge.", error);
+            throw error;
+        }
+    }
+
+}
+
+export default new BadgeService();
\ No newline at end of file
diff --git a/src/services/internal/BankStatementService.js b/src/services/internal/BankStatementService.js
new file mode 100644
index 0000000000000000000000000000000000000000..77fa671ebf5144792dd3e2f85f587fa5ad9ccddd
--- /dev/null
+++ b/src/services/internal/BankStatementService.js
@@ -0,0 +1,141 @@
+import axios from "axios";
+import UserService from "./UserService";
+
+/**
+ * Service class for handling bank statement-related API requests.
+ */
+class BankStatementService {
+
+  baseURL = "http://localhost:8080/api/v1/bank-statements";
+
+  /**
+   * Creates an instance of BankStatementService.
+   * Sets the Axios authorization header using the authentication token from sessionStorage.
+   */
+  constructor() {
+    UserService.setAxiosAuthHeader(sessionStorage.getItem("authToken"));
+  }
+
+  /**
+   * Adds a bank statement.
+   *
+   * @param {FormData} formData - The form data containing the bank statement file.
+   * @param {string} bankName - The name of the bank associated with the statement.
+   * @returns {Promise<Object>} The response data.
+   */
+  async addBankStatement(formData, bankName) {
+    const url = `${this.baseURL}/` + "?bankName=" + bankName
+    try {
+      const response = await axios.post(url, formData);
+      return response.data;
+    } catch (error) {
+      console.error("Error adding bank transcript", error);
+      throw error;
+    }
+  }
+
+  /**
+   * Analyzes a bank statement.
+   *
+   * @param {number} statementId - The ID of the bank statement to analyze.
+   * @param {boolean} [force=false] - Whether to force a new analysis.
+   * @param {boolean} [categorize=true] - Whether to categorize the transactions during analysis.
+   * @returns {Promise<Object>} The response data.
+   */
+  async analyzeBankStatement(statementId, force = false, categorize = true) {
+    try {
+      const response = await axios.get(
+          `${this.baseURL}/${statementId}/analysis?forceNewAnalysis=${force}&categorize=${categorize}`);
+      return response.data;
+    } catch (error) {
+      console.error("Error analyzing bank transcript", error);
+      throw error;
+    }
+  }
+
+  /**
+   * Retrieves bank statements.
+   *
+   * @param {number} month - The month for which to retrieve statements.
+   * @param {number} year - The year for which to retrieve statements.
+   * @returns {Promise<Object[]>} The array of bank statement objects.
+   */
+  async retrieveBankStatements(month = 0, year = 0) {
+    try {
+      const response = await axios.get(`${this.baseURL}/?month=${month}&year=${year}`);
+      return response.data;
+    } catch (error) {
+      console.error("Error retrieving bank transcripts", error);
+      throw error;
+    }
+  }
+
+  /**
+   * Retrieves bank statements with specified month and year.
+   *
+   * @param {number} month - The month for which to retrieve statements.
+   * @param {number} year - The year for which to retrieve statements.
+   * @returns {Promise<Object[]>} The array of bank statement objects.
+   */
+  async retrieveBankStatementsWithMY(month, year) {
+    try {
+      const response = await axios.get(`${this.baseURL}/` + "?month=" + month + "&year=" + year);
+      return response.data;
+    } catch (error) {
+      console.error("Error retrieving bank transcripts", error);
+      throw error;
+    }
+  }
+
+  /**
+   * Retrieves analyses for bank statements.
+   *
+   * @param {Object} retrieveAnalysesDTO - The DTO object containing parameters for retrieving analyses.
+   * @returns {Promise<Object[]>} The array of analysis objects.
+   */
+  async retrieveAnalyses(retrieveAnalysesDTO) {
+    try {
+      const response = await axios.get(`${this.baseURL}/analyses`, {params: retrieveAnalysesDTO}, {
+        headers: {'Content-Type': 'application/json'}
+      });
+      return response.data;
+    } catch (error) {
+      console.error("Error retrieving analyses", error);
+      throw error;
+    }
+  }
+
+  /**
+   * Deletes a bank statement.
+   *
+   * @param {number} statementId - The ID of the bank statement to delete.
+   * @returns {Promise<Object>} The response data.
+   */
+  async deleteBankStatement(statementId) {
+    try {
+      const response = await axios.delete(`${this.baseURL}/${statementId}`);
+      return response.data;
+    } catch (error) {
+      console.error("Error deleting bank transcript", error);
+      throw error;
+    }
+  }
+
+  /**
+   * Updates an analysis for a bank statement.
+   *
+   * @param {Object} bankStatementAnalysisDTO - The DTO object containing updated analysis data.
+   * @returns {Promise<Object>} The response data.
+   */
+  async updateAnalysis(bankStatementAnalysisDTO) {
+    try {
+      const response = await axios.put(`${this.baseURL}/analyses`, bankStatementAnalysisDTO);
+      return response.data;
+    } catch (error) {
+      console.error("Error updating analysis", error);
+      throw error;
+    }
+  }
+}
+
+export default new BankStatementService();
\ No newline at end of file
diff --git a/src/services/internal/BudgetService.js b/src/services/internal/BudgetService.js
deleted file mode 100644
index 35f3cdec27c313f06b621a3666f459c526b284da..0000000000000000000000000000000000000000
--- a/src/services/internal/BudgetService.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import axios from "axios";
-
-class BudgetService {
-  baseURL = "http://localhost:8080/budget";
-
-  async getBudget() {
-    try {
-      const response = await axios.get(`${this.baseURL}/getBudget`);
-      console.log(response.data);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  }
-
-}
-export default new BudgetService();
\ No newline at end of file
diff --git a/src/services/internal/ChallengeService.js b/src/services/internal/ChallengeService.js
index dba52fc0b3d5e32236ab136045aa2a9bd47af709..b2feae06fb5a304529603d41b662fd545df3c275 100644
--- a/src/services/internal/ChallengeService.js
+++ b/src/services/internal/ChallengeService.js
@@ -1,123 +1,140 @@
 import axios from "axios";
-import { data } from "flickity";
+import UserService from "./UserService";
 
+/**
+ * Service for managing challenges.
+ */
 class ChallengeService {
-  baseURL = "http://localhost:8080/Challenge";
 
+  baseURL = "http://localhost:8080/api/v1/challenge-management";
 
-  async createChallenge(title, totalSum, amountSaved, pigLife, currentTile, DateFrom, DateTo) {
-    try {
-      let formData = new FormData();
-      formData.append("title", title);
-      formData.append("totalSum", totalSum);
-      formData.append("amountSaved", amountSaved);
-      formData.append("pigLife", pigLife);
-      formData.append("currentTile", currentTile);
-      formData.append("dateFrom", DateFrom);
-      formData.append("dateTo", DateTo);
+  /**
+   * Creates an instance of ChallengeService.
+   */
+  constructor() {
+    UserService.setAxiosAuthHeader(sessionStorage.getItem("authToken"));
+  }
 
+  /**
+   * Adds a new challenge.
+   *
+   * @param {string} challengeType - The type of challenge.
+   * @param {string} title - The title of the challenge.
+   * @param {string} description - The description of the challenge.
+   * @param {Date} startDate - The start date of the challenge.
+   * @param {Date} endDate - The end date of the challenge.
+   * @param {string} difficulty - The difficulty of the challenge.
+   * @param {number} progress - The progress of the challenge.
+   * @returns {Promise<Object>} A promise that resolves with the added challenge data.
+   */
+  async addChallenge(challengeType, title, description, startDate, endDate, difficulty, progress) {
+    try {
       const response = await axios.post(
-        `${this.baseURL}/createChallenge`,
-        formData,
-        {
+          `${this.baseURL}/challenge`, {
+            challengeType,
+            title,
+            description,
+            startDate,
+            endDate,
+            difficulty,
+            progress,
+          }, {
             headers: {
-                'Content-Type': 'application/json'
+              'Content-Type': 'application/json'
             }
-        }
-      );
-      console.log(response.data);
+          });
       return response.data;
     } catch (error) {
-      console.error("Error adding account");
+      console.error("Error adding account.", error);
       throw error;
     }
   }
 
-  async updatePigLife(challengeID) {
+  /**
+   * Retrieves challenges.
+   *
+   * @param {number} pageSize - The number of challenges per page.
+   * @param {number} page - The page number.
+   * @returns {Promise<Object>} A promise that resolves with the retrieved challenges data.
+   */
+  async getChallenges(pageSize, page) {
     try {
-      let formData = new FormData();
-      formData.append("challengeID", challengeID);
-
-      const response = await axios.put(
-        `${this.baseURL}/updatePigLige`,
-        formData,
-        {
-            headers: {
-                'Content-Type': 'application/json'
-            }
-        }
-      );
-      console.log(response.data);
+      const response = await axios.get(`${this.baseURL}/challenges?page=${page}&pageSize=${pageSize}`);
       return response.data;
     } catch (error) {
-      console.error("Error adding account");
+      console.error("Failed to retrieve challenges.", error);
       throw error;
     }
   }
-  
-  async updateCurrentTile(challengeID) {
-    try {
-      let formData = new FormData();
-      formData.append("challengeID", challengeID);
 
-      const response = await axios.put(
-        `${this.baseURL}/updateCurrentTile`,
-        formData,
-        {
-            headers: {
-                'Content-Type': 'application/json'
-            }
-        }
-      );
-      console.log(response.data);
+  /**
+   * Retrieves a specific challenge.
+   *
+   * @param {string} challengeID - The ID of the challenge to retrieve.
+   * @returns {Promise<Object>} A promise that resolves with the retrieved challenge data.
+   */
+  async getChallenge(challengeID) {
+    try {
+      const response = await axios.get(`${this.baseURL}/challenge/${challengeID}`);
       return response.data;
     } catch (error) {
-      console.error("Error adding account");
+      console.error("Failed to retrieve challenge.", error);
       throw error;
     }
   }
 
-  async updateSavedAmount(challengeID, addedValue) {
+  /**
+   * Updates the progress of a challenge.
+   *
+   * @param {string} challengeId - The ID of the challenge to update.
+   * @param {number} progress - The new progress value.
+   * @returns {Promise<Object>} A promise that resolves with the updated challenge data.
+   */
+  async updateProgress(challengeId, progress) {
     try {
-      let formData = new FormData();
-      formData.append("challengeID", challengeID);
-      formData.append("addedValue", addedValue);
-
       const response = await axios.put(
-        `${this.baseURL}/updateSavedAmount`,
-        formData,
-        {
+          `${this.baseURL}/challenge/${challengeId}`, {
+            progress: progress,
+          }, {
             headers: {
-                'Content-Type': 'application/json'
+              'Content-Type': 'application/json'
             }
-        }
-      );
-      console.log(response.data);
+          });
       return response.data;
     } catch (error) {
-      console.error("Error adding account");
+      console.error("Failed to update progress.", error)
       throw error;
     }
   }
 
-  async getChallenges() {
+  /**
+   * Joins a shared challenge using a join code.
+   *
+   * @param {string} joinCode - The join code of the shared challenge.
+   * @returns {Promise<Object>} A promise that resolves with the joined challenge data.
+   */
+  async joinChallenge(joinCode) {
     try {
-      const response = await axios.get(`${this.baseURL}/getChallenges`);
-      console.log(response.data);
+      const response = await axios.get(`${this.baseURL}/shared-challenge/${joinCode}`);
       return response.data;
     } catch (error) {
+      console.error("Failed to join challenge", error);
       throw error;
     }
   }
 
-  async getChallenge(challengeID) {
+  /**
+   * Retrieves a friend's challenge.
+   *
+   * @param {string} sharedChallengeId - The ID of the shared challenge.
+   * @returns {Promise<Object>} A promise that resolves with the retrieved challenge data.
+   */
+  async getFriendChallenge(sharedChallengeId) {
     try {
-      const response = await axios.get(`${this.baseURL}/getChallenge`, {
-        params: { challengeID },
-      });
-      console.log(response.data);
+      const response = await axios.get(`${this.baseURL}/shared-challenge/users/${sharedChallengeId}?sharedChallengeId=${sharedChallengeId}`);
       return response.data;
     } catch (error) {
+      console.error("Failed to retrieve challenge.", error);
       throw error;
     }
   }
diff --git a/src/services/internal/CookieService.js b/src/services/internal/CookieService.js
new file mode 100644
index 0000000000000000000000000000000000000000..43cbd98eb20e6e1a2787feadc9e257c6960ff87e
--- /dev/null
+++ b/src/services/internal/CookieService.js
@@ -0,0 +1,81 @@
+/**
+ * Service class for managing cookies.
+ */
+class CookieService {
+
+  /**
+   * Retrieves the value of a cookie by its name.
+   * @param {string} name - The name of the cookie.
+   * @returns {string|undefined} The value of the cookie, or undefined if the cookie does not exist.
+   */
+  getCookie(name) {
+    const value = `; ${document.cookie}`;
+    const parts = value.split(`; ${name}=`);
+    if (parts.length === 2) return parts.pop().split(';').shift();
+  }
+
+  /**
+   * Sets a new cookie with the specified name, value, and expiration days.
+   * @param {string} name - The name of the cookie.
+   * @param {string} value - The value of the cookie.
+   * @param {number} days - The number of days until the cookie expires.
+   */
+  setCookie(name, value, days) {
+    let expires = "";
+    if (days) {
+      const date = new Date();
+      date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
+      expires = "; expires=" + date.toUTCString();
+    }
+    document.cookie = name + "=" + (value || "") + expires + "; path=/";
+  }
+
+  /**
+   * Retrieves a string containing all the cookies.
+   * @returns {string} A string containing all the cookies.
+   */
+  listCookies() {
+    let theCookies = document.cookie.split(';');
+    let aString = '';
+    for (let i = 1; i <= theCookies.length; i++) {
+      aString += i + ' ' + theCookies[i - 1] + "\n";
+    }
+    return aString;
+  }
+
+  /**
+   * Checks if the cookie 'cookiesAccepted' has been set to 'true'.
+   * @returns {boolean} True if the cookie 'cookiesAccepted' is set to 'true', otherwise false.
+   */
+  isCookieAccepted() {
+    const cookieAccepted = this.getCookie('cookiesAccepted');
+    return cookieAccepted === 'true';
+  }
+
+  /**
+   * Retrieves the value of a cookie with consent.
+   * @param {string} name - The name of the cookie.
+   * @returns {string|undefined} Cookie value, or undefined if the cookie does not exist or consent has not been given.
+   */
+  getCookieWithConsent(name) {
+
+    if (this.isCookieAccepted()) {
+      console.log('cookie accepted')
+      return this.getCookie(name);
+    }
+  }
+
+  /**
+   * Sets a new cookie with consent.
+   * @param {string} name - The name of the cookie.
+   * @param {string} value - The value of the cookie.
+   * @param {number} days - The number of days until the cookie expires.
+   */
+  setCookieWithConsent(name, value, days) {
+    if (this.isCookieAccepted()) {
+      this.setCookie(name, value, days);
+    }
+  }
+}
+
+export default new CookieService();
\ No newline at end of file
diff --git a/src/services/internal/EmailService.js b/src/services/internal/EmailService.js
index 4065aa8aa7986916869c0ec5ec8dc9913546ae13..fa5d7604bf9d55e2ff3f7afda75d6d3ec6baa44b 100644
--- a/src/services/internal/EmailService.js
+++ b/src/services/internal/EmailService.js
@@ -1,43 +1,92 @@
 import axios from "axios";
 
+/**
+ * Service for handling email-related operations.
+ */
 class EmailService {
-  baseURL = "http://localhost:8080/email";
 
-  async sendFeedback(fromEmail, message) {
+  baseURL = "http://localhost:8080/api/v1/email";
+
+  /**
+   * Sends a contact message.
+   *
+   * @param {Object} emailDetailsDto - Details of the email to be sent.
+   * @throws {Error} If an error occurs while sending the contact message.
+   */
+  async sendContactMessage(emailDetailsDto) {
+    const url = `${this.baseURL}/contact`;
+
     try {
-      const response = await axios.post(
-        `${this.baseURL}/sendFeedback?fromEmail=${fromEmail}&message=${message}`
-      );
-      console.log("Feedback sent successfully:", response.data);
-      return true;
+      await axios.post(url, emailDetailsDto);
     } catch (error) {
-      console.error("Failed to send feedback:", error);
+      console.error("Failed to send contact message.", error);
       throw error;
     }
   }
 
-  async sendPasswordResetToken(toEmail) {
+  /**
+   * Sends a registration token to the specified email address.
+   *
+   * @param {string} email - The email address to send the registration token to.
+   * @returns {Promise<Object>} A promise that resolves to the response data containing the expiration timestamp .
+   * @throws {Error} If an error occurs while sending the registration token.
+   */
+  async sendRegisterToken(email) {
+    const url = `${this.baseURL}/verification/code?email=${email}`
+
     try {
-      const response = await axios.post(
-        `${this.baseURL}/sendPasswordResetToken?toEmail=${toEmail}`
-      );
-      console.log("Password reset token sent successfully");
+      const response = await axios.get(url);
       return response.data;
     } catch (error) {
-      console.error("Failed to send password reset token:", error);
+      console.error("Failed to send register token.", error);
       throw error;
     }
   }
 
-  async sendRegisterToken(toEmail) {
+  /**
+   * Verifies if the specified email address is available.
+   *
+   * @param {string} email - The email address to verify availability for.
+   * @throws {Error} If an error occurs while verifying email availability.
+   */
+  async verifyEmailAvailability(email) {
+    const url = `${this.baseURL}/available?email=${email}`
     try {
-      const response = await axios.post(
-        `${this.baseURL}/sendRegisterToken?toEmail=${toEmail}`
-      );
-      console.log("Register token sent successfully");
-      return response.data;
+      await axios.get(url);
+    } catch (error) {
+      console.error("Failed to verify if email is available.", error);
+      throw error;
+    }
+  }
+
+  /**
+   * Verifies if the specified email address exists.
+   *
+   * @param {string} email - The email address to verify existence for.
+   * @throws {Error} If an error occurs while verifying email existence.
+   */
+  async verifyEmailExistence(email) {
+    const url = `${this.baseURL}/exist?email=${email}`
+    try {
+      await axios.get(url);
+    } catch (error) {
+      console.error("Failed to verify if email exist.", error);
+      throw error;
+    }
+  }
+
+  /**
+   * Sends a challenge code to the specified email address.
+   *
+   * @param {string} email - The email address to send the challenge code to.
+   * @param {string} id - The ID associated with the challenge.
+   * @throws {Error} If an error occurs while sending the challenge code.
+   */
+  async sendChallengeCode(email, id) {
+    try {
+      await axios.get(`${this.baseURL}/challenge/join/${id}?email=${email}&id=${id}`);
     } catch (error) {
-      console.error("Failed to send register token:", error);
+      console.error("Failed to send code:", error);
       throw error;
     }
   }
diff --git a/src/services/internal/GoalService.js b/src/services/internal/GoalService.js
new file mode 100644
index 0000000000000000000000000000000000000000..ec3f19004f705958619f79d5a5c747a9684a6bf5
--- /dev/null
+++ b/src/services/internal/GoalService.js
@@ -0,0 +1,248 @@
+import axios from "axios";
+import UserService from "./UserService";
+
+/**
+ * Service for managing goals.
+ */
+class GoalService {
+
+  baseURL = "http://localhost:8080/api/v1/goal-manager";
+
+  /**
+   * Creates an instance of GoalService.
+   */
+  constructor() {
+    UserService.setAxiosAuthHeader(sessionStorage.getItem("authToken"));
+  }
+
+  /**
+   * Adds a new goal.
+   *
+   * @param {string} goalName - The name of the goal.
+   * @param {number} totalAmount - The total amount for the goal.
+   * @param {number} lives - The number of lives for the goal.
+   * @param {Date} startDate - The start date of the goal.
+   * @param {Date} endDate - The end date of the goal.
+   * @returns {Promise<Object>} A promise that resolves with the added goal data.
+   * @throws {Error} If an error occurs while adding the goal.
+   */
+  async addGoal(goalName, totalAmount, lives, startDate, endDate) {
+    try {
+      const response = await axios.post(
+          `${this.baseURL}/goals`, {
+            goalName: goalName,
+            totalAmount: totalAmount,
+            lives: lives,
+            startDate: startDate,
+            endDate: endDate,
+          }, {
+            headers: {
+              'Content-Type': 'application/json'
+            }
+          }
+      );
+      return response.data;
+    } catch (error) {
+      console.error("Error adding account.", error);
+      throw error;
+    }
+  }
+
+  /**
+   * Retrieves goals.
+   *
+   * @param {number} pageSize - The number of goals per page.
+   * @param {number} page - The page number.
+   * @returns {Promise} A promise that resolves with the retrieved goals data.
+   * @throws {Error} If an error occurs while retrieving goals.
+   */
+  async getGoals(pageSize, page) {
+    try {
+      const response = await axios.get(`${this.baseURL}/goals?page=${page}&pageSize=${pageSize}`);
+      return response.data;
+    } catch (error) {
+      console.error("Failed to retrieve goals.", error);
+      throw error;
+    }
+  }
+
+  /**
+   * Retrieves a specific goal.
+   *
+   * @param {string} id - The ID of the goal to retrieve.
+   * @returns {Promise} A promise that resolves with the retrieved goal data.
+   * @throws {Error} If an error occurs while retrieving the goal.
+   */
+  async getGoal(id) {
+    try {
+      const response = await axios.get(`${this.baseURL}/goal/${id}`, {
+            id: id
+          }, {
+            headers: {
+              'Content-Type': 'application/json'
+            }
+          }
+      );
+      return response.data;
+    } catch (error) {
+      console.error("Error retrieving goal.", error);
+      throw error;
+    }
+  }
+
+  /**
+   * Retrieves the amount saved for a goal.
+   *
+   * @param {string} id - The ID of the goal.
+   * @param {string} title - The title of the goal.
+   * @param {string} state - The state of the goal.
+   * @returns {Promise} A promise that resolves with the retrieved amount saved data.
+   * @throws {Error} If an error occurs while retrieving the amount saved.
+   */
+  async getAmountSaved(id, title, state) {
+    try {
+      const response = await axios.post(
+          `${this.baseURL}/goal/save`, {
+            id: id,
+            title: title,
+            state: state,
+          }, {
+            headers: {
+              'Content-Type': 'application/json'
+            }
+          }
+      );
+      return response.data;
+    } catch (error) {
+      console.error("Error retrieving amount saved for the goal.", error);
+      throw error;
+    }
+  }
+
+  /**
+   * Retrieves contributors for a goal.
+   *
+   * @param {string} id - The ID of the goal.
+   * @returns {Promise} A promise that resolves with the retrieved goal contributors data.
+   * @throws {Error} If an error occurs while retrieving goal contributors.
+   */
+  async getGoalContributors(id) {
+    try {
+      const response = await axios.get(
+          `${this.baseURL}/goal/contributors/${id}`, {
+            id: id
+          }, {
+            headers: {
+              'Content-Type': 'application/json'
+            }
+          }
+      );
+      return response.data;
+    } catch (error) {
+      console.error("Error getting goal");
+      throw error;
+    }
+  }
+
+  /**
+   * Adds a contribution to a goal.
+   *
+   * @param {string} goalId - The ID of the goal.
+   * @param {number} contribution - The contribution amount.
+   * @returns {Promise} A promise that resolves with the added contribution data.
+   * @throws {Error} If an error occurs while adding the contribution.
+   */
+  async addContribution(goalId, contribution) {
+    try {
+      const response = await axios.put(
+          `${this.baseURL}/goal/save`, {
+            goalId: goalId,
+            contribution: contribution
+          }, {
+            headers: {
+              'Content-Type': 'application/json'
+            }
+          }
+      );
+      return response.data;
+    } catch (error) {
+      console.error("Error adding contribution to goal.", error);
+      throw error;
+    }
+  }
+
+  /**
+   * Updates the progress of a goal.
+   *
+   * @param {string} id - The ID of the goal to update.
+   * @param {string} goalState - The new state of the goal.
+   * @returns {Promise<Object>} A promise that resolves with the updated goal data.
+   */
+  async updateProgress(id, goalState) {
+    try {
+      const response = await axios.put(
+          `${this.baseURL}/goal/state`, {
+            id: id,
+            goalState: goalState,
+          }, {
+            headers: {
+              'Content-Type': 'application/json'
+            }
+          }
+      );
+      return response.data;
+    } catch (error) {
+      console.error("Failed to update progress for goal.", error);
+      throw error;
+    }
+  }
+
+  /**
+   * Joins a goal using a join code.
+   *
+   * @param {string} joinCode - The join code of the goal.
+   * @returns {Promise<Object>} A promise that resolves with the joined goal data.
+   * @throws {Error} If an error occurs while joining the goal.
+   */
+  async joinGoal(joinCode) {
+    try {
+      const response = await axios.put(
+          `${this.baseURL}/goal`, {
+            joinCode: joinCode,
+          }, {
+            headers: {
+              'Content-Type': 'application/json'
+            }
+          }
+      );
+      return response.data;
+    } catch (error) {
+      console.error("Error joining goal.", error);
+      throw error;
+    }
+  }
+
+  /**
+   * Retrieves the total saved amount.
+   *
+   * @returns {Promise<Object>} A promise that resolves with the total saved amount data.
+   * @throws {Error} If an error occurs while retrieving the total saved amount.
+   */
+  async getTotalSaved() {
+    try {
+      const response = await axios.get(
+          `http://localhost:8080/api/v1/achievement-manager/total`, {}, {
+            headers: {
+              'Content-Type': 'application/json'
+            }
+          }
+      );
+      return response.data;
+    } catch (error) {
+      console.error("Error retrieving total saved.", error);
+      throw error;
+    }
+  }
+}
+
+export default new GoalService();
\ No newline at end of file
diff --git a/src/services/internal/GoalsService.js b/src/services/internal/GoalsService.js
deleted file mode 100644
index 3a599230be77b05d2ca70368eacbbb6a347601de..0000000000000000000000000000000000000000
--- a/src/services/internal/GoalsService.js
+++ /dev/null
@@ -1,124 +0,0 @@
-import axios from "axios";
-
-class GoalsService {
-  baseURL = "http://localhost:8080/goal";
-
-  async createGoal(title, totalSum, amountSaved, pigLife, currentTile, DateFrom, DateTo) {
-    try {
-      let formData = new FormData();
-      formData.append("title", title);
-      formData.append("totalSum", totalSum);
-      formData.append("amountSaved", amountSaved);
-      formData.append("pigLife", pigLife);
-      formData.append("currentTile", currentTile);
-      formData.append("dateFrom", DateFrom);
-      formData.append("dateTo", DateTo);
-
-      const response = await axios.post(
-        `${this.baseURL}/createGoal`,
-        formData,
-        {
-          headers: {
-            "Content-Type": "multipart/form-data",
-          },
-        }
-      );
-      console.log(response.data);
-      return response.data;
-    } catch (error) {
-      console.error("Error adding account");
-      throw error;
-    }
-  }
-
-  async updatePigLife(goalID) {
-    try {
-      let formData = new FormData();
-      formData.append("goalID", goalID);
-
-      const response = await axios.put(
-        `${this.baseURL}/updatePigLige`,
-        formData,
-        {
-          headers: {
-            "Content-Type": "multipart/form-data",
-          },
-        }
-      );
-      console.log(response.data);
-      return response.data;
-    } catch (error) {
-      console.error("Error adding account");
-      throw error;
-    }
-  }
-  
-  async updateCurrentTile(goalID) {
-    try {
-      let formData = new FormData();
-      formData.append("goalID", goalID);
-
-      const response = await axios.put(
-        `${this.baseURL}/updateCurrentTile`,
-        formData,
-        {
-          headers: {
-            "Content-Type": "multipart/form-data",
-          },
-        }
-      );
-      console.log(response.data);
-      return response.data;
-    } catch (error) {
-      console.error("Error adding account");
-      throw error;
-    }
-  }
-
-  async updateSavedAmount(goalID, addedValue) {
-    try {
-      let formData = new FormData();
-      formData.append("goalID", goalID);
-      formData.append("addedValue", addedValue);
-
-      const response = await axios.put(
-        `${this.baseURL}/updateSavedAmount`,
-        formData,
-        {
-            headers: {
-                'Content-Type': 'application/json'
-            }
-        }
-      );
-      console.log(response.data);
-      return response.data;
-    } catch (error) {
-      console.error("Error adding account");
-      throw error;
-    }
-  }
-
-  async getGoals() {
-    try {
-      const response = await axios.get(`${this.baseURL}/getGoals`);
-      console.log(response.data);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  }
-
-  async getGoal(goalID) {
-    try {
-      const response = await axios.get(`${this.baseURL}/getGoal`, {
-        params: { goalID },
-      });
-      console.log(response.data);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  }
-}
-
-export default new GoalsService();
\ No newline at end of file
diff --git a/src/services/internal/NewsService.js b/src/services/internal/NewsService.js
index 2c0b6aa27d79e34807743580623adc653bc0d2f3..10621989755583d1d019d944c151e2b90a3a3f7a 100644
--- a/src/services/internal/NewsService.js
+++ b/src/services/internal/NewsService.js
@@ -1,8 +1,29 @@
 import axios from "axios";
 
+/**
+ * Service for fetching news data.
+ */
 class NewsService {
-  baseURL = "http://localhost:8080/news";
 
+    baseURL = "http://localhost:8080/api/v1/news";
 
+    /**
+     * Fetches news data from the backend.
+     *
+     * @param {number} page - The page number of news to fetch.
+     * @param {number} pageSize - The number of news items to fetch per page.
+     * @returns {Promise<object>} A Promise that resolves to the news data.
+     * @throws {Error} If there is an error fetching news data.
+     */
+    async getNews(page, pageSize) {
+        try {
+            const response = await axios.get(`${this.baseURL}/?page=${page}&pageSize=${pageSize}`);
+            return response.data;
+        } catch (error) {
+            console.error("Error fetching news.", error);
+            throw error;
+        }
+    }
 }
+
 export default new NewsService();
\ No newline at end of file
diff --git a/src/services/internal/StockService.js b/src/services/internal/StockService.js
new file mode 100644
index 0000000000000000000000000000000000000000..75eeb763e253c59daec6a5f6bb857eaf433dfbf9
--- /dev/null
+++ b/src/services/internal/StockService.js
@@ -0,0 +1,29 @@
+import axios from "axios";
+import CookieService from "@/services/internal/CookieService";
+
+/**
+ * Service for fetching stock information from an external API.
+ */
+class StockService {
+
+  /**
+   * Fetches stock information based on the provided search query.
+   *
+   * @param {string} searchQuery - The search query for the stock symbol.
+   * @returns {Promise<object>} A promise that resolves to the stock information object.
+   * @throws {Error} If an error occurs during retrieval of stock data.
+   */
+  async fetchStockInfo(searchQuery) {
+    CookieService.setCookieWithConsent('stockSearch', searchQuery, 1);
+    const symbol = searchQuery.trim().toUpperCase();
+    try {
+      const response = await axios.get(`http://localhost:8080/api/v1/stock/${symbol}`);
+      return response.data;
+    } catch (error) {
+      console.error("Failed to retrieve stock data.", error);
+      throw error;
+    }
+  }
+}
+
+export default new StockService();
diff --git a/src/services/internal/StreakService.js b/src/services/internal/StreakService.js
new file mode 100644
index 0000000000000000000000000000000000000000..c505afffe7fb78132d970a8fdb85d20599cece9a
--- /dev/null
+++ b/src/services/internal/StreakService.js
@@ -0,0 +1,57 @@
+import axios from "axios";
+import UserService from "./UserService";
+
+/**
+ * Service class for managing user streaks.
+ */
+class StreakService {
+
+  baseURL = "http://localhost:8080/api/v1/streak";
+
+  /**
+   * Constructs an instance of StreakService and sets the authentication header using the user's session token.
+   */
+  constructor() {
+    UserService.setAxiosAuthHeader(sessionStorage.getItem("authToken"));
+  }
+
+  /**
+   * Retrieves the user's streak from the server.
+   * @returns {Promise} The response data containing the user's streak.
+   * @throws {Error} If an error occurs during the request.
+   */
+  async getStreak() {
+    try {
+      const response = await axios.get(`${this.baseURL}`);
+      return response.data;
+    } catch (error) {
+      console.error("Error fetching streak");
+      throw error;
+    }
+  }
+
+  /**
+   * Changes the user's streak by the specified increment.
+   * @param {number} increment - The amount by which to increment or decrement the streak.
+   * @returns {Promise} The response data containing the updated streak.
+   * @throws {Error} If an error occurs during the request.
+   */
+  async changeStreak(increment) {
+    try {
+      const response = await axios.put(`${this.baseURL}`, {
+        increment,
+      }, {
+        headers: {
+          'Content-Type': 'application/json'
+        }
+      });
+      return response.data;
+    } catch (error) {
+      console.error("Error fetching news");
+      throw error;
+    }
+  }
+
+
+}
+export default new StreakService();
\ No newline at end of file
diff --git a/src/services/internal/TransactionService.js b/src/services/internal/TransactionService.js
index f5a3b99984a37a2955183507d9107b8470ee4479..0220a08dc45899d20c061ac50d4cc4919a0d8a35 100644
--- a/src/services/internal/TransactionService.js
+++ b/src/services/internal/TransactionService.js
@@ -1,23 +1,41 @@
 import axios from "axios";
+import UserService from "./UserService";
+import TransactionDTO from "@/models/transaction/TransactionDTO";
 
+/**
+ * Service class for managing user transactions.
+ */
 class TransactionService {
-  baseURL = "http://localhost:8080/transaction";
 
+  baseURL = "http://localhost:8080/api/v1/bank-statements";
+
+  /**
+   * Constructs an instance of TransactionService and sets the authentication header.
+   */
+  constructor() {
+    UserService.setAxiosAuthHeader(sessionStorage.getItem("authToken"));
+  }
+
+  /**
+   * Adds transactions from a PDF file.
+   * @param {File} pdfFile - The PDF file containing the transactions.
+   * @returns {Promise} The response data from the server.
+   * @throws {Error} If an error occurs during the request.
+   */
   async addTransactions(pdfFile) {
     try {
       let formData = new FormData();
       formData.append("pdfFile", pdfFile);
 
       const response = await axios.post(
-        `${this.baseURL}/addTransactions`,
-        formData,
-        {
+          `${this.baseURL}/addTransactions`,
+          formData,
+          {
             headers: {
-                'Content-Type': 'application/json'
+              'Content-Type': 'application/json'
             }
-        }
+          }
       );
-      console.log(response.data);
       return response.data;
     } catch (error) {
       console.error("Error adding account");
@@ -25,16 +43,48 @@ class TransactionService {
     }
   }
 
-  async getTransactions(accountNumber) {
+  /**
+   * Retrieves the account numbers associated with the user's transactions.
+   * @returns {Promise<Array>} An array of account numbers.
+   * @throws {Error} If an error occurs during the request.
+   */
+  async getAccountNumbers() {
+    try {
+      const response = await axios.get(this.baseURL + '/account-numbers')
+      return response.data;
+    } catch (error) {
+      console.log(error)
+    }
+  }
+
+  /**
+   * Updates a transaction.
+   * @param {TransactionDTO} transactionDTO - The transaction data transfer object.
+   * @returns {Promise<Object>} The response data from the server.
+   * @throws {Error} If an error occurs during the request.
+   */
+  async updateTransaction(transactionDTO) {
     try {
-      const response = await axios.get(`${this.baseURL}/getTransactions`, {
-        params: { accountNumber },
+      const dto = new TransactionDTO(
+          transactionDTO.id,
+          transactionDTO.date,
+          transactionDTO.description,
+          transactionDTO.amount,
+          transactionDTO.isIncoming,
+          transactionDTO.category
+      );
+
+      const response = await axios.put(`http://localhost:8080/api/v1/transactions/`, dto, {
+        headers: {
+          'Content-Type': 'application/json'
+        }
       });
-      console.log(response.data);
       return response.data;
     } catch (error) {
+      console.error(error);
       throw error;
     }
   }
 }
+
 export default new TransactionService();
\ No newline at end of file
diff --git a/src/services/internal/UserDetailsInputService.js b/src/services/internal/UserDetailsInputService.js
new file mode 100644
index 0000000000000000000000000000000000000000..f2e7e397be2ad1e2459a414f76df3e9b82013e5e
--- /dev/null
+++ b/src/services/internal/UserDetailsInputService.js
@@ -0,0 +1,163 @@
+/**
+ * This service is responsible for validating user input for user details.
+ */
+class UserDetailsValidationService {
+
+  MAX_PASSWORD_LENGTH = 64;
+  MIN_PASSWORD_LENGTH = 8;
+  MAX_NAME_LENGTH = 64;
+  MIN_NAME_LENGTH = 2;
+  MAX_EMAIL_LENGTH = 64;
+
+  constructor() {
+  }
+
+  /**
+   * Validates a request to edit a password
+   *
+   * @param {string} newPassword - The new password
+   * @param {string} confirmPassword - The confirmed new password
+   * @param {string} oldPassword - The old password
+   * @throws {Error} Throws error if validation fails.
+   */
+  validateEditPassword(newPassword, confirmPassword, oldPassword) {
+    if (!newPassword || !confirmPassword || !oldPassword) {
+      throw new Error('Vennligst fyll ut alle passordfeltene.');
+    }
+    if (newPassword.length < 8) {
+      throw new Error('Det nye passordet må være minst 8 tegn langt.');
+    }
+    if (newPassword !== confirmPassword) {
+      throw new Error('De nye passordene stemmer ikke overens.');
+    }
+    if (!oldPassword) {
+      throw new Error('Vennligst skriv inn ditt gamle passord.');
+    }
+
+    this.validatePasswordPattern(newPassword)
+  }
+
+  /**
+   * Validates an input email address
+   *
+   * @param {string} email - The email address to validate
+   * @throws {Error} Throws error if validation fails.
+   */
+  validateEmailPattern(email) {
+    if (!email) {
+      throw new Error('E-postadresse er påkrevd.');
+    }
+
+    if (email.length > this.MAX_EMAIL_LENGTH) {
+      throw new Error(`E-postadressen må være på maksimalt ${this.MAX_EMAIL_LENGTH} tegn.`);
+    }
+
+    if (!email.includes('@')) {
+      throw new Error('E-postadressen må inneholde @.');
+    }
+
+    if (email.startsWith('@')) {
+      throw new Error('E-postadressen kan ikke starte med @.');
+    }
+
+    if (!email.includes('.')) {
+      throw new Error('E-postadressen må inneholde en domene.');
+    }
+
+    let parts = email.split('@');
+    if (parts[1].indexOf('.') === 0) {
+      throw new Error('Domene kan ikke starte med en dot.');
+    }
+
+    let domainParts = parts[1].split('.');
+    if (domainParts.some(part => part.length === 0)) {
+      throw new Error('Domene kan ikke ha tomme segmenter (f.eks. ".." eller ".com").');
+    }
+
+    // Regex pattern for checking email has the shape: [string]@[string].[string]
+    const emailPattern = /^[^@]+@[^@]+\.[^@]+$/;
+    if (!emailPattern.test(email)) {
+      throw new Error('Vennligst skriv inn en gyldig e-postadresse.');
+    }
+  }
+
+  /**
+   * Validates an input name
+   *
+   * @param {string} name - The name to validate
+   * @throws {Error} Throws error if validation fails.
+   */
+  validateNamePattern(name) {
+    if (!name) {
+      throw new Error('Navn er påkrevd.');
+    }
+
+    if (name.length < this.MIN_NAME_LENGTH) {
+      throw new Error(`Navnet må være på minst ${this.MIN_NAME_LENGTH} tegn.`);
+    }
+
+    if (name.length > this.MAX_NAME_LENGTH) {
+      throw new Error(`Navnet må være på maksimalt ${this.MAX_NAME_LENGTH} tegn.`);
+    }
+
+    if (!name.match(/^[a-zA-Z]+$/)) {
+      throw new Error('Navnet kan bare inneholde bokstaver.');
+    }
+
+  }
+
+  /**
+   * Validates an input password
+   *
+   * @param {string} password - The password to validate
+   * @throws {Error} Throws error if validation fails.
+   */
+  validatePasswordPattern(password) {
+
+    const errors = [];
+
+    if (!password) {
+      errors.push('Passord er påkrevd.');
+    }
+
+    if (password.length < this.MIN_PASSWORD_LENGTH) {
+      errors.push(`være minst ${this.MIN_PASSWORD_LENGTH} tegn`);
+    }
+
+    if (password.length > this.MAX_PASSWORD_LENGTH) {
+      errors.push(`være maksimalt ${this.MAX_PASSWORD_LENGTH} tegn.`);
+    }
+
+    const hasUpperCase = /[A-Z]/.test(password);
+    const hasLowerCase = /[a-z]/.test(password);
+    const hasNumbers = /\d/.test(password);
+    const hasSpecialChar = /[\W_]/.test(password);
+
+    if (!hasUpperCase) errors.push('inkludere stor bokstav');
+    if (!hasLowerCase) errors.push('inkludere liten bokstav');
+    if (!hasNumbers) errors.push('inkludere et tall');
+    if (!hasSpecialChar) errors.push('inkludere spesialtegn');
+
+    if (errors.length > 0) {
+      throw new Error(`Passordet må ${errors.join(', ')}.`);
+    }
+
+  }
+
+  /**
+   * Validates an input feedback
+   *
+   * @param {string} value - The feedback value to validate
+   * @throws {Error} Throws error if validation fails.
+   */
+  validateFeedbackPattern(value) {
+    if (!value) {
+      throw new Error('Tilbakemelding er påkrevd.');
+    }
+    if (value.isEmpty) {
+      throw new Error('Tilbakemelding kan ikke bare inneholde mellomrom.');
+    }
+  }
+}
+
+export default new UserDetailsValidationService();
\ No newline at end of file
diff --git a/src/services/internal/UserDetailsService.js b/src/services/internal/UserDetailsService.js
index f166017f4231ba7392cf3fca96e5b1b6eabf16e5..e1d6096d0574bffa87722052178fdab34748bd39 100644
--- a/src/services/internal/UserDetailsService.js
+++ b/src/services/internal/UserDetailsService.js
@@ -1,27 +1,195 @@
 import axios from "axios";
+import UserService from "./UserService";
 
 class UserDetailsService {
-  baseURL = "http://localhost:8080/userDetails";
 
-  async addUserDetails(income, householdType) {
+  baseURL = "http://localhost:8080/api/v1/users";
+
+  /**
+   * Constructs a new instance of UserDetailsService.
+   */
+  constructor() {
+    UserService.setAxiosAuthHeader(sessionStorage.getItem("authToken"));
+  }
+
+  /**
+   * Retrieves details of the currently logged-in user.
+   *
+   * @returns {Promise<Object>} A Promise that resolves to the user details.
+   * @throws {Error} If an error occurs during retrieval.
+   */
+  async retrieveUserDetails() {
+    try {
+      const response = await axios.get(`${this.baseURL}/details`);
+      return response.data;
+    } catch (error) {
+      console.error("Error retrieving user details.", error);
+      throw error;
+    }
+  }
+
+  /**
+   * Changes the first name of the currently logged-in user.
+   *
+   * @param {object} changeFirstNameDTO Dto containing the new first name.
+   * @returns {Promise<Object>} A Promise that resolves to the updated user details.
+   * @throws {Error} If an error occurs during the operation.
+   */
+  async changeFirstName(changeFirstNameDTO) {
+    try {
+      const response = await axios.put(`${this.baseURL}/first-name`, changeFirstNameDTO, {
+        headers: { 'Content-Type': 'application/json' }
+      });
+      return response.data;
+    } catch (error) {
+      console.error("Error adding account.", error);
+      throw error;
+    }
+  }
+
+  /**
+   * Changes the last name of the currently logged-in user.
+   *
+   * @param {object} changeLastNameDTO Dto containing the new last name.
+   * @returns {Promise<Object>} A Promise that resolves to the updated user details.
+   * @throws {Error} If an error occurs during the operation.
+   */
+  async changeLastName(changeLastNameDTO) {
+    try {
+      const response = await axios.put(`${this.baseURL}/last-name`, changeLastNameDTO, {
+        headers: { 'Content-Type': 'application/json' }
+      });
+      return response.data;
+    } catch (error) {
+      console.error("Error adding account.", error);
+      throw error;
+    }
+  }
+
+  /**
+   * Changes the password of the currently logged-in user.
+   *
+   * @param {object} changePasswordDTO Dto containing the new password.
+   * @returns {Promise<Object>} A Promise that resolves to the updated user details.
+   * @throws {Error} If an error occurs during the operation.
+   */
+  async changePassword(changePasswordDTO) {
+    try {
+      const response = await axios.put(`${this.baseURL}/password`, changePasswordDTO, {
+        headers: { 'Content-Type': 'application/json' }
+      });
+      return response.data;
+    } catch (error) {
+      console.error("Error changing password.", error);
+      throw error;
+    }
+  }
+
+  /**
+   * Adds additional details for the currently logged-in user.
+   *
+   * @param {object} addAdditionalUserDetailsDTO Dto containing additional user details.
+   * @returns {Promise<Object>} A Promise that resolves to the updated user details.
+   * @throws {Error} If an error occurs during the operation.
+   */
+  async addAdditionalUserDetails(addAdditionalUserDetailsDTO) {
+    try {
+      const response = await axios.post(`${this.baseURL}/info`, addAdditionalUserDetailsDTO, {
+        headers: { 'Content-Type': 'application/json' }
+      });
+      return response.data;
+    } catch (error) {
+      console.error("Error adding user information.", error);
+      throw error;
+    }
+  }
+
+  /**
+   * Changes the living status of the currently logged-in user.
+   *
+   * @param {object} changeLivingStatusDTO Dto containing the new living status.
+   * @returns {Promise<Object>} A Promise that resolves to the updated user details.
+   * @throws {Error} If an error occurs during the operation.
+   */
+  async changeLivingStatus(changeLivingStatusDTO) {
+    try {
+      const response = await axios.put(`${this.baseURL}/living-status`, changeLivingStatusDTO, {
+        headers: { 'Content-Type': 'application/json' }
+      });
+      return response.data;
+    } catch (error) {
+      console.error("Error changing living status.", error);
+      throw error;
+    }
+  }
+
+  /**
+   * Changes the income of the currently logged-in user.
+   *
+   * @returns {Promise<Object>} A Promise that resolves to the updated user details.
+   * @throws {Error} If an error occurs during the operation.
+   * @param changeIncomeDTO
+   */
+  async changeIncome(changeIncomeDTO) {
     try {
-      let formData = new FormData();
-      formData.append("income", income);
-      formData.append("householdType", householdType);
+      const response = await axios.put(`${this.baseURL}/income`, changeIncomeDTO, {
+        headers: { 'Content-Type': 'application/json' }
+      });
+      return response.data;
+    } catch (error) {
+      console.error("Error changing income.", error);
+      throw error;
+    }
+  }
 
-      const response = await axios.post(
-        `${this.baseURL}/addUserDetails`,
-        formData,
-        {
-            headers: {
-                'Content-Type': 'application/json'
-            }
-        }
-      );
-      console.log(response.data);
+  /**
+   * Changes the saving percentage of the currently logged-in user.
+   *
+   * @returns {Promise<Object>} A Promise that resolves to the updated user details.
+   * @throws {Error} If an error occurs during the operation.
+   * @param savingPercentage
+   */
+
+  async changeSavingPercentage(savingPercentage) {
+    try {
+      const response = await axios.put(`${this.baseURL}/saving-percentage`, savingPercentage, {
+        headers: { 'Content-Type': 'application/json' }
+      });
       return response.data;
     } catch (error) {
-      console.error("Error adding account");
+      console.error("Error changing income.", error);
+      throw error;
+    }
+  }
+
+  /**
+   * Deletes the currently logged-in user.
+   *
+   * @param {string} verificationCode Verification code for user deletion.
+   * @throws {Error} If an error occurs during deletion.
+   */
+  async deleteUser(verificationCode) {
+    try {
+      await axios.delete(`${this.baseURL}?verificationCode=${verificationCode}`, {
+        headers: { 'Content-Type': 'application/json' }
+      });
+    } catch (error) {
+      console.error("Error deleting user.", error)
+      throw error;
+    }
+  }
+
+  /**
+   * Resets the user's password.
+   *
+   * @param resetPasswordDto DTO with required information for resetting password.
+   * @throws {Error} If an error occurs during resetting.
+   */
+  async resetPassword(resetPasswordDto) {
+    try {
+      await axios.put(`${this.baseURL}/password-reset`, resetPasswordDto)
+    } catch (error) {
+      console.error("Error resetting password.", error)
       throw error;
     }
   }
diff --git a/src/services/internal/UserService.js b/src/services/internal/UserService.js
index 89028e73af74a032207a3b159c9489f892e858b0..da4334a728090c29e2c8a0faff03065313161cce 100644
--- a/src/services/internal/UserService.js
+++ b/src/services/internal/UserService.js
@@ -1,166 +1,95 @@
 import axios from "axios";
 
+/**
+ * Service class for user-related operations.
+ */
 class UserService {
   baseURL = "http://localhost:8080/api/v1/auth";
+  authInterceptor = null;
 
-  async register(email, firstName, lastName, password) {
-    try {
-      const response = await axios.post(`${this.baseURL}/register`, {
-        email,
-        firstName,
-        lastName,
-        password
-      }, {
-        headers: {
-          'Content-Type': 'application/json'
-        }
-      });
-      console.log(response.data);
-      return response.data;
-    } catch (error) {
-      console.error("Error adding account", error);
-      throw error;
+  /**
+   * Constructs an instance of UserService and sets the authentication header.
+   */
+  constructor() {
+    const authToken = sessionStorage.getItem("authtoken");
+    if (authToken) {
+      this.setAxiosAuthHeader(authToken);
     }
   }
-  
 
-  async login(email, password) {
+  /**
+   * Registers a new user.
+   * @param {Object} registerRequestDTO - The registration request data transfer object.
+   * @returns {Promise} The response data from the server.
+   * @throws {Error} If an error occurs during the request.
+   */
+  async register(registerRequestDTO) {
     try {
-      let formData = new FormData();
-      formData.append("email", email);
-      formData.append("password", password);
+      const response = await axios.post(`${this.baseURL}/register`, registerRequestDTO)
 
-      const response = await axios.post(
-        `${this.baseURL}/login`,
-        formData,
-        {
-        headers: {
-            'Content-Type': 'application/json'
-        }
-        }
-      );
-      const token = response.data.token;
-      sessionStorage.removeItem("authToken");
-      sessionStorage.setItem("authToken", token);
-      console.log(token);
-      this.setAxiosAuthHeader(token);
-      console.log(response.data);
       return response.data;
     } catch (error) {
-      console.error("Error logging in");
+      console.error("Error adding account", error);
       throw error;
     }
   }
 
-  setAxiosAuthHeader() {
-    axios.interceptors.request.use(
-      (config) => {
-        const token = sessionStorage.getItem("AuthToken");
-        if (token) {
-          config.headers["Authorization"] = "Bearer " + token;
-        }
-        return config;
-      },
-      (error) => {
-        return Promise.reject(error);
-      }
-    );
-  }
-
-  async changeEmail(newEmail) {
+  /**
+   * Logs in a user.
+   * @param {Object} loginRequestDTO - The login request data transfer object.
+   * @returns {Promise} The response data from the server.
+   * @throws {Error} If an error occurs during the request.
+   */
+  async login(loginRequestDTO) {
     try {
-      let formData = new FormData();
-      formData.append("newEmail", newEmail);
-
-      const response = await axios.put(
-        `${this.baseURL}/changeEmail`,
-        formData,
-        {
-        headers: {
-            'Content-Type': 'application/json'
-        }
-        }
-      );
-      console.log(response.data);
+      const response = await axios.post(`${this.baseURL}/login`, loginRequestDTO, {
+        headers: {'Content-Type': 'application/json'}
+      });
+      this.setAxiosAuthHeader(response.data.token);
       return response.data;
     } catch (error) {
-      console.error("Error adding account");
+      console.error(error.response.data.errorMessage);
       throw error;
     }
   }
 
-  async changeFirstName(newFirstName) {
+  /**
+   * Checks if an email exists.
+   * @param {string} email - The email address to check.
+   * @returns {Promise} True if the email exists, otherwise false.
+   * @throws {Error} If an error occurs during the request.
+   */
+  async emailExist(email) {
     try {
-      let formData = new FormData();
-      formData.append("newFirstName", newFirstName);
-
-      const response = await axios.put(
-        `${this.baseURL}/changeFirstName`,
-        formData,
-        {
-            headers: {
-                'Content-Type': 'application/json'
-            }
-        }
-      );
-      console.log(response.data);
+      const response = await axios.get(`${this.baseURL}/emailExist`, {params: {email}});
       return response.data;
     } catch (error) {
-      console.error("Error adding account");
+      console.error("Error checking email existence", error);
       throw error;
     }
   }
 
-  async changeLastName(newLastName) {
-    try {
-      let formData = new FormData();
-      formData.append("newLastName", newLastName);
-
-      const response = await axios.put(
-        `${this.baseURL}/changeLastName`,
-        formData,
-        {
-            headers: {
-                'Content-Type': 'application/json'
-            }
-        }
-      );
-      console.log(response.data);
-      return response.data;
-    } catch (error) {
-      console.error("Error adding account");
-      throw error;
+  /**
+   * Sets the Axios authentication header with the provided token.
+   * @param {string} token - The authentication token.
+   */
+  setAxiosAuthHeader(token) {
+    if (this.authInterceptor !== null) {
+      axios.interceptors.request.eject(this.authInterceptor);
     }
-  }
 
-  async changePassword(newPassword) {
-    try {
-      let formData = new FormData();
-      formData.append("newPassword", newPassword);
-
-      const response = await axios.put(
-        `${this.baseURL}/changePassword`,
-        formData,
-        {
-            headers: {
-                'Content-Type': 'application/json'
-            }
+    this.authInterceptor = axios.interceptors.request.use(
+        (config) => {
+          if (token) {
+            config.headers["Authorization"] = `Bearer ${token}`;
+          }
+          return config;
+        },
+        (error) => {
+          return Promise.reject(error);
         }
-      );
-      console.log(response.data);
-      return response.data;
-    } catch (error) {
-      console.error("Error adding account");
-      throw error;
-    }
-  }
-  
-  async EmailExist(email) {
-    const response = await axios.get(`${this.baseURL}/emailExist`, {
-    params: { email },
-    });
-    console.log(response.data);
-    return response.data;
+    );
   }
 }
-export default new UserService();
\ No newline at end of file
+
+export default new UserService();
diff --git a/src/store/UserStore.js b/src/store/UserStore.js
new file mode 100644
index 0000000000000000000000000000000000000000..efc045b4710c6656ff433e6a8ff94ae3ff2fd2dd
--- /dev/null
+++ b/src/store/UserStore.js
@@ -0,0 +1,318 @@
+import axios from 'axios';
+import LoginRequestDTO from '../models/user/LoginRequestDTO';
+import RegisterRequestDTO from '../models/user/RegisterRequestDTO';
+import UserService from '@/services/internal/UserService';
+import UserDetailsService from '@/services/internal/UserDetailsService';
+import ChangeFirstNameDTO from '../models/user/ChangeFirstNameDTO';
+import ChangeLastNameDTO from '../models/user/ChangeLastNameDTO';
+import ChangeIncomeDTO from '../models/user/ChangeIncomeDTO';
+import ChangeLivingStatusDTO from '../models/user/ChangeLivingStatusDTO';
+
+const userStore = {
+
+    /**
+     * State for user details and authentication status.
+     *
+     * @namespace state
+     * @property {string} email User's email.
+     * @property {string} firstName User's first name.
+     * @property {string} lastName User's last name.
+     * @property {string} income User's income.
+     * @property {string} savingPercentage the percentage of the income that is to be saved.
+     * @property {string} livingStatus User's living status.
+     * @property {string} authToken Authentication token.
+     * @property {boolean} isAuthenticated Authentication status.
+     */
+    state: () => ({
+    email: sessionStorage.getItem("email") || "",
+    firstName: sessionStorage.getItem("firstName") || "",
+    lastName: sessionStorage.getItem("lastName") || "",
+    income: sessionStorage.getItem("income") || "",
+    savingPercentage: sessionStorage.getItem("savingPercentage") || "",
+    livingStatus: sessionStorage.getItem("livingStatus") || "",
+    authToken: sessionStorage.getItem("authToken") || "",
+    isAuthenticated: sessionStorage.getItem("isAuthenticated") === "true"
+  }),
+
+    /**
+     * Mutations for updating state.
+     *
+     * @namespace mutations
+     */
+  mutations: {
+
+      /**
+       * Set authentication token.
+       *
+       * @param {Object} state - The Vuex state.
+       * @param {string} authToken - The authentication token.
+       */
+    setAuthToken(state, authToken) {
+      state.authToken = authToken;
+      sessionStorage.setItem("authToken", authToken);
+      axios.defaults.headers.common['Authorization'] = `Bearer ${authToken}`;
+    },
+
+      /**
+       * Set user's email.
+       *
+       * @param {Object} state The Vuex state.
+       * @param {string} email The user's email.
+       */
+    setEmail(state, email) {
+      state.email = email;
+      sessionStorage.setItem("email", email);
+    },
+
+      /**
+       * Set user's first name.
+       *
+       * @param {Object} state The Vuex state.
+       * @param {string} firstName The user's first name.
+       */
+    setFirstName(state, firstName) {
+      state.firstName = firstName;
+      sessionStorage.setItem("firstName", firstName);
+    },
+
+      /**
+       * Set user's last name.
+       *
+       * @param {Object} state The Vuex state.
+       * @param {string} lastName The user's last name.
+       */
+    setLastName(state, lastName) {
+      state.lastName = lastName;
+      sessionStorage.setItem("lastName", lastName);
+    },
+
+      /**
+       * Set user's income.
+       *
+       * @param {Object} state The Vuex state.
+       * @param {string} income The user's income.
+       */
+    setIncome(state, income) {
+      state.income = income;
+      sessionStorage.setItem("income", income);
+    },
+      /**
+       * Set user's savingPercentage.
+       *
+       * @param {Object} state The Vuex state.
+       * @param {string} savingPercentage The user's desired saving percentage.
+       */
+      setSavingPercentage(state, savingPercentage) {
+        state.savingPercentage = savingPercentage;
+        sessionStorage.setItem("savingPercentage", savingPercentage);
+      },
+
+      /**
+       * Set user's living status.
+       *
+       * @param {Object} state The Vuex state.
+       * @param {string} livingStatus The user's living status.
+       */
+    setLivingStatus(state, livingStatus) {
+      state.livingStatus = livingStatus;
+      sessionStorage.setItem("livingStatus", livingStatus);
+    },
+
+      /**
+       * Set authentication status.
+       *
+       * @param {Object} state The Vuex state.
+       * @param {boolean} isAuthenticated The authentication status.
+       */
+    setAuthentication(state, isAuthenticated) {
+      state.isAuthenticated = isAuthenticated;
+      sessionStorage.setItem("isAuthenticated", isAuthenticated.toString());
+    },
+
+      /**
+       * Reset state to initial values.
+       *
+       * @param {Object} state The Vuex state.
+       */
+    resetState(state) {
+      state.email = "";
+      state.firstName = "";
+      state.lastName = "";
+      state.income = "";
+      state.savingPercentage = "";
+      state.livingStatus = "";
+      state.authToken = "";
+      state.isAuthenticated = false;
+      sessionStorage.clear();
+    }
+  },
+  actions: {
+
+    /**
+     * Login user.
+     *
+     * @param {Object} context The Vuex context.
+     * @param {Object} payload The login payload.
+     * @param {string} payload.email The user's email.
+     * @param {string} payload.password The user's password.
+     */
+    async loginUser({commit, dispatch}, {email, password}) {
+      const loginRequestDTO = new LoginRequestDTO(email, password);
+      try {
+        const response = await UserService.login(loginRequestDTO);
+        commit('setAuthToken', response.token);
+        commit('setEmail', email);
+        await dispatch('fetchUserDetails');
+        commit('setAuthentication', true);
+      } catch (error) {
+        console.error("Error logging in user:", error);
+        throw error;
+      }
+    },
+
+    /**
+     * Register user.
+     *
+     * @param {Object} context The Vuex context.
+     * @param {Object} payload The registration payload.
+     * @param {string} payload.email The user's email.
+     * @param {string} payload.firstName The user's first name.
+     * @param {string} payload.lastName The user's last name.
+     * @param {string} payload.password The user's password.
+     * @param {string} payload.emailVerificationCode - The email verification code.
+     */
+    async registerUser({commit}, {email, firstName, lastName, password, emailVerificationCode}) {
+      const registerRequestDTO = new RegisterRequestDTO(email, firstName,
+          lastName, password, emailVerificationCode);
+
+      try {
+        const response = await UserService.register(registerRequestDTO);
+
+        commit('setAuthToken', response.token);
+        commit('setEmail', email);
+        commit('setFirstName', firstName);
+        commit('setLastName', lastName);
+        commit('setAuthentication', true);
+
+      } catch (error) {
+        console.error("Error registering user:", error);
+        throw error;
+      }
+    },
+
+    /**
+     * Fetch user details.
+     *
+     * @param {Object} context The Vuex context.
+     */
+    async fetchUserDetails({commit}) {
+      try {
+        const userDetails = await UserDetailsService.retrieveUserDetails();
+        commit('setFirstName', userDetails.firstName);
+        commit('setLastName', userDetails.lastName);
+        commit('setSavingPercentage', userDetails.savingPercentage);
+        commit('setIncome', userDetails.income);
+        commit('setLivingStatus', userDetails.livingStatus);
+      } catch (error) {
+        console.error("Error retrieving user details:", error);
+        throw error;
+      }
+    },
+
+    /**
+     * Change user's first name.
+     *
+     * @param {Object} context The Vuex context.
+     * @param {string} newFirstName The new first name.
+     */
+    async changeFirstName({commit}, newFirstName) {
+      const changeFirstNameDTO = new ChangeFirstNameDTO(newFirstName);
+      try {
+        await UserDetailsService.changeFirstName(changeFirstNameDTO);
+        commit('setFirstName', newFirstName);
+      } catch (error) {
+        console.error("Error changing first name:", error);
+        throw error;
+      }
+    },
+
+    /**
+     * Change user's last name.
+     *
+     * @param {Object} commit The Vuex context.
+     * @param {string} newLastName The new last name.
+     */
+    async changeLastName({commit}, newLastName) {
+      const changeLastNameDTO = new ChangeLastNameDTO(newLastName);
+      try {
+        await UserDetailsService.changeLastName(changeLastNameDTO);
+        commit('setLastName', newLastName);
+      } catch (error) {
+        console.error("Error changing last name:", error);
+        throw error;
+      }
+    },
+
+    /**
+     * Change user's income.
+     *
+     * @param {Object} context The Vuex context.
+     * @param {string} newIncome The new income.
+     */
+    async changeIncome({commit}, newIncome) {
+      const changeIncomeDTO = new ChangeIncomeDTO(newIncome);
+      console.log(changeIncomeDTO)
+      try {
+        await UserDetailsService.changeIncome(changeIncomeDTO);
+        commit('setIncome', newIncome);
+      } catch (error) {
+        console.error("Error changing income:", error);
+        throw error;
+      }
+    },
+
+    /**
+     * Change user's saving percentage.
+     *
+     * @param {Object} context The Vuex context.
+     * @param {string} newSavingPercentage The new income.
+     */
+    async changeSavingPercentage({commit}, newSavingPercentage) {
+      try {
+        await UserDetailsService.changeSavingPercentage(newSavingPercentage);
+        commit('setSavingPercentage', newSavingPercentage);
+      } catch (error) {
+        console.error("Error changing saving percentage:", error);
+        throw error;
+      }
+    },
+
+    /**
+     * Change user's living status.
+     *
+     * @param {Object} context The Vuex context.
+     * @param {string} newLivingStatus The new living status.
+     */
+    async changeLivingStatus({commit}, newLivingStatus) {
+      const changeLivingStatusDTO = new ChangeLivingStatusDTO(newLivingStatus);
+      try {
+        await UserDetailsService.changeLivingStatus(changeLivingStatusDTO);
+        commit('setLivingStatus', newLivingStatus);
+      } catch (error) {
+        console.error("Error changing living status:", error);
+        throw error;
+      }
+    },
+
+    /**
+     * Logout user.
+     *
+     * @param {Object} context - The Vuex context.
+     */
+    async logout({commit}) {
+      commit('resetState');
+    },
+  }
+};
+
+export default userStore;
diff --git a/src/store/index.js b/src/store/index.js
index 7f5b89c73b493e8a5fdfbc3f18c7ae96068fffae..bc078d3a52e70c64df68bf8964ce24e15cd9055c 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -1,14 +1,8 @@
-import { createStore } from 'vuex'
+import { createStore } from 'vuex';
+import userStore from './UserStore';
 
 export default createStore({
-  state: {
-  },
-  getters: {
-  },
-  mutations: {
-  },
-  actions: {
-  },
   modules: {
+    user: userStore
   }
-})
+});
\ No newline at end of file
diff --git a/src/views/BudgetView.vue b/src/views/BudgetView.vue
index e3cc20c86aaceb94662de59adf7608ba99015d82..cb4d3bbb13c6ed81449f6fb59d15ecb4c6b15186 100644
--- a/src/views/BudgetView.vue
+++ b/src/views/BudgetView.vue
@@ -7,3 +7,10 @@ import BudgetMain from "@/components/budget/BudgetMain.vue";
     <BudgetMain />
   </div>
 </template>
+
+<style scoped>
+.budget {
+  box-sizing: border-box;
+  overflow-y: auto;
+}
+</style>
diff --git a/src/views/ContactView.vue b/src/views/ContactView.vue
new file mode 100644
index 0000000000000000000000000000000000000000..efe92a1f05a0825a3ce28c8090752120a74bc28b
--- /dev/null
+++ b/src/views/ContactView.vue
@@ -0,0 +1,78 @@
+<script setup>
+import {onMounted, nextTick, ref} from 'vue';
+import Contact from "@/components/contact/ContactComponent.vue";
+import FAQs from "@/components/contact/FAQs.vue";
+
+const contactFormRef = ref(null);
+
+onMounted(() => {
+  nextTick(() => {
+    setTimeout(() => {
+      const contactFormElement = contactFormRef.value;
+      if (contactFormElement) {
+        const contactFormHeight = contactFormElement.clientHeight;
+        const scrollOffset = contactFormHeight * 0.2;
+        const scrollTargetPosition = contactFormElement.offsetTop + scrollOffset;
+
+        window.scrollTo({
+          top: scrollTargetPosition,
+          behavior: 'smooth'
+        });
+
+        setTimeout(() => {
+          window.scrollTo({
+            top: 0,
+            behavior: 'smooth'
+          });
+        }, 700);
+      }
+    }, 1000);
+  });
+});
+</script>
+
+<template>
+  <div class="contact-wrapper">
+    <div class="contact-main-container">
+      <Contact class="contact-main"/>
+    </div>
+    <FAQs class="contact-sidebar"/>
+  </div>
+</template>
+
+<style scoped>
+.contact-wrapper {
+  overflow-y: scroll;
+}
+
+.contact-main-container {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 100vh;
+  width: 100%;
+}
+
+@media (min-width: 900px) {
+  .contact-wrapper {
+    display: grid;
+    height: 100vh;
+    width: 100vw;
+    overflow: scroll;
+    grid-template-columns: 75% 25%;
+  }
+}
+
+@media (max-width: 900px) {
+  .contact-wrapper {
+    display: flex;
+    flex-direction: column;
+    height: 100vh;
+    width: 100vw;
+  }
+
+  .contact-sidebar {
+    overflow: auto;
+  }
+}
+</style>
diff --git a/src/views/ForgotPasswordView.vue b/src/views/ForgotPasswordView.vue
new file mode 100644
index 0000000000000000000000000000000000000000..73aec798ce38104f43be9bd2cba8fcaeacfec500
--- /dev/null
+++ b/src/views/ForgotPasswordView.vue
@@ -0,0 +1,15 @@
+<script setup>
+
+import ForgotPassword from "@/components/login/ForgotPassword.vue";
+
+</script>
+
+<template>
+  <div class="forgot-password">
+    <ForgotPassword />
+  </div>
+</template>
+
+<style scoped>
+
+</style>
diff --git a/src/views/GoalsView.vue b/src/views/GoalsView.vue
index 87185c42f86914de9686606f3ebcbfae2546d5c3..4a3b689c899771262f896cad87597c17f477d84d 100644
--- a/src/views/GoalsView.vue
+++ b/src/views/GoalsView.vue
@@ -1,22 +1,146 @@
 <script setup>
-import { ref } from 'vue';
-import GoalsMain from "@/components/goals/GoalsMain.vue";
-import GoalsChoice from "@/components/goals/GoalsChoice.vue";
-import CreateGoals from "@/components/goals/CreateGoals.vue";
-import CreateChallenge from "@/components/goals/CreateChallenge.vue";
-
-// State to control which component is shown
-const activeComponent = ref('GoalsMain');
+import { ref , onMounted} from 'vue';
+import { useRoute } from 'vue-router';
+import AddChoice from '@/components/goals/AddChoice.vue';
+import ChallengeComponent from '@/components/goals/ChallengeComponent.vue';
+import CreateChallenge from '@/components/goals/CreateChallenge.vue';
+import CreateGoal from '@/components/goals/CreateGoal.vue';
+import CreateChallengeCompetition from '@/components/goals/CreateChallengeCompetition.vue';
+import CreateSharedGoal from '@/components/goals/CreateSharedGoal.vue';
+import GoalComponent from '@/components/goals/GoalComponent.vue';
+import MainButtons from '@/components/goals/MainButtons.vue';
+import OverviewComponent from '@/components/goals/OverviewComponent.vue';
+import SharedChoice from '@/components/goals/SharedChoice.vue';
+import joinSharedGoal from '@/components/goals/JoinSharedGoal.vue';
+import ChallengeCompetitionComponent from '@/components/goals/ChallengeCompetitionComponent.vue';
+import CompetitionChoice from '@/components/goals/CompetitionChoice.vue';
+import JoinChallengeCompetition from '@/components/goals/JoinChallengeCompetition.vue';
+import ChallengeChoice from '@/components/goals/ChallengeChoice.vue';
+import CreateAutomaticChallenges from '@/components/goals/CreateAutomaticChallenges.vue';
+
+
+const activeComponent = ref('MainButtons');
+const challengeId = ref(null);
+const goalId = ref(null);
+const friendChallengeId = ref(null);
+
+const route = useRoute();
+const goalIdFromQuery = route.query.goalId;
+
+onMounted(async () => {
+  if(goalIdFromQuery){
+    goalId.value = goalIdFromQuery + 1;
+    activeComponent.value = 'GoalComponent';
+  } else {
+    activeComponent.value = 'MainButtons';
+  }
+});
+
+function receiveChallengeId(id){
+  challengeId.value = id;
+  activeComponent.value = 'ChallengeComponent';
+}
+
+function receiveGoalId(id){
+  goalId.value = id;
+  activeComponent.value = 'GoalComponent';
+}
+
+function receiveChallengeCompetitionId(id, friendId){
+  challengeId.value = id;
+  friendChallengeId.value = friendId;
+  activeComponent.value = 'ChallengeCompetitionComponent';
+}
+
 </script>
 
 <template>
   <div class="goals">
-    <GoalsMain v-if="activeComponent === 'GoalsMain'" @switch="activeComponent = 'GoalsChoice'" />
-    <GoalsChoice v-if="activeComponent === 'GoalsChoice'" @back="activeComponent = 'GoalsMain'"
-    @challenge="activeComponent = 'CreateChallenges'" @goal="activeComponent = 'CreateGoals'" />
-    <CreateGoals v-if="activeComponent === 'CreateGoals'" @back="activeComponent = 'GoalsChoice'"
-    @create="activeComponent = 'GoalsMain'"/>
-    <CreateChallenge v-if="activeComponent === 'CreateChallenges'" @back="activeComponent = 'GoalsChoice'"
-    @create="activeComponent = 'GoalsMain'"/>
+    <AddChoice v-if="activeComponent === 'AddChoice'" 
+    @back="activeComponent = 'MainButtons'"
+    @createChallenge="activeComponent = 'ChallengeChoice'"
+    @createGoal="activeComponent = 'CreateGoal'"
+    @competitionChoice="activeComponent = 'CompetitionChoice'"
+    @sharedChoice="activeComponent = 'SharedChoice'"
+    />
+    <CreateChallenge v-if="activeComponent === 'CreateChallenge'" 
+    @back="activeComponent = 'ChallengeChoice'"
+    @challengeComponent="receiveChallengeId"
+    />
+    <CreateGoal v-if="activeComponent === 'CreateGoal'" 
+    @back="activeComponent = 'AddChoice'"
+    @goalComponent="receiveGoalId"
+    />
+    <CreateChallengeCompetition v-if="activeComponent === 'CreateChallengeCompetition'" 
+    @back="activeComponent = 'CompetitionChoice'"
+    @goalSharedComponent="receiveChallengeCompetitionId"
+    />
+    <CreateSharedGoal v-if="activeComponent === 'CreateSharedGoal'" 
+    @back="activeComponent = 'SharedChoice'" 
+    @goalComponent="receiveGoalId"
+    />
+    <GoalComponent v-if="activeComponent === 'GoalComponent'" 
+    @back="activeComponent = 'OverviewComponent'" 
+    @goalComponent="receiveGoalId"
+    :goal-id="goalId"
+    />
+    <ChallengeComponent v-if="activeComponent === 'ChallengeComponent'" 
+    @back="activeComponent = 'OverviewComponent'" 
+    :challenge-id="challengeId"
+    />
+    <ChallengeCompetitionComponent v-if="activeComponent === 'ChallengeCompetitionComponent'" 
+    @back="activeComponent = 'OverviewComponent'" 
+    :challenge-id="challengeId"
+    :friend-challenge-id="friendChallengeId"
+    />
+    <MainButtons v-if="activeComponent === 'MainButtons'" 
+    @overview="activeComponent = 'OverviewComponent'"
+    @add="activeComponent = 'AddChoice'"
+    />
+    <OverviewComponent v-if="activeComponent === 'OverviewComponent'" 
+    @back="activeComponent = 'MainButtons'"
+    @add="activeComponent = 'AddChoice'"
+    @challengeComponent="receiveChallengeId"
+    @goalComponent="receiveGoalId"
+    @challengeCompetitionComponent="receiveChallengeCompetitionId"
+    />
+    <joinSharedGoal v-if="activeComponent === 'JoinSharedGoal'" 
+    @back="activeComponent = 'SharedChoice'"
+    @goalComponent="receiveGoalId"
+    />
+    <SharedChoice v-if="activeComponent === 'SharedChoice'" 
+    @createSharedGoal="activeComponent = 'CreateSharedGoal'"
+    @joinSharedGoal="activeComponent = 'JoinSharedGoal'"
+    @back="activeComponent = 'AddChoice'" 
+    />
+    <CompetitionChoice v-if="activeComponent === 'CompetitionChoice'" 
+    @join="activeComponent = 'JoinChallengeCompetition'"
+    @create="activeComponent = 'CreateChallengeCompetition'"
+    @back="activeComponent = 'AddChoice'" 
+    />
+    <JoinChallengeCompetition v-if="activeComponent === 'JoinChallengeCompetition'" 
+    @goalSharedComponent="receiveChallengeCompetitionId"
+    @back="activeComponent = 'CompetitionChoice'" 
+    />
+    <ChallengeChoice v-if="activeComponent === 'ChallengeChoice'" 
+    @back="activeComponent = 'AddChoice'" 
+    @create="activeComponent = 'CreateChallenge'"
+    @automatic="activeComponent = 'CreateAutomaticChallenges'"
+    />
+    <CreateAutomaticChallenges v-if="activeComponent === 'CreateAutomaticChallenges'" 
+    @back="activeComponent = 'ChallengeChoice'" 
+    @challengeComponent="receiveChallengeId"
+    />
   </div>
 </template>
+
+<style scoped>
+.goals {
+  overflow-y: auto;
+}
+
+.goals::-webkit-scrollbar {
+  background: transparent; 
+}
+</style>
+
diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue
index 456ffa1dfdd1bdbfef776f00413ac6430abab5bc..2e5500577b495f7f0be801757cceb7ce1b86d946 100644
--- a/src/views/HomeView.vue
+++ b/src/views/HomeView.vue
@@ -1,9 +1,18 @@
 <script setup>
-import HomeMain from "@/components/home/HomeMain.vue";
+import { ref } from 'vue';
+import HomeMain from '@/components/home/HomeMain.vue';
+import HomeWelcome from '@/components/home/HomeWelcome.vue';
+
+const isAuthenticated = ref(sessionStorage.getItem('authToken'));
+
 </script>
 
 <template>
   <div class="home">
-    <HomeMain />
-  </div>
+    <HomeMain v-if="isAuthenticated" />
+    <HomeWelcome v-else />
+  </div>  
 </template>
+
+<style scoped>
+</style>
diff --git a/src/views/LoginView.vue b/src/views/LoginView.vue
index 529e3bb752811093ef95d38685af613e3b7e5c1a..21cf3704d39898a9dcad40df7fcfce5b85c4d28d 100644
--- a/src/views/LoginView.vue
+++ b/src/views/LoginView.vue
@@ -1,27 +1,39 @@
 <script setup>
 import { onMounted, ref } from 'vue';
-import Login from '@/components/login/LoginComponent.vue';
-import Register from '@/components/login/RegisterComponent.vue';
+import LoginComponent from '@/components/login/LoginComponent.vue';
+import RegisterComponent from '@/components/login/RegisterComponent.vue';
 
 const isLoginActive = ref(sessionStorage.getItem('isLoginActive') === 'true' || true);
 
-function toggleView(isActive) {
-  console.log('Toggle view called with:', isActive);
-  isLoginActive.value = isActive;
-  sessionStorage.setItem('isLoginActive', isActive.toString());
-}
-
 onMounted(() => {
   const registerSession = sessionStorage.getItem('isLoginActive');
   if (registerSession) {
     isLoginActive.value = registerSession === 'true';
   }
 });
+
+/**
+ * Toggles the active state between login and registration views.
+ * @param {boolean} isActive - Determines if the login is active or not.
+ */
+function toggleView(isActive) {
+  isLoginActive.value = isActive;
+  sessionStorage.setItem('isLoginActive', isActive.toString());
+}
 </script>
 
 <template>
   <div class="loginRegisterComponent">
-    <Login v-if="isLoginActive" :isLoginActive="isLoginActive" @toggle-view="toggleView" />
-    <Register v-else :isLoginActive="isLoginActive" @toggle-view="toggleView" />
+    <LoginComponent v-if="isLoginActive" :is="isLoginActive" @toggle-view="toggleView" />
+    <RegisterComponent v-else :is-login-active="isLoginActive" @toggle-view="toggleView" />
   </div>
 </template>
+
+<style scoped>
+.loginRegisterComponent {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  margin: 10px;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/ProfileView.vue b/src/views/ProfileView.vue
index e63eda6c61932ec8840c9a06213bcea31596404d..849400794bc211a22aea7b669886d9d26b6f198c 100644
--- a/src/views/ProfileView.vue
+++ b/src/views/ProfileView.vue
@@ -1,37 +1,63 @@
 <script setup>
-import { ref } from 'vue';
+import {onMounted, ref} from 'vue';
+import {useRoute} from "vue-router";
 import ProfileMain from "@/components/profile/ProfileMain.vue";
 import ProfileBar from "@/components/profile/ProfileBar.vue";
+import ProfileDropdown from "@/components/profile/ProfileDropdown.vue";
 
+const route = useRoute();
 const activeComponent = ref('MyProfile');
 
 const setActiveComponent = (componentName) => {
   activeComponent.value = componentName;
 };
+
+onMounted(() => {
+  try {
+    if (route.params.component){
+      setActiveComponent(route.params.component);
+    }
+  } catch (e) {
+    console.error(e);
+  }
+});
+
 </script>
 
 <template>
-  <div class="profile-grid-container">
+  <div class="profile-view">
+    <ProfileDropdown class="profile-dropdown" @select="setActiveComponent" />
     <ProfileMain :component="activeComponent" />
-    <ProfileBar @select="setActiveComponent" />
+    <ProfileBar class="profile-bar" @select="setActiveComponent" />
   </div>
 </template>
 
+
 <style scoped>
-.profile-grid-container {
-  display: grid;
-  grid-template-columns: 75% 25%;
-  grid-template-areas: 'profile-main profile-bar'; 
-  height: 100%;
-  width: 100%;
-  overflow: hidden;
+.profile-view {
+  padding-right: 0;
 }
 
-.profile-main {
-  grid-area: profile-main;
+@media (min-width: 800px){
+  .profile-dropdown {
+    display: none;
+  }
+
+  .profile-view {
+    grid-template-columns: 3fr 1fr;
+    display: grid;
+  }
 }
 
-.profile-bar {
-  grid-area: profile-bar;
+@media screen and (max-width: 800px) {
+  .profile-dropdown {
+    width: 100%;
+    margin-top: 1em;
+    margin-bottom: 1em;
+  }
+
+  .profile-bar {
+    display: none;
+  }
 }
 </style>
diff --git a/src/views/TransactionsView.vue b/src/views/TransactionsView.vue
index 4658d58dbd4d1430b621d8f8e295d668b0a9f5e9..cb79f06d7cfe16073afa2fce12bfceeaa84268b0 100644
--- a/src/views/TransactionsView.vue
+++ b/src/views/TransactionsView.vue
@@ -7,3 +7,10 @@ import TransactionsMain from "@/components/transactions/TransactionsMain.vue";
     <TransactionsMain />
   </div>
 </template>
+
+<style scoped>
+.transactions {
+  display: flex;
+  align-items: center;
+}
+</style>
diff --git a/src/views/UserDetailsView.vue b/src/views/UserDetailsView.vue
new file mode 100644
index 0000000000000000000000000000000000000000..e52dc94a6520bdb30fdac06439bfdea29baf3fa4
--- /dev/null
+++ b/src/views/UserDetailsView.vue
@@ -0,0 +1,21 @@
+<script setup>
+import { ref } from 'vue';
+import ImportFiles from "@/components/userDetails/ImportFiles.vue";
+import SelectHousehold from "@/components/userDetails/SelectHousehold.vue";
+import SelectIncome from "@/components/userDetails/SelectIncome.vue";
+
+const activeComponent = ref('SelectIncome');
+</script>
+
+<template>
+    <SelectIncome
+        v-if="activeComponent === 'SelectIncome'"
+        @next="activeComponent = 'SelectHousehold'" />
+    <SelectHousehold
+        v-if="activeComponent === 'SelectHousehold'"
+        @back="activeComponent = 'SelectIncome'"
+        @next="activeComponent = 'ImportFiles'" />
+    <ImportFiles
+        v-if="activeComponent === 'ImportFiles'"
+        @back="activeComponent = 'SelectHousehold'"/>
+</template>
\ No newline at end of file
diff --git a/src/views/VerificationView.vue b/src/views/VerificationView.vue
new file mode 100644
index 0000000000000000000000000000000000000000..2fd2efbca98b5c783829045d54f19cfa8c0627e2
--- /dev/null
+++ b/src/views/VerificationView.vue
@@ -0,0 +1,384 @@
+<script setup>
+import {ref, onMounted, onUnmounted} from 'vue';
+import {defineEmits, defineProps} from 'vue';
+import EmailService from "@/services/internal/EmailService";
+
+const inputRefs = ref([]);
+const numberOfInputs = 6;
+const inputValue = ref(Array(numberOfInputs).fill(' '));
+const timerDisplay = ref('');
+const currentDeadline = ref(null)
+const isResendBtnDisabled = ref(false);
+const resendCountdown = ref(50);
+const countdownTimer = ref(null);
+
+const emits = defineEmits(['verificationSubmit', 'goBack']);
+
+const props = defineProps({
+  email: String,
+  timerDeadline: Date,
+  errorMessage: String
+});
+
+/**
+ * Handles key press events, handling the different key presses
+ *
+ * @param event The key press event
+ * @param index The index of the input field
+ */
+const handleKeyPress = (event, index) => {
+  console.log(event.key)
+  switch (event.key) {
+    case 'Backspace':
+      handleBackspace(index);
+      break;
+    case 'ArrowLeft':
+      handleArrowLeft(index);
+      break;
+    case 'ArrowRight':
+      handleArrowRight(index);
+      break;
+    case 'tab':
+      handleArrowRight(index);
+      break;
+    case 'Enter':
+      handleVerificationSubmit();
+      break;
+    default:
+      handleCharacterInput(event, index);
+      break;
+  }
+};
+
+/**
+ * Handles pasting of text into the input fields, pasting it into the first part of the input fields
+ *
+ * @param event The paste event
+ */
+function handlePasteInput(event) {
+  if (event.clipboardData) {
+    const text = event.clipboardData.getData('text').trim();
+    console.log("Text: " + text)
+    for (let i = 0; i < Math.min(text.length, numberOfInputs); i++) {
+      inputValue.value[i] = text[i];
+    }
+  }
+}
+
+/**
+ * Handles backspace key press, removing the character in the input field and moving focus to the previous input field
+ *
+ * @param index The index of the input field
+ */
+function handleBackspace(index) {
+  inputValue.value[index] = ' ';
+  if (index > 0) {
+    inputRefs.value[index - 1].focus();
+  }
+}
+
+/**
+ * Handles arrow key press, moving focus to the previous field if available
+ *
+ * @param index The index of the input field
+ */
+function handleArrowLeft(index) {
+  if (index > 0) {
+    inputRefs.value[index - 1].focus();
+  }
+}
+
+/**
+ * Handles arrow key press, moving focus to the next field if available
+ *
+ * @param index The index of the input field
+ */
+function handleArrowRight(index) {
+  if (index < numberOfInputs - 1) {
+    inputRefs.value[index + 1].focus();
+  }
+}
+
+/**
+ * Handles character input, adding the character to the input field and moving focus to the next field if available
+ *
+ * @param event
+ * @param index
+ */
+function handleCharacterInput(event, index) {
+  console.log("Character input '" + event.key + "'")
+  const inputChar = event.key;
+  if (inputChar && inputChar.length === 1) {
+    inputValue.value[index] = inputChar;
+    if (index < numberOfInputs - 1) {
+      inputRefs.value[index + 1].focus();
+    }
+  }
+}
+
+/**
+ * Handles the submission of the verification code
+ */
+const handleVerificationSubmit = async () => {
+  const verificationCode = inputValue.value.join('').trim();
+  emits('verificationSubmit', verificationCode);
+};
+
+const countdown = () => {
+  const now = new Date();
+  const diff = currentDeadline.value - now;
+
+  if (diff > 0) {
+    const minutes = Math.floor((diff / 1000 / 60) % 60);
+    const seconds = Math.floor((diff / 1000) % 60);
+    timerDisplay.value = `${minutes}:${seconds < 10 ? '0' + seconds : seconds}`;
+  } else {
+    clearInterval(countdownTimer.value);
+    timerDisplay.value = "00:00";
+  }
+};
+
+const resendCode = async () => {
+  try {
+    startResendCountdown();
+
+    const newExpirationDate = await EmailService.sendRegisterToken(props.email)
+
+
+    if (countdownTimer.value) {
+      currentDeadline.value = new Date(newExpirationDate.expirationTimestamp)
+      countdownTimer.value = setInterval(countdown, 1000);
+    }
+
+  } catch (error) {
+    console.error("Failed to resend token: ", error);
+    isResendBtnDisabled.value = false;
+  }
+};
+
+const startResendCountdown = () => {
+  resendCountdown.value = 50;
+  isResendBtnDisabled.value = true;
+  const countdown = setInterval(() => {
+    resendCountdown.value -= 1;
+    if (resendCountdown.value <= 0) {
+      clearInterval(countdown);
+      isResendBtnDisabled.value = false;
+    }
+  }, 1000);
+};
+
+onMounted(() => {
+  currentDeadline.value = props.timerDeadline;
+  countdownTimer.value = setInterval(countdown, 1000);
+  addEventListener('paste' , handlePasteInput);
+});
+
+onUnmounted(() => {
+  removeEventListener('paste', handlePasteInput);
+  clearInterval(countdownTimer.value);
+});
+
+const returnToPreviousPage = () => {
+  emits('goBack');
+};
+
+
+</script>
+
+
+<template>
+  <div class="main-container-verification">
+    <div class="verification-container">
+      <h2 class="verification-header">
+        Verifiser e-post adressen din
+        <img class="icon" src="@/assets/img/mail.png" alt="">
+      </h2>
+      <p class="verification-code-message">En verifikasjonskode har blitt sendt til <strong>{{ props.email }}</strong>
+      </p>
+      <p class="verification-timer">Koden utgår om {{ timerDisplay }}</p>
+
+      <div class="input-group">
+        <label>Tast inn koden her:</label><br>
+        <input
+            tabindex="0"
+            v-for="(value, index) in inputValue"
+            :key="index"
+            type="text"
+            maxlength="1"
+            id="index"
+            v-model="inputValue[index]"
+            @keydown.prevent="event => handleKeyPress(event, index)"
+            @paste.prevent="event => handlePasteInput(event, index)"
+            :ref="element => inputRefs[index] = element"
+            class="input-box"/>
+      </div>
+      <button class="verify-btn" @click="handleVerificationSubmit">Verifiser</button>
+      <p>{{ props.errorMessage }}</p>
+      <p>
+        <span @click.prevent="resendCode"
+              @keydown.prevent="resendCode"
+              tabindex="0"
+              role="button"
+              :class="{ 'disabled-action-link': isResendBtnDisabled, 'action-links': !isResendBtnDisabled }">Resend kode </span>
+        <span v-if="resendCountdown">{{ `(${resendCountdown})` }}</span>
+      </p>
+      <a href="#" class="back-link" @click="returnToPreviousPage">
+        <img class="icon" src="@/assets/img/backArrow.png" alt="">
+        Tilbake
+      </a>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.main-container-verification {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  padding: 20px;
+}
+
+.verification-container {
+  background-color: var(--accent-color);
+  padding: 2rem;
+  border-radius: var(--border-radius-general);
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+  text-align: center;
+  width: 400px;
+  min-height: 400px;
+  margin: 30px 0 30px 0;
+}
+
+.verification-header {
+  color: var(--black-text);
+  margin-bottom: 1rem;
+}
+
+.verification-code-message {
+  font-size: 1rem;
+  color: var(--middle-color);
+  margin-bottom: 1rem;
+}
+
+.verification-timer {
+  color: var(--black-text);
+  margin-bottom: 1.5rem;
+}
+
+.input-group {
+  margin-bottom: 1rem;
+}
+
+input[type="text"] {
+  padding: 0.5rem;
+  margin-right: 0.25rem;
+  text-align: center;
+  border: 1px solid var(--white-general);
+  border-radius: var(--border-radius-general);
+  width: 50px;
+}
+
+.verify-btn {
+  background-color: var(--middle-color);
+  color: var(--white-text);
+  border: none;
+  padding: 0.75rem 1.5rem;
+  border-radius: var(--border-radius-general);
+  cursor: pointer;
+  width: 100%;
+  margin-bottom: 1rem;
+  transition: background-color 0.3s ease, transform 0.3s ease, box-shadow 0.3s ease;
+  box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.2);
+}
+
+.verify-btn:hover {
+  background-color: var(--dark-color);
+  transform: translateY(-3px);
+  box-shadow: 2px 4px 8px rgba(0, 0, 0, 0.4);
+}
+
+.verification-code strong {
+  font-weight: bold;
+}
+
+.action-links {
+  position: relative;
+  color: var(--middle-color);
+  cursor: pointer;
+  text-decoration: none;
+  overflow: hidden;
+}
+
+.action-links::after {
+  content: '';
+  position: absolute;
+  left: 0;
+  bottom: -2px;
+  height: 1px;
+  width: 0;
+  background-color: var(--dark-color);
+  transition: width 0.3s ease;
+}
+
+.action-links:hover::after {
+  width: 100%;
+}
+
+.action-links:focus {
+  color: var(--dark-color);
+}
+
+.disabled-action-link {
+  color: var(--grey-text);
+  cursor: not-allowed;
+  pointer-events: none;
+}
+
+.back-link {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  color: var(--black-text);
+  margin-top: 1rem;
+  text-decoration: none;
+}
+
+.back-link .icon {
+  transition: transform 0.3s ease;
+  height: 60%;
+  width: auto;
+}
+
+.back-link:hover .icon {
+  transform: translateX(-5px);
+}
+
+.icon {
+  height: 60%;
+  width: auto;
+}
+
+.input-box {
+  padding: 0.5rem;
+  margin: 0.12rem;
+  text-align: center;
+  border-radius: var(--border-radius-general);
+  width: 40px;
+  outline: none;
+}
+
+.input-box:hover, .input-box:focus {
+  scale: 1.1;
+}
+
+div.verification-container input.input-box {
+  border: 1px solid var(--accent-color-dark);
+}
+
+@media screen and (max-width: 760px) {
+  .verification-container {
+    width: 300px;
+  }
+}
+</style>
diff --git a/tests/e2e/fixtures/handelsbankenExample.pdf b/tests/e2e/fixtures/handelsbankenExample.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..aa8122e2b83419709e79c5beb3d501bec1bca024
--- /dev/null
+++ b/tests/e2e/fixtures/handelsbankenExample.pdf
@@ -0,0 +1,446 @@
+%PDF-1.3
+%âãÏÓ
+1 0 obj
+<< /Creator (HP Exstream Version 8.0.319 64-bit)
+/CreationDate (3/28/2024 03:19:09)
+/Author (Registered to: EDB DRFT)
+/Title (MultiC_JavaConnector_Arkiv)
+>>
+endobj
+%PDF Font (F3)
+6      0 obj
+<<
+/Type /Font
+/Subtype /Type1
+/Encoding 4          0 R
+/BaseFont /Courier-Bold
+>>
+endobj
+%PDF Font (F7)
+7     0 obj
+<<
+/Type /Font
+/Subtype /Type1
+/Encoding 4          0 R
+/BaseFont /Courier
+>>
+endobj
+%PDF Font (F20)
+8     0 obj
+<<
+/Type /Font
+/Subtype /Type1
+/Encoding 4          0 R
+/BaseFont /Times-Roman
+>>
+endobj
+%PDF Font (F26)
+9     0 obj
+<<
+/Type /Font
+/Subtype /Type1
+/Encoding 4          0 R
+/BaseFont /Times-Bold
+>>
+endobj
+%PDF Font (F41)
+10     0 obj
+<<
+/Type /Font
+/Subtype /Type1
+/Encoding 4          0 R
+/BaseFont /Times-Italic
+>>
+endobj
+%PDF Font (F53)
+11    0 obj
+<<
+/Type /Font
+/Subtype /Type1
+/Encoding 4          0 R
+/BaseFont /Helvetica
+>>
+endobj
+%PDF Font (F56)
+12    0 obj
+<<
+/Type /Font
+/Subtype /Type1
+/Encoding 4          0 R
+/BaseFont /Helvetica-Bold
+>>
+endobj
+%PDF Font (F73)
+13     0 obj
+<<
+/Type /Font
+/Subtype /Type1
+/Encoding 4          0 R
+/BaseFont /Helvetica-Oblique
+>>
+endobj
+%PDF Font (F78)
+14    0 obj
+<<
+/Type /Font
+/Subtype /Type1
+/Encoding 4          0 R
+/BaseFont /Helvetica-Bold
+>>
+endobj
+%PDF Font (F79)
+15    0 obj
+<<
+/Type /Font
+/Subtype /Type1
+/Encoding 4          0 R
+/BaseFont /Helvetica
+>>
+endobj
+% PDF Font (F93)
+% FullName (Verdana)
+% FamilyName (Verdana)
+% Font Version (Version 5.33)
+% Notice (© 2016 Microsoft Corporation. All Rights Reserved.)
+% Trademark (Verdana is a trademark of the Microsoft group of companies.)
+16     0 obj
+<<
+/Type /Font
+/Subtype /TrueType
+/BaseFont /Verdana
+/FirstChar 30
+/LastChar 255
+/Encoding /WinAnsiEncoding
+/Widths [ 1000 1000 352 394 459 818 636 1076 727 269 454 454 636 818 364 454 
+364 454 636 636 636 636 636 636 636 636 636 636 454 454 818 818 
+818 545 1000 684 686 698 771 632 575 775 751 421 455 693 557 843 
+748 787 603 787 695 684 616 732 684 989 685 615 685 454 454 454 
+818 636 636 601 623 521 623 596 352 623 633 272 344 592 272 973 
+633 607 623 623 427 521 394 633 592 818 592 592 525 635 454 635 
+818 1000 636 1000 269 636 459 818 636 636 636 1521 684 454 1070 1000 
+685 1000 1000 269 269 459 459 545 636 1000 636 977 521 454 981 1000 
+525 615 352 394 636 636 636 636 454 636 636 1000 545 645 818 454 
+1000 636 542 818 542 542 636 642 636 364 636 542 545 645 1000 1000 
+1000 545 684 684 684 684 684 684 984 698 632 632 632 632 421 421 
+421 421 775 748 787 787 787 787 787 818 787 732 732 732 732 615 
+605 620 601 601 601 601 601 601 955 521 596 596 596 596 272 272 
+272 272 612 633 607 607 607 607 607 818 607 633 633 633 633 592 
+623 592 ] 
+/FontDescriptor 17     0 R
+>>
+endobj
+17     0 obj
+<<
+/Type /FontDescriptor
+/Ascent 765
+/CapHeight 0
+/Descent -207
+/Flags 42
+/FontBBox [-560 -303 1523 1051]
+/FontName /Verdana
+/ItalicAngle 0
+/StemV  0
+>>
+endobj
+% PDF Font (F102)
+% FullName (Verdana_Bold)
+% FamilyName (Verdana)
+% Font Version (Version 5.33)
+% Notice (© 2016 Microsoft Corporation. All Rights Reserved.)
+% Trademark (Verdana is a trademark of the Microsoft group of companies.)
+18     0 obj
+<<
+/Type /Font
+/Subtype /TrueType
+/BaseFont /Verdana-Bold
+/FirstChar 30
+/LastChar 255
+/Encoding /WinAnsiEncoding
+/Widths [ 1000 1000 342 402 587 867 711 1272 862 332 543 543 711 867 361 480 
+361 689 711 711 711 711 711 711 711 711 711 711 402 402 867 867 
+867 617 964 776 762 724 830 683 650 811 837 546 555 771 637 948 
+847 850 733 850 782 710 682 812 764 1128 764 737 692 543 689 543 
+867 711 711 668 699 588 699 664 422 699 712 342 403 671 342 1058 
+712 687 699 699 497 593 456 712 650 980 669 651 597 711 543 711 
+867 1000 711 1000 332 711 587 1049 711 711 711 1777 710 543 1135 1000 
+692 1000 1000 332 332 587 587 711 711 1000 711 964 593 543 1068 1000 
+597 737 342 402 711 711 711 711 543 711 711 964 598 850 867 480 
+964 711 587 867 598 598 711 721 711 361 711 598 598 850 1182 1182 
+1182 617 776 776 776 776 776 776 1094 724 683 683 683 683 546 546 
+546 546 830 847 850 850 850 850 850 867 850 812 812 812 812 737 
+735 713 668 668 668 668 668 668 1018 588 664 664 664 664 342 342 
+342 342 679 712 687 687 687 687 687 867 687 712 712 712 712 651 
+699 651 ] 
+/FontDescriptor 19     0 R
+>>
+endobj
+19     0 obj
+<<
+/Type /FontDescriptor
+/Ascent 765
+/CapHeight 0
+/Descent -207
+/Flags 42
+/FontBBox [-550 -303 1707 1072]
+/FontName /Verdana_Bold
+/ItalicAngle 0
+/StemV  0
+>>
+endobj
+%PDF Font (F178)
+20     0 obj
+<<
+/Type /Font
+/Subtype /Type1
+/Encoding 4          0 R
+/BaseFont /Helvetica-BoldOblique
+>>
+endobj
+%PDF Font (F211)
+21     0 obj
+<<
+/Type /Font
+/Subtype /Type1
+/Encoding 4          0 R
+/BaseFont /Times-BoldItalic
+>>
+endobj
+%PDF Font (F297)
+22    0 obj
+<<
+/Type /Font
+/Subtype /Type1
+/Encoding 23    0 R
+/ToUnicode 24    0 R
+/BaseFont /Symbol
+>>
+endobj
+23    0 obj
+<<
+/Type /Encoding
+/Differences [ 1/Sigma/epsilon/chi/omicron/nu/delta]
+>>
+endobj
+24    0 obj
+<<
+/Filter [/ASCII85Decode /FlateDecode]
+/Length 321
+>>
+stream
+8;U<-4`?!-%#/tTKu^5T"HJNq.+4AZ-&-lI[+ul[.+45G+Un;G`ds`2+g:7QZ0^)D
+Bs4X>*nZ6(U-fHddaI)`<[SF_S<Scp$rRC]A5U>$>@=/8gD5ikX%*87SZpF:>eJhL
+e8=ebDNF#aY]hJB.6YN3/,T;PTNeP3iJcJX$Clae0k&=BDr:C/6htDT4?E`cM2Ua)
+\mA/<FdhhC'aN7ZIk@YnNa^_lj(jnWP4J\!'M$9;9*0BtUGPaL$Asc?`s[H]ic59*
+!nB#t5f&Yg`ns&8LsC'#,Za)[f6G2HPDgrMpG6#`aToN?=+C;O-@gD+~>
+endstream
+endobj
+% PDF Font (F301)
+% FullName (Verdana_Italic)
+% FamilyName (Verdana)
+% Font Version (Version 5.33)
+% Notice (© 2016 Microsoft Corporation. All Rights Reserved.)
+% Trademark (Verdana is a trademark of the Microsoft group of companies.)
+25     0 obj
+<<
+/Type /Font
+/Subtype /TrueType
+/BaseFont /Verdana-Italic
+/FirstChar 30
+/LastChar 255
+/Encoding /WinAnsiEncoding
+/Widths [ 1000 1000 352 394 459 818 636 1076 727 269 454 454 636 818 364 454 
+364 454 636 636 636 636 636 636 636 636 636 636 454 454 818 818 
+818 545 1000 683 686 698 766 632 575 775 751 421 455 693 557 843 
+748 787 603 787 695 684 616 732 683 990 685 615 685 454 454 454 
+818 636 636 601 623 521 623 596 352 622 633 274 344 587 274 973 
+633 607 623 623 427 521 394 633 591 818 592 591 525 635 454 635 
+818 1000 636 1000 269 636 459 818 636 636 636 1519 684 454 1070 1000 
+685 1000 1000 269 269 459 459 545 636 1000 636 977 521 454 980 1000 
+525 615 352 394 636 636 636 636 454 636 636 1000 545 645 818 454 
+1000 636 542 818 542 542 636 642 636 364 636 542 545 645 1000 1000 
+1000 545 683 683 683 683 683 683 989 698 632 632 632 632 421 421 
+421 421 766 748 787 787 787 787 787 818 787 732 732 732 732 615 
+605 620 601 601 601 601 601 601 955 521 596 596 596 596 274 274 
+274 274 612 633 607 607 607 607 607 818 607 633 633 633 633 591 
+623 591 ] 
+/FontDescriptor 26     0 R
+>>
+endobj
+26     0 obj
+<<
+/Type /FontDescriptor
+/Ascent 765
+/CapHeight 0
+/Descent -207
+/Flags 106
+/FontBBox [-453 -303 1585 1051]
+/FontName /Verdana_Italic
+/ItalicAngle -13
+/StemV  0
+>>
+endobj
+% PDF Font (F309)
+% FullName (Interleaved_2of5_Text)
+% FamilyName (I2OF5TXT)
+% Font Version (Fontlab V2.5 8/17/98)
+% Notice (Copyright 1998 by Chaos Microsystems Inc., All rights reserved.)
+27     0 obj
+<<
+/Type /Font
+/Subtype /TrueType
+/BaseFont /Interleaved2of5Text
+/FirstChar 30
+/LastChar 241
+/Encoding /WinAnsiEncoding
+/Widths [ 742 742 720 742 742 742 742 742 742 742 160 240 742 742 742 742 
+742 742 720 720 720 720 720 720 720 720 720 720 720 720 720 720 
+720 720 720 720 720 720 720 720 720 720 720 720 720 720 720 720 
+720 720 720 720 720 720 720 720 720 720 720 720 720 720 720 720 
+720 720 720 720 742 742 742 742 742 742 742 742 742 742 742 742 
+742 742 742 742 742 742 742 742 742 742 742 742 742 742 742 742 
+742 742 742 742 742 742 742 742 742 742 742 742 742 742 742 742 
+742 742 742 742 742 742 742 742 742 742 742 742 742 742 742 742 
+742 742 742 742 742 742 742 742 742 742 742 742 742 742 742 742 
+742 742 742 742 742 742 742 742 742 742 742 742 742 742 742 742 
+742 742 720 720 720 720 720 720 720 720 720 720 720 720 720 720 
+720 720 720 720 720 720 720 720 720 720 720 720 720 720 720 720 
+720 720 720 720 720 720 720 720 720 720 720 720 720 720 720 720 
+720 720 720 720 ] 
+/FontDescriptor 28     0 R
+>>
+endobj
+28     0 obj
+<<
+/Type /FontDescriptor
+/Ascent 0
+/CapHeight 0
+/Descent 0
+/Flags 42
+/FontBBox [0 -250 720 688]
+/FontName /Interleaved_2of5_Text
+/ItalicAngle 0
+/StemV  0
+>>
+endobj
+4 0 obj
+<</Type/Encoding/BaseEncoding/WinAnsiEncoding/Differences[
+32 /space/exclam/quotedbl/numbersign/dollar/percent/ampersand/quotesingle/parenleft/parenright/asterisk/plus/comma/hyphen/period/slash
+/zero/one/two/three/four/five/six/seven/eight/nine/colon/semicolon/less/equal/greater/question/at/A/B/C/D/E/F/G/H/I/J/K/L/M/N/O/P
+/Q/R/S/T/U/V/W/X/Y/Z/bracketleft/backslash/bracketright/asciicircum/underscore/grave/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w
+/x/y/z/braceleft/bar/braceright/asciitilde/.notdef/Euro/.notdef/quotesinglbase/florin/quotedblbase/ellipsis/dagger/daggerdbl/circumflex
+/perthousand/Scaron/guilsinglleft/OE/.notdef/Zcaron/.notdef/.notdef/quoteleft/quoteright/quotedblleft/quotedblright/bullet/endash
+/emdash/tilde/trademark/scaron/guilsinglright/oe/.notdef/zcaron/Ydieresis/space/exclamdown/cent/sterling/currency/yen/brokenbar/section
+/dieresis/copyright/ordfeminine/guillemotleft/logicalnot/hyphen/registered/macron/degree/plusminus/twosuperior/threesuperior/acute
+/mu/paragraph/periodcentered/cedilla/onesuperior/ordmasculine/guillemotright/onequarter/onehalf/threequarters/questiondown/Agrave
+/Aacute/Acircumflex/Atilde/Adieresis/Aring/AE/Ccedilla/Egrave/Eacute/Ecircumflex/Edieresis/Igrave/Iacute/Icircumflex/Idieresis/Eth
+/Ntilde/Ograve/Oacute/Ocircumflex/Otilde/Odieresis/multiply/Oslash/Ugrave/Uacute/Ucircumflex/Udieresis/Yacute/Thorn/germandbls/agrave
+/aacute/acircumflex/atilde/adieresis/aring/ae/ccedilla/egrave/eacute/ecircumflex/edieresis/igrave/iacute/icircumflex/idieresis/eth
+/ntilde/ograve/oacute/ocircumflex/otilde/odieresis/divide/oslash/ugrave/uacute/ucircumflex/udieresis/yacute/thorn/ydieresis]
+>>
+endobj
+31 0 obj
+<</Length 2696/Filter/FlateDecode>>stream
+xÚÅYl×ß/~4
#ë@h
¯´Œ9ö{ï~Gç&$a±Ö¦|1ÆÎÙ;;II»zZ±i뢭ÿŒÒN4Q‹6èDêÒ@§ÑI›´u%hPP+cŠø%Šs{ç;ÿ8ß%ظҌpÞ÷ü}ï}îó¾¿Þ{�:1
 ö/ÝØÚÊ V(sûÊ\ŠrRä|Ý�104ðùÁ·›7@ÁãÀ·”5øò{±€âœŒ¦¡õ¢)€)JÐûEäx¤7)Áî8' @wD!í9 ƒœ;1Ä<‚ *)Áˆ_’DNHiiP¨¬àVzC1½ï90hƒ>Çã®m­nmcùÔÔ�a¢K¦N
Ä°,DÓr{ê\ÞMžF_ê9â)ãy•èW¤XLªkj[ë[Ûš›;::Š�A¾´aS]02Fè³†¨èbzΐ.r†N‰i]Qÿ‘µïL‰¼!ÉæitòâhãA»ŽŠ1DÉ<¯Ñ;=X\¡½²ß¬,šÍëk¼>›¦cƒŽÃ<†öoÜgVÎ#+Ù<ìqdØó²}g]Äi1j~ãïQ4£+pŸ26ý«Û¼PyC…ÌHºug€qÐÖÜ°iê‚\\3­‹Wû
0
¹[ÑŠNO£;|:èo@¥©Æ‚¦‚�]Ðì’a3Ó#¯hցQᎨ-²Öu£¨HŠk@Ã3qE푘$wE”�ðÈò6IQ$¬O>%"û·IÁÃÁè´ýA©Ô³ì#žs:Ç4Må«r(ÕklÀk„�Í:‰ÖÆ`4[!Æ»@K¤+Kñ8hîUbÕ€(’"mHè‚%BÄB.ƒ¸3ð2¾B3؁uÿ¡Yœ¯W>$ðœ-…Ô(ôF	Pë-–7ÚðV.ˆrycJàµòÆaËåóƃg„\ÞØ4o3qµzþû‚ì=	ä,É0³¹¢".—ANCØ.õä荋þXP€zqÛòVx¬ñÝ,<¾xÇäÈg	Lñ†%g]¤Çå&ž;#{ÈY¶1¿¢Ð±|®Û¢Ÿ
<^°>„Š‚Gçz-Êx-+^vïˆIÅòFV–Ú¯êýEdˆv¹8ˆ››ÓÎø…7Žµšº~‡"5b8&ÅfXWlŽ)CçyEF¯ p4§/¬/®˜æ¡“fœ˜!¶ÜR\Ç«uÄN£ÈDY�Q,²F‘"QŒ­¥‘
+F[Qßö^IÞ+ÀÙpEú—
+([h÷ik¸0[+"£g±FE¬Ö¢m\’c`VôÊRH$	"µÅÛ÷‹²4¶i¹˜Ò)“áÒå¡à¤!vp´±3‚ùšÁÂ,“ët	э±YYÞÁ[V–)
+¦²´5‹JX-¢Ôœ2–ú‚!ҐµÄšñ	~ŸH}6Ï$V”EWTn ™5—=¶öx—…ÚÂ^qø(Sîb>W|Xã/	dL8•#ܽJ€ÍZ1ÒÒ+kÿCR@ó@¶hÔ/†GÛÅ»âÀ!Ó⦪¦µ‘i·h[BGä."´J¤ÀË>*„ÂÌ#¶IÿÅ€XÁb€©¶^	jnÄs›tQèHãKC‡MµI©%€PXX.!OçnuÑR©ê©)	„%°bÙ„÷vI±_ŠÄfi¥‘JÉ	‚ÅKî§L¬e
+	Ðtþ®1£W6&wk„"Œbx‡P<ÄdÓ‡ÇO’ÇŽ@X$Ù†`XR⹉Cûā¡%qd÷´ÙÄ‘Q+k¤`µEЋJ$°ýŸà0ÆÎ߉êŠÖ”¤Tª1(Ë!…$]™†™­W8'õ×l½RRš¹WQ|ODÚ¹¶AœSÈ©=t_ˆ žÂjûÈôR ¨D�'‹ƒŽ›CŽbÁš¨ŠB@›v÷¸„
*¶çæ\¾âb*_ŠË­01U<›
+QÖÓ\L…™º¶I»b´5&Å¥7!›°ž¤$I1	vãJŸ±Ï¿¬ƒ1Ü’Gùz
+¨ààÅMƒ‹³D–¶‰3'Y±
‹oÓך®2‡èy׊Yì6ߺL{Än¾ÿÀ”ù°ß|i“¹v›ïŠd³˜,¦‹ŒýÈ’ùF"O9o^¿ÝD…Ì”"mO‡˜ÍéÚXЯOé!(¤·ÃÀRtZó8£¡µm40ŸÕ mC# C™.4y‚uR»ïUªµ=—_
+ǺDRšËŽ¬Ç:¦yeF‹RN–e9݈eô»ˆz1ž’½¼œ~<Bˆõ¦Ù’LJr}Ë€hXßæõw[³p¤µóäö¶Öú5
žÑ)’´ÁHAHBˆ,|ªmO’啐Àê´¢Lq4ü\ Ð3}¡�A(ŒG§kì5dF²öIÝŠnxTGƒy;²i	�À°À§ËADç±Ãkj\iáŠXchmJÀ©ÒÚ!ƒ-kAcD!,#ËŒ‘X+ÅÓxëðIa©;"W“¥ã)*]pk³ð}�€6f6äÃaò…ȌĈéoí!¢Û\›RJ®5š‚˽¾ àªóWp5’Vùª_V®t5ƒ*í§H¸·GŽéÚ#ý1ÒoÕ*੏îüú_þç{5Ã}¼÷­Iïº÷::§†T»Ï'@9Щ‚“âìŠ
+ìø.\=5—>1»f|òÕ…§jž{¥óß++`íG»Æ+>üj֏ïü†ÓÓ|á	úzåÜÊ]_©ygç’Spi|β“³:Üѱ֪ys^½¸¨v¸bàé
W¬Ù¾àý#•û_›#>ñÛO÷ïYøù‹ûÞ¸t¦ÿÀ+Ozß}û±k7ÞzþýÏ;·odÀ“¬8ñÍüô“«n\ÿåX[ë´¼{÷Åùƒ©fϹƒw.Ä++~°|÷Ã_Z>ëcºíõDÿˆçÒ÷_ºtýkàÝÉÅÂ'‚ºyìðg䡵¢CÛè¨øÀ™Ë?Ù}{³úü¢Ó¡U¿þàÏýìË	õZxàÓ¡ÉA<öìS5Çvú]]Ùc€Þ´¹rüôÂKËÏ+~;1¸mÎ_~zláÔÄ]WòJå…ù·ÕêÝ%/_x=9qY½z»ª_=¤N&Ô	õìÇw€¨NM‘¿üfHwGUÇ“‹ߥ=N$‰]§#jÒu~òéòYɉdâàG·&&<98Þt§S]úÎØÄõªÚû§¿H.Kvª‡ÎoÛ¹´õ᫉ˉϦN_Û”LÈݪŽÞü`ôÚ¨z¾¾zõ‡®“³Ž¿²ìPrD]òÖañÙxtìèUýóïSÍKï¼™<sñ˜*—/SÕ->õJâÚNߘúWbìø–-êØgÏ_íÙ0u¶óÂ`d‰ÿÁŸ‡¶Ý
+%‡ïÒWª^�/ýªü5õìSU³Þ[¾|žø¦HŸ¿:zÑ]^_7wŽÓ‹|'Ê—«â3ûvv­k|¤|Aíîòoí{3:±gϏÎn=R¾ ¥¾~wÙ£_¨ø"hð€ï€ÿipòÔ
+endstream
+endobj
+32 0 obj
+<</Type/Page/Parent 33 0 R/Contents 31 0 R/Resources 5 0 R>>
+endobj
+35 0 obj
+<</Length 2203/Filter/FlateDecode>>stream
+xÚ­YKoÜ8¾çWÔe€™EG–¨WË·5ò°a$YÄöžr¡Gl™[jìÎîþú-Š’ÚdÓšvv�ÅbñãWOÊG$ƒØü›þÜ»|úï®îß]|"1¤i”âÜo ÉÈ3¸¯á÷Û‡8Žá¸†wïOWeITE9­JII†u¨î/‡eIl65ïÒ$ŠÓˆÄ$&Ilj;^³NF@ì‚u9¾«¸ªª7lNìæר·pÏ6’Ù­’õ¨r_Y-|ù‘–±¬{d²†jyWƒt“YºŒ$âû·¯®?Þ|yå3dðÓn›T¶@RRÙgw'ü–Qn¸4`3i\–á¾Óý^«VòCC
+›^BkÞCçI”ÃÚz
vLòÙ‚x&ÞÑY¸’ûVÙµ]ó:w'pRçÓHØÈ@oMRCi‡“áZ;œì%†á$Kíd^ʇád–ÎÝÆâN’rrïU>™»ï¸zR¦G¿×®0ug½µãñ‹‰Ž‹Ã݉Äá\a,Y0Ž™½›q^l‡dîÜÿH³Ü
+”ÿ7dÅ4{åÊSÕºH6vHf`et7âlpç“è"&³äÛ°$“Ì�<אîpãzì«ð\ß™³š·Ü¦®<áÞÅ)qr×$sm­ìl^[»ÑÏÎΤÊlÁø[–ä8,0!Á×¾EêãȈõ’¸f
ƒ¿¥i
·{©.þŒ‡Ï¦È!EœZ¿Š'
+PÏj\OøH9ʝ1¶åáŸT29BüζÔ+ß{…O=>}¦p/û®~b|{qÚ:©%Uºª,Ĭ ¿q]Ä/!–âÇiI_ ºéº'&%«Õ¡{>²^•ÿ#>tIŸÂ+*ÙŸ{ö$-û{¾¤¬&ÿ*¾2µ&¾çân©h\*9ƒkª;`SpM;$òu4ÇWù&KÏ�sšƒŠ1$îÜ ¢n|ŠÅìÝÂÝÙnl/§3oV¹™ÚK@ȃ_B©ð¯h!q¶>ò’¬£ªÌVEq¾}‹8Y芳*[ìK.\áÝbAêƒ,¹y¹rïÔÑ<|îh‚†%ŠÇPÂí|Þlì¤^Qå¹r»÷¸^©oC¾HŠð	Â]Ã[˲Û5$ø69Ì…„³L¶Ê’óÝ0ÏËP·³L
5Ë‹=+)ÃíÀÖí<ª=aÏ-<ƒ»6ô¼°	:¥ëgÄíïO›N?;¥yzb–¹¾Ï¯ÎÊ·Ùxý»éðþ´¥ê¹ï ß‚dfòb'û7ïð¹ß¶½Òï¬bóeËž}neÝ«×Î\̎׬óQ™Šòâb•-^¬ò°IEГÖáµ<dá9J­«tòü¸°–u)ï:tç&¢ð]*
wž¿¥,›Ò?»úrñêÜ:è©£'.ÜKIñ+e²	^­¦#—˜iV¥{ü_o½In›²[칸¶î>¦À2Ÿƒ—Ào#Ç^8þ¼¡~Ow«¤Š²8Y‘âô³ÈðE7ߢg¡I‡çñ³ÈK	<í,Ï'¤(דÄðü‰d–Hó£DÀAò¬œ%Ìó©Dºž‘ω´8Jàsø#P–Gëù#J&èXC?=6Ÿn›—9>8§Ög×Ûçèqêõ©ÓÒÌÑà
Gö
+µ•ó°u5«P!š³ƒÕœ­ÃûÎ"Íݲ[¸ˆÛãÍCïŒNåbëjf.]ní¥®æ.ô鄤Kt͹6<›ü!3Náö54è$¯ÐuJ¯}»š—/;É2Û:œÃÂùW³G¾p3èÞEåíôŸä¼ã{Þ¥‚ßn²°;yÂ^´öAw*ÎòomãôÑUåÐãª]Œµƒ›ŽÚPë2ÇuÐúóF^*[6™]B_3J½HÕüž$Eïó<´ww.»^J¦‹±¸w³BØÓ¥ð4’…Aª`Kñ–P{-L£óZZ¬ZIYUc£Ý©v_ת¡’všŸ×²ۖܨ஘Ôø¨¸ùJS3¡iײ¾>|lÔkÖ¶LÖÕFTs½Í–ÁMO­~™>}Pôoì‚¢óÄ~”ø‚ÚÛ ª-ë˜:7’âfü@õŽIeö^™«Ó´µãõ¢o€×Œ	Á —
í¸¢öÁ·Í›5Áf¤6¼£âƒ$ï”æzoalçБ3KۏOµ0 +f§>¶ŠmwÂp`²ý‚>…·"üÐb^¤ÛÃ(ÞJ|«æsíXgtÌ/<\&k¾£;.ç/_u5ô‘ˆà¦«ù×ûÚ¤²—õ‘±¶Ç©Vóýlx×ìTÿÞ²xÍÛàI˜~ƒÁ³Üb¾bÂtP;Õ Éz›e/àZ-íÀˆ¶Æ
&DGÞš«Ç»äì!†fôCQ£¼B™B¦ü žØàߣ-¨<¬P’µåJ1ŸÕ†ØëG¤UçÇ%T4@ðØ£bKíÕRͳ*qk4o-isÄf"
+È\§­šg&j0ç±!·B‡Ú0¤Ø³m¨ÐVŠÙìñû€-æA÷‰J¼~k»F¡±`Û㥱®†“@ÅA±#ªpZ˜àx˜:8(÷rÍä=.‡½ÆàjÔnßµz áyˆ]ƒ½ƒ}Ë<„ãîzVŒkÜGÁïãÑQ?BØ^èk8¨ùÈ“áE™ÚwÆüýÐ@k<WZÁþ·Äë…ýv(bêl;˜Üø\ÍÀØF¡U;ä3ìÐ]Z¶D˜
$zLk£Îª�nòïuU~™äeòã,7£÷ÎôðùdRîímSϘDÊ1xvH,Ò‡7“ÁSò6>›ÁY1Êñ.mþ^ÛËÞf|e�:çœN²Â3àšlÛ–#Ö#¢î<i½S—?þŒ,¯L3\WG]q´ÔÊ<N
+endstream
+endobj
+36 0 obj
+<</Type/Page/Parent 33 0 R/Contents 35 0 R/Resources 5 0 R>>
+endobj
+33 0 obj
+<</Type/Pages/Count 2/Kids[32 0 R 36 0 R]/MediaBox[0 0 595 842]/Resources 5 0 R>>
+endobj
+5 0 obj
+<</ProcSet[/PDF/ImageB/ImageC/Text]/Font<</F3 6 0 R 
+/F7 7 0 R /F20 8 0 R /F26 9 0 R /F41 10 0 R /F53 11 0 R /F56 12 0 R /F73 13 0 R /F78 14 0 R /F79 15 0 R /F93 16 0 R 
+/F102 18 0 R /F178 20 0 R /F211 21 0 R /F297 22 0 R /F301 25 0 R /F309 27 0 R >>
+>>
+endobj
+29 0 obj
+<</Title (74586: )/Dest[32 0 R/XYZ 999 999 0]/Parent 3 0 R/First 30 0 R/Last 30 0 R/Count -1>>
+
+endobj
+30 0 obj
+<</Title (KU000_Kontoutskrift_AlleTyper)/Dest[32 0 R/XYZ 999 999 0]/Parent 29 0 R/First 34 0 R/Last 37 0 R/Count -2>>
+
+endobj
+34 0 obj
+<</Title (1: KU000_Kontoutskrift_AlleTyper)/Dest[32 0 R/XYZ 999 999 0]/Parent 30 0 R/Next 37 0 R>>
+
+endobj
+37 0 obj
+<</Title (2: MultiC_Default_Flow_page)/Dest[36 0 R/XYZ 999 999 0]/Parent 30 0 R/Prev 34 0 R>>
+
+endobj
+3 0 obj
+<< /Count 1 /First 29 0 R /Last 29 0 R >>
+endobj
+2 0 obj
+<</Type/Catalog/Pages 33 0 R/Outlines 3 0 R/PageMode/UseOutlines>>
+endobj
+xref
+0 38
+0000000000 65535 f 
+0000000015 00000 n 
+0000015975 00000 n 
+0000015918 00000 n 
+0000008229 00000 n 
+0000015175 00000 n 
+0000000202 00000 n 
+0000000320 00000 n 
+0000000433 00000 n 
+0000000550 00000 n 
+0000000666 00000 n 
+0000000785 00000 n 
+0000000900 00000 n 
+0000001020 00000 n 
+0000001144 00000 n 
+0000001264 00000 n 
+0000001590 00000 n 
+0000002697 00000 n 
+0000003103 00000 n 
+0000004218 00000 n 
+0000004413 00000 n 
+0000004542 00000 n 
+0000004666 00000 n 
+0000004778 00000 n 
+0000004872 00000 n 
+0000005521 00000 n 
+0000006635 00000 n 
+0000007008 00000 n 
+0000008052 00000 n 
+0000015444 00000 n 
+0000015556 00000 n 
+0000009886 00000 n 
+0000012651 00000 n 
+0000015077 00000 n 
+0000015691 00000 n 
+0000012728 00000 n 
+0000015000 00000 n 
+0000015807 00000 n 
+trailer<</Size 38/Info 1 0 R/Root 2 0 R
+>>
+startxref
+16057
+%%EOF
diff --git a/tests/e2e/fixtures/pigrich.png b/tests/e2e/fixtures/pigrich.png
new file mode 100644
index 0000000000000000000000000000000000000000..741e81e9e1a202b329190eb1f5c6fe47e5d63150
Binary files /dev/null and b/tests/e2e/fixtures/pigrich.png differ
diff --git a/tests/e2e/specs/budget/budgetMain.cy.js b/tests/e2e/specs/budget/budgetMain.cy.js
new file mode 100644
index 0000000000000000000000000000000000000000..fd129602bd8bbfd3d7bbe8e19a5000e8c6415c5d
--- /dev/null
+++ b/tests/e2e/specs/budget/budgetMain.cy.js
@@ -0,0 +1,42 @@
+describe('Budget Component', () => {
+    beforeEach(() => {
+        cy.login('admin@example.com', 'password');
+        cy.visit('http://localhost:8082/budget');
+    });
+
+    it('displays budget information correctly', () => {
+        // Assert that the budget main container is visible
+        cy.get('[data-cy=budget-component]').should('be.visible')
+
+        // Assert that selectors are present
+        cy.get('[data-cy=account-dropdown]').should('be.visible')
+        cy.get('[data-cy=date-picker]').should('be.visible')
+
+        // Assert that budget information is displayed correctly
+        cy.get('[data-cy=income-section]').should('be.visible')
+        cy.get('[data-cy=expenses-section]').should('be.visible')
+
+        // Assert that the monthly spending chart is visible
+        cy.get('[data-cy=monthly-spending-chart]').should('be.visible')
+    })
+
+    it('should be present and can be interacted with', () => {
+        // Select a specific month and year
+        cy.get('[data-cy=date-picker]')
+            .type('2022-05-01')
+            .should('have.value', '2022-05-01');
+    });
+
+    it('should display graph and edit button when a budget exists', () => {
+        // Select a specific month and year
+        cy.get('[data-cy=date-picker]')
+        .type('2024-03-01')
+        .should('have.value', '2024-03-01');
+
+        // Assert that the spent chart is visible
+        cy.get('[data-cy=spent-chart]').should('be.visible')
+
+        // Assert that the edit button is visible
+        cy.get('[data-cy=edit-button]').should('be.visible')
+    });    
+});
diff --git a/tests/e2e/specs/contact/contact.cy.js b/tests/e2e/specs/contact/contact.cy.js
new file mode 100644
index 0000000000000000000000000000000000000000..19015e0a326dc2c96cf4ed476ab2cc834e525920
--- /dev/null
+++ b/tests/e2e/specs/contact/contact.cy.js
@@ -0,0 +1,63 @@
+describe('Contact Component', () => {
+    beforeEach(() => {
+        cy.visit('http://localhost:8082/contact');
+    });
+
+    it('displays the form correctly', () => {
+        // Ensure the form title is displayed
+        cy.contains('h1', 'Kontaktskjema').should('exist');
+
+        // Ensure the form elements are displayed
+        cy.get('form').within(() => {
+            cy.get('label[for="selectedOptionSubject"]').should('exist');
+            cy.get('select#selectedOptionSubject').should('exist');
+
+            cy.get('label[for="name"]').should('exist');
+            cy.get('input#name').should('exist').and('have.attr', 'placeholder', 'Fornavn');
+
+            cy.get('label[for="email"]').should('exist');
+            cy.get('input#email').should('exist').and('have.attr', 'placeholder', 'E-postadresse');
+
+            cy.get('label[for="feedback"]').should('exist');
+            cy.get('textarea#feedback').should('exist').and('have.attr', 'placeholder', 'Melding');
+
+            cy.get('button').should('exist').and('have.text', 'Send inn!');
+        });
+    });
+
+    it('displays error messages for empty data', () => {
+        // Submit the form without filling in any data
+        cy.get('select#selectedOptionSubject').select('Feil og problemer');
+        cy.get('[data-cy=submit-button]').click({ force: true });
+
+        cy.contains('.confirmation-or-error-message', 'Navn er påkrevd.').should('exist');
+    });
+
+    it('displays error message for invalid email', () => {
+        // Fill in form fields with invalid email
+        cy.get('select#selectedOptionSubject').select('Feil og problemer');
+        cy.get('input#name').type('John');
+        cy.get('input#email').type('invalid_email');
+        cy.get('textarea#feedback').type('This is a test feedback.');
+
+        // Submit the form
+        cy.get('[data-cy=submit-button]').click({ force: true });
+
+        // Ensure an error message is displayed
+        cy.contains('.confirmation-or-error-message', 'E-postadressen må inneholde @.').should('exist');
+    });
+
+    it('submits the form correctly with valid data', () => {
+        // Fill in form fields with valid data
+        cy.get('select#selectedOptionSubject').select('Feil og problemer');
+        cy.get('input#name').type('John');
+        cy.get('input#email').type('john.doe@example.com');
+        cy.get('textarea#feedback').type('This is a test feedback.');
+
+        // Submit the form
+        cy.get('[data-cy=submit-button]').click({ force: true });
+
+        // Ensure a success message is displayed
+        cy.contains('.confirmation-or-error-message', 'Meldingen ble sendt!').should('exist');
+    });
+});
diff --git a/tests/e2e/specs/goals/goal.cy.js b/tests/e2e/specs/goals/goal.cy.js
new file mode 100644
index 0000000000000000000000000000000000000000..804cc0bbe7eec65af4627bd1132814d2af8a40b4
--- /dev/null
+++ b/tests/e2e/specs/goals/goal.cy.js
@@ -0,0 +1,91 @@
+describe('Goal and Challenge components', () => {
+    beforeEach(() => {
+        cy.login('admin@example.com', 'password');
+        cy.visit('http://localhost:8082/goals');
+    })
+
+    it('Checks if games work', () => {
+
+        cy.get('[data-cy=add-goal-button]').should('exist');
+        cy.get('[data-cy=overview-goals-button]').should('exist');
+
+        cy.get('[data-cy=add-goal-button]').click();
+
+        cy.get('[data-cy=start-challenge-button]').should('exist');
+        cy.get('[data-cy=start-goal-button]').should('exist');
+
+        cy.get('[data-cy=start-goal-button]').click();
+
+        cy.get('[data-cy=goal-title-input]').should('exist');
+        cy.get('[data-cy=goal-amount-input]').should('exist');
+        cy.get('[data-cy=start-date-input]').should('exist');
+        cy.get('[data-cy=end-date-input]').should('exist');
+
+        cy.get('[data-cy=goal-title-input]').type('My Savings Goal');
+        cy.get('[data-cy=goal-amount-input]').type('1000');
+        cy.get('[data-cy=start-date-input]').type('2024-05-05');
+        cy.get('[data-cy=end-date-input]').type('2025-05-05');
+
+        cy.get('[data-cy=create-goal-button]').click();
+
+        cy.get('[data-cy=input-contribution]').should('exist');
+        cy.get('[data-cy=add-button-contribution]').should('exist');
+
+        cy.get('[data-cy=input-contribution]').type('1000');
+
+        cy.get('[data-cy=add-button-contribution]').click();
+
+        cy.get('.modal-overlay').should('exist');
+
+        cy.get('.modalFinished p').should('contain', 'Gratulerer, du klarte målet ditt!');
+
+        cy.get('.modal-overlay button').should('exist');
+
+        cy.get('.modalFinished button').click();
+
+        cy.get('.button-container .button-box button').should('have.length', 2).each(($button) => {
+            expect($button.text().trim()).to.not.be.empty;
+        });
+
+        cy.contains('.button-container .button-box button', 'Legg til sparemål og utfordringer').click();
+
+        cy.get('[data-cy=start-challenge-button]').should('exist');
+
+        cy.get('[data-cy=start-challenge-button]').click();
+
+        cy.get('.button-box button').should('have.length', 3);
+
+        cy.get('.button-box button').eq(1).click();
+
+        cy.get('.container button').should('have.length', 2);
+
+        cy.contains('.container button', 'GÃ¥ tilbake').click();
+
+        cy.get('.button-box button').should('have.length', 3);
+
+        cy.get('.button-box button').eq(0).click();
+
+        cy.get('#title').should('exist');
+        cy.get('.container .input-field-container input').should('have.length', 2);
+        cy.get('.date-container input').should('have.length', 2);
+        cy.get('#create-goal-button').should('exist');
+
+        cy.get('.container .input-field-container input').eq(0).type('My Challenge Title');
+        cy.get('.container .input-field-container input').eq(1).type('My Challenge Description');
+        cy.get('.date-container input').eq(0).type('2024-05-05');
+        cy.get('.date-container input').eq(1).type('2025-05-05');
+
+        cy.get('#create-goal-button').click();
+
+        cy.contains('button', 'GÃ¥ tilbake').should('exist');
+        cy.contains('button', 'Jeg har feilet på utfordringen').should('exist');
+
+        cy.get('.main-title').should('not.be.empty');
+
+        cy.get('.description').should('not.be.empty');
+
+        cy.get('.start-date').should('not.be.empty');
+
+        cy.get('.end-date').should('not.be.empty');
+    });
+})
\ No newline at end of file
diff --git a/tests/e2e/specs/home/homeMain.cy.js b/tests/e2e/specs/home/homeMain.cy.js
new file mode 100644
index 0000000000000000000000000000000000000000..fdf0ab4bac5cac8e5d186385a1b3f81da26f900f
--- /dev/null
+++ b/tests/e2e/specs/home/homeMain.cy.js
@@ -0,0 +1,24 @@
+describe('Home Main Component', () => {
+    beforeEach(() => {
+        cy.login('admin@example.com', 'password');
+    });
+
+    it('displays the heading correctly', () => {
+        cy.contains('h1', 'Bli rik med Sparesti!').should('exist');
+    });
+
+    it('displays the image correctly', () => {
+        cy.get('.home-container img').should('exist');
+    });
+
+    it('displays the button correctly and navigates to the Goals page when clicked', () => {
+        // Check if the button exists
+        cy.contains('button', 'Lag nytt sparemål').should('exist');
+
+        // Click on the button
+        cy.contains('button', 'Lag nytt sparemål').click({ force: true });
+
+        // Ensure the correct route is navigated to
+        cy.url().should('include', '/goals');
+    });
+});
diff --git a/tests/e2e/specs/login/login.cy.js b/tests/e2e/specs/login/login.cy.js
new file mode 100644
index 0000000000000000000000000000000000000000..65337cd99500d580512d48400a253e4ec91127c1
--- /dev/null
+++ b/tests/e2e/specs/login/login.cy.js
@@ -0,0 +1,45 @@
+describe('Login Component', () => {
+    beforeEach(() => {
+        cy.visit('http://localhost:8082/login');
+    })
+
+    it('displays the login form correctly', () => {
+        cy.get('form').should('exist')
+        cy.get('input[type="email"]').should('exist')
+        cy.get('input[type="password"]').should('exist')
+        cy.get('#login-button').should('contain.text', 'Logg inn')
+        cy.get('.forgot-password-link').should('contain.text', 'Glemt passord?')
+        cy.get('.status-message').should('not.exist')
+    })
+
+    it('displays error message for invalid login attempt', () => {
+        cy.get('input[type="email"]').type('invalid@example.com')
+        cy.get('input[type="password"]').type('invalidpassword')
+        cy.get('#login-button').click()
+        cy.contains('.status-message', 'E-post eller passord var feil.').should('exist')
+    })
+
+    it('disables login button when any field is empty', () => {
+        // Check if login button is disabled when both fields are empty
+        cy.get('#login-button').should('be.disabled')
+        
+        // Fill in valid email but leave password field empty
+        cy.get('input[type="email"]').type('valid@example.com')
+        cy.get('#login-button').should('be.disabled')
+        
+        // Fill in password but leave email field empty
+        cy.get('input[type="email"]').clear()
+        cy.get('input[type="password"]').type('validpassword')
+        cy.get('#login-button').should('be.disabled')
+    })
+
+    it('successfully logs in with valid credentials', () => {
+        const validEmail = 'admin@example.com';
+        const validPassword = 'password';
+        cy.get('input[type="email"]').type(validEmail)
+        cy.get('input[type="password"]').type(validPassword)
+        cy.get('#login-button').click()
+
+        cy.get('.home-container').should('exist');
+    })
+})
diff --git a/tests/e2e/specs/login/register.cy.js b/tests/e2e/specs/login/register.cy.js
new file mode 100644
index 0000000000000000000000000000000000000000..1da4d2ce44a608d9cc30fe2b07b8fc9b03e9d791
--- /dev/null
+++ b/tests/e2e/specs/login/register.cy.js
@@ -0,0 +1,85 @@
+describe('Register Component', () => {
+    beforeEach(() => {
+        cy.visit('http://localhost:8082/login');
+        cy.get('.navigation')
+        .contains('h5', 'Registrer bruker') 
+        .click();    
+    });
+
+    it('displays the registration form correctly', () => {
+        cy.get('form').should('exist');
+        cy.get('input[type="email"]').should('exist');
+        cy.get('input[type="text"]').should('have.length', 2);
+        cy.get('input[type="password"]').should('exist');
+        cy.get('[data-cy=submit-button]').should('contain.text', 'Fortsett');
+    });
+
+    it('disables submit button when any field is empty', () => {
+        // Check if submit button is disabled when all fields are empty
+        cy.get('[data-cy=submit-button]').should('be.disabled');
+        
+        // Fill in valid email but leave other fields empty
+        cy.get('input[type="email"]').type('valid@example.com');
+        cy.get('[data-cy=submit-button]').should('be.disabled');
+        
+        // Fill in all fields except password
+        cy.get('input[type="text"]').eq(0).type('John');
+        cy.get('input[type="text"]').eq(1).type('Doe');
+        cy.get('[data-cy=submit-button]').should('be.disabled');
+    });
+
+    it('displays error message for invalid email', () => {
+        // Test with invalid email
+        cy.get('input[type="text"]').eq(0).type('John');
+        cy.get('input[type="text"]').eq(1).type('Doe');
+        cy.get('input[type="email"]').type('invalid_email');
+        cy.get('input[type="password"]').type('Password123!');
+        cy.get('[data-cy=submit-button]').click();
+
+        cy.contains('.status-message', 'E-postadressen må inneholde @.').should('exist');
+    });
+
+    it('displays error message for invalid first name', () => {
+        // Test with invalid first name
+        cy.get('input[type="text"]').eq(0).type('John36');
+        cy.get('input[type="text"]').eq(1).type('Doe');
+        cy.get('input[type="email"]').type('mail@example.com');
+        cy.get('input[type="password"]').type('Password123!');
+        cy.get('[data-cy=submit-button]').click();
+
+        cy.contains('.status-message', 'Navnet kan bare inneholde bokstaver.').should('exist');
+    });
+
+    it('displays error message for invalid last name', () => {
+        // Test with invalid last name
+        cy.get('input[type="text"]').eq(0).type('John');
+        cy.get('input[type="text"]').eq(1).type('Doe36');
+        cy.get('input[type="email"]').type('mail@example.com');
+        cy.get('input[type="password"]').type('Password123!');
+        cy.get('[data-cy=submit-button]').click();
+
+        cy.contains('.status-message', 'Navnet kan bare inneholde bokstaver.').should('exist');
+    });    
+
+    it('displays error message for invalid password', () => {
+        // Test with invalid password
+        cy.get('input[type="text"]').eq(0).type('John');
+        cy.get('input[type="text"]').eq(1).type('Doe');
+        cy.get('input[type="email"]').type('mail@example.com');
+        cy.get('input[type="password"]').type('password');
+        cy.get('[data-cy=submit-button]').click();
+
+        cy.contains('.status-message', 'Passordet må inkludere stor bokstav, inkludere et tall, inkludere spesialtegn.').should('exist');
+    });    
+
+    it('displays error message for email already in use', () => {
+        // Test with email already in use
+        cy.get('input[type="email"]').type('admin@example.com');
+        cy.get('input[type="text"]').eq(0).type('John');
+        cy.get('input[type="text"]').eq(1).type('Doe');
+        cy.get('input[type="password"]').type('Password123');
+        cy.get('[data-cy=submit-button]').click();
+
+        cy.contains('.status-message', 'E-postadresse er allerede i bruk.').should('exist');
+    });    
+})
diff --git a/tests/e2e/specs/navigationBar.cy.js b/tests/e2e/specs/navigationBar.cy.js
new file mode 100644
index 0000000000000000000000000000000000000000..555f9d92b58ac1b5c07a7b5b6dfd5ee8b210e438
--- /dev/null
+++ b/tests/e2e/specs/navigationBar.cy.js
@@ -0,0 +1,51 @@
+describe('Navigation Bar', () => {
+    beforeEach(() => {
+        cy.login('admin@example.com', 'password');
+        cy.visit('http://localhost:8082/');
+    })
+
+    it('should load correctly', () => {
+        cy.get('[data-cy=navigation-component]').should('exist');
+    });
+
+    it('should toggle navigationbar', () => {
+        cy.get('[data-cy=navigation-button]').click();
+        cy.get('[data-cy=navigation-menu]').should('be.visible');
+    });
+    
+    it('should navigate to /transactions when "Transaksjoner" is clicked', () => {
+        cy.get('[data-cy=navigation-button]').click();
+        cy.contains('Transaksjoner').click();
+        cy.url().should('include', '/transactions');
+    });
+
+    it('should navigate to /budget when "Budsjett" is clicked', () => {
+        cy.get('[data-cy=navigation-button]').click();
+        cy.contains('Budsjett').click();
+        cy.url().should('include', '/budget');
+    });
+
+    it('should navigate to /goals when "Sparemål" is clicked', () => {
+        cy.get('[data-cy=navigation-button]').click();
+        cy.contains('Sparemål').click();
+        cy.url().should('include', '/goals');
+    });
+
+    it('should navigate to /profile when "Profil" is clicked', () => {
+        cy.get('[data-cy=navigation-button]').click();
+        cy.contains('Profil').click();
+        cy.url().should('include', '/profile');
+    });
+
+    it('should navigate to /news when "Nyheter" is clicked', () => {
+        cy.get('[data-cy=navigation-button]').click();
+        cy.contains('Nyheter').click();
+        cy.url().should('include', '/news');
+    });
+
+    it('should navigate to /contact when "Kontakt oss!" is clicked', () => {
+        cy.get('[data-cy=navigation-button]').click();
+        cy.contains('Kontakt oss!').click();
+        cy.url().should('include', '/contact');
+    });
+});
\ No newline at end of file
diff --git a/tests/e2e/specs/news/newsMain.cy.js b/tests/e2e/specs/news/newsMain.cy.js
new file mode 100644
index 0000000000000000000000000000000000000000..ac1b56deb2cd6707b900d3771ee4f1993d33e4f9
--- /dev/null
+++ b/tests/e2e/specs/news/newsMain.cy.js
@@ -0,0 +1,51 @@
+// newsComponent.spec.js
+describe('News Component Tests', () => {
+    beforeEach(() => {
+        cy.visit('http://localhost:8082/news');
+    });
+
+    it('successfully loads the initial news items', () => {
+        cy.wait(2000);
+        cy.get('[data-cy=news-container]').should('have.length.at.least', 1);
+    });
+
+    it('logs all options in the select dropdown', () => {
+        cy.wait(2000);
+        cy.get('[data-cy=category-dropdown]').then($select => {
+            // Ensure the select is visible and interactable
+            cy.wrap($select).should('be.visible');
+
+            // Log each option's text and value
+            cy.wrap($select).find('option').each(($option) => {
+                const text = $option.text();
+                const value = $option.val();
+                cy.log(`Option Text: ${text}, Value: ${value}`);
+            });
+        });
+    });
+
+    it('filters news items by category', () => {
+        cy.wait(2000);
+        cy.get('[data-cy=category-dropdown] option').then(options => {
+            // Select the first category from the dropdown
+            const category = [...options][1].text;
+            cy.get('[data-cy=category-dropdown]').select(category);
+
+            // Ensure all news items are of the selected category
+            cy.get('[data-cy=news-info]').each(($el) => {
+                cy.wrap($el).should('contain', category);
+            });
+        });
+    });
+
+    it('ensures news items have all required properties', () => {
+        cy.wait(2000);
+        // Ensure each news item has a link, title, info, and image
+        cy.get('[data-cy=news-container]').within(() => {
+            cy.get('[data-cy=news-link]').should('have.attr', 'href').and('include', 'http');
+            cy.get('[data-cy=news-title]').should('not.be.empty');
+            cy.get('[data-cy=news-info]').should('not.be.empty');
+            cy.get('img').should('have.attr', 'src').and('include', 'http');
+        });
+    });
+});
diff --git a/tests/e2e/specs/profile/bankStatements.cy.js b/tests/e2e/specs/profile/bankStatements.cy.js
new file mode 100644
index 0000000000000000000000000000000000000000..cd707bd81ee4cc94e54dc6fdbd123999ec944990
--- /dev/null
+++ b/tests/e2e/specs/profile/bankStatements.cy.js
@@ -0,0 +1,64 @@
+describe('Bank Statements Component', () => {
+  beforeEach(() => {
+    cy.login('admin@example.com', 'password');
+    cy.visit('http://localhost:8082/profile');
+    cy.get('[data-cy="bank-statements-link"]').click();
+  });
+
+  it('loads the initial page and displays the main container', () => {
+    // Check for main container visibility and title
+    cy.get('.main-container-bankStatements').should('be.visible');
+    cy.get('h1').contains('Kontoutskrifter');
+  });
+
+  it('logs all options in the select dropdown', () => {
+    cy.get('[data-cy="bank-select"]').then($select => {
+      // Ensure the select is visible and interactable
+      cy.wrap($select).should('be.visible');
+
+      // Log each option's text and value
+      cy.wrap($select).find('option').each(($option) => {
+        const text = $option.text();
+        const value = $option.val();
+        cy.log(`Option Text: ${text}, Value: ${value}`);
+      });
+    });
+  });
+
+  it('Should allow selecting a bank from the dropdown', () => {
+    // Select a bank from the dropdown
+    cy.get('[data-cy="bank-select"]').select('HANDELSBANKEN').should('have.value', 'HANDELSBANKEN');
+  });
+
+  it('allows a user to select a file', () => {
+    // Selecting a file
+    cy.get('[data-cy="file-input"]').attachFile('handelsbankenExample.pdf');
+  });
+
+  it('Should display error message for non-PDF file types', () => {
+    // Selecting a bank from the dropdown
+    cy.get('[data-cy="bank-select"]').select('HANDELSBANKEN').should('have.value', 'HANDELSBANKEN');
+
+    // Selecting a non-PDF file
+    cy.get('input[type="file"]').attachFile('pigrich.png');
+    cy.get('[data-cy="error-message"]').should('contain', 'Vennligst last opp en gyldig PDF-fil.');
+  });
+
+  it('should upload bank statement', () => {
+    // Intercept the POST request for adding a bank statement
+    cy.intercept('POST', 'http://localhost:8080/api/v1/bank-statements/?bankName=HANDELSBANKEN', {
+      statusCode: 200,
+      body: {
+        id: 1, 
+      }
+    }).as('uploadBankStatement');
+
+    // Mock selecting a bank
+    cy.get('[data-cy="bank-select"]').select('HANDELSBANKEN').should('have.value', 'HANDELSBANKEN');
+  
+    // Mock selecting and uploading a valid file
+    cy.get('[data-cy="file-input"]').attachFile('handelsbankenExample.pdf');
+  
+    cy.wait('@uploadBankStatement').its('response.statusCode').should('eq', 200);
+  });
+});
\ No newline at end of file
diff --git a/tests/e2e/specs/profile/myProfile.cy.js b/tests/e2e/specs/profile/myProfile.cy.js
new file mode 100644
index 0000000000000000000000000000000000000000..c2f8eead1f80961abf1635c5c4bd31d15f883c2a
--- /dev/null
+++ b/tests/e2e/specs/profile/myProfile.cy.js
@@ -0,0 +1,150 @@
+describe('My Profile Component', () => {
+    beforeEach(() => {
+        cy.login('admin@example.com', 'password');
+        cy.visit('http://localhost:8082/profile');
+        cy.get('[data-cy="my-profile-link"]').click();
+    })
+
+    it('loads correctly', () => {
+        cy.get('.my-profile-main-container').should('exist')
+    })
+    
+    it('displays user information correctly', () => {
+        cy.get('#firstName').should('have.value', 'Admin')
+        cy.get('#lastName').should('have.value', 'Admin')
+        cy.get('#email').should('have.value', 'admin@example.com')
+        cy.get('#income').should('have.value', '10000')
+        cy.get('#livingStatus').should('have.value', '1')
+      })
+
+    it('allows editing first name', () => {
+        // Check that input is disabled
+        cy.get('#firstName').should('be.disabled')
+
+        // Test changing first name
+        cy.get('[data-cy=edit-button]').click()
+        cy.get('#firstName').clear().type('Jane')
+        cy.get('[data-cy=edit-button]').click()
+
+        cy.get('[data-cy=user-status-message]').should('contain.text', 'Fornavn oppdatert.')
+        cy.get('#firstName').should('have.value', 'Jane')
+
+        // Reset the name
+        cy.get('[data-cy=edit-button]').click()
+        cy.get('#firstName').clear().type('Admin')
+        cy.get('[data-cy=edit-button]').click()
+
+        cy.get('[data-cy=user-status-message]').should('contain.text', 'Fornavn oppdatert.')
+        cy.get('#firstName').should('have.value', 'Admin')
+    })
+
+    it('allows editing last name', () => {
+        // Check that input is disabled
+        cy.get('#lastName').should('be.disabled')
+    
+        // Test changing last name
+        cy.get('[data-cy=edit-button]').click()
+        cy.get('#lastName').clear().type('Doe')
+        cy.get('[data-cy=edit-button]').click()
+    
+        cy.get('[data-cy=user-status-message]').should('contain.text', 'Etternavn oppdatert.')
+        cy.get('#lastName').should('have.value', 'Doe')
+    
+        // Reset the name
+        cy.get('[data-cy=edit-button]').click()
+        cy.get('#lastName').clear().type('Admin')
+        cy.get('[data-cy=edit-button]').click()
+    
+        cy.get('[data-cy=user-status-message]').should('contain.text', 'Etternavn oppdatert.')
+        cy.get('#lastName').should('have.value', 'Admin')
+    })
+    
+    it('allows editing income', () => {
+        // Check that input is disabled
+        cy.get('#income').should('be.disabled')
+    
+        // Test changing income
+        cy.get('[data-cy=edit-button]').click()
+        cy.get('#income').clear().type('70000')
+        cy.get('[data-cy=edit-button]').click()
+    
+        cy.get('[data-cy=user-status-message]').should('contain.text', 'Inntekt oppdatert.')
+        cy.get('#income').should('have.value', '70000')
+    
+        // Reset the income
+        cy.get('[data-cy=edit-button]').click()
+        cy.get('#income').clear().type('10000')
+        cy.get('[data-cy=edit-button]').click()
+    
+        cy.get('[data-cy=user-status-message]').should('contain.text', 'Inntekt oppdatert.')
+        cy.get('#income').should('have.value', '10000')
+    })
+    
+    it('allows editing living status', () => {
+        // Check that select is disabled
+        cy.get('#livingStatus').should('be.disabled')
+    
+        // Test changing living status
+        cy.get('[data-cy=edit-button]').click()
+        cy.get('#livingStatus').select('Par med barn')
+
+        cy.get('[data-cy=edit-button]').click()
+        cy.get('[data-cy=user-status-message]').should('contain.text', 'Bosituasjon oppdatert.')
+    
+        // Reset the living status
+        cy.get('[data-cy=edit-button]').click()
+        cy.get('#livingStatus').select('Bor alene')
+        cy.get('[data-cy=edit-button]').click()
+    
+        cy.get('[data-cy=user-status-message]').should('contain.text', 'Bosituasjon oppdatert.')
+    })
+
+    it('allows editing saving percentage', () => {
+        // Check that input is disabled
+        cy.get('#savingPercentage').should('be.disabled')
+    
+        // Test changing saving percentage
+        cy.get('[data-cy=edit-button]').click()
+        cy.get('#savingPercentage').clear().type('20')
+        cy.get('[data-cy=edit-button]').click()
+    
+        cy.get('[data-cy=user-status-message]').should('contain.text', 'Spareprosent oppdatert.')
+        cy.get('#savingPercentage').should('have.value', '20')
+    });    
+
+    it('displays error message if input is blank', () => {
+        // Test changing first name
+        cy.get('[data-cy=edit-button]').click()
+        cy.get('#firstName').clear()
+        cy.get('[data-cy=edit-button]').click()
+        cy.get('[data-cy=user-status-message]').should('contain.text', 'Fornavn kan ikke være tomt.')
+
+        // Test changing last name
+        cy.get('[data-cy=edit-button]').click()
+        cy.get('#lastName').clear()
+        cy.get('[data-cy=edit-button]').click()
+        cy.get('[data-cy=user-status-message]').should('contain.text', 'Etternavn kan ikke være tomt.')
+
+        // Test changing income
+        cy.get('[data-cy=edit-button]').click()
+        cy.get('#income').clear()
+        cy.get('[data-cy=edit-button]').click()
+        cy.get('[data-cy=user-status-message]').should('contain.text', 'Inntekt kan ikke være tom.')
+    });
+
+/*     it('allows changing password', () => {
+        // Test changing password
+        cy.get('#oldPassword').type('password')
+        cy.get('#newPassword').type('password123')
+        cy.get('#confirmPassword').type('password123')
+        cy.get('[data-cy=edit-password-button]').click({force: true});
+        cy.get('[data-cy=password-status-message]').should('contain.text', 'Passord endret.')
+
+        // Reset password
+        cy.get('#oldPassword').type('password123')
+        cy.get('#newPassword').type('password')
+        cy.get('#confirmPassword').type('password123')
+        cy.get('[data-cy=edit-password-button]').click({force: true});
+        cy.get('[data-cy=password-status-message]').should('contain.text', 'Passord endret.')
+    }) */
+})
diff --git a/tests/e2e/specs/profile/profileBar.cy.js b/tests/e2e/specs/profile/profileBar.cy.js
new file mode 100644
index 0000000000000000000000000000000000000000..472bfad41d753eea47875932b882903d69189d88
--- /dev/null
+++ b/tests/e2e/specs/profile/profileBar.cy.js
@@ -0,0 +1,39 @@
+describe('Profile Bar Navigation', () => {
+    beforeEach(() => {
+        cy.login('admin@example.com', 'password');
+        cy.visit('http://localhost:8082/profile');    
+    });
+  
+    it('navigates to My Profile component when My Profile link is clicked', () => {
+      // Click on My Profile link
+      cy.get('[data-cy=my-profile-link]').click();
+  
+      // Ensure that the right component is displayed
+        cy.get('.my-profile-main-container').should('exist');
+    });
+  
+    it('navigates to Bank Statements component when Bank Statements link is clicked', () => {
+      // Click on Bank Statements link
+      cy.get('[data-cy=bank-statements-link]').click();
+  
+      // Ensure that the right component is displayed
+      cy.get('.main-container-bankStatements').should('exist');
+    });
+  
+    it('navigates to Settings component when Settings link is clicked', () => {
+      // Click on Settings link
+      cy.get('[data-cy=settings-link]').click();
+  
+      // Ensure that the right component is displayed
+      cy.get('.settings-main-container').should('exist');
+    });
+  
+    it('navigates to Badges component when Badges link is clicked', () => {
+      // Click on Badges link
+      cy.get('[data-cy=badges-link]').click();
+  
+      // Ensure that the right component is displayed
+      cy.get('.badges-container').should('exist');
+    });
+  });
+  
diff --git a/tests/e2e/specs/test.js b/tests/e2e/specs/test.js
deleted file mode 100644
index 155e3021cb5751c26f404cdf0ba29acd9fd1cc5f..0000000000000000000000000000000000000000
--- a/tests/e2e/specs/test.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// https://docs.cypress.io/api/table-of-contents
-
-describe('My First Test', () => {
-  it('Visits the app root url', () => {
-    cy.visit('/')
-    cy.contains('h1', 'Welcome to Your Vue.js App')
-  })
-})
diff --git a/tests/e2e/specs/transactions/transactionsMain.cy.js b/tests/e2e/specs/transactions/transactionsMain.cy.js
new file mode 100644
index 0000000000000000000000000000000000000000..0a6a928355592f4871c1393833be1a9f5a377dac
--- /dev/null
+++ b/tests/e2e/specs/transactions/transactionsMain.cy.js
@@ -0,0 +1,25 @@
+describe('Transactions Component', () => {
+    beforeEach(() => {
+        cy.login('admin@example.com', 'password');
+        cy.visit('http://localhost:8082/transactions');
+    })
+
+    it('loads and displays the correct initial month', () => {
+        const prevMonth = new Date();
+        prevMonth.setMonth(prevMonth.getMonth() - 1);
+        const expectedDate = `${prevMonth.getFullYear()}-${prevMonth.getMonth() + 1 < 10 ? '0' + (prevMonth.getMonth() + 1) : prevMonth.getMonth() + 1}-01`;
+        cy.get('#selected-month').should('have.value', expectedDate);
+    });
+
+    it('allows editing of transactions when edit mode is toggled', () => {
+        cy.get('button').contains('Rediger').click();
+        cy.get('button').contains('Ferdig').click();
+    });
+
+/*     it('filters transactions based on search input', () => {
+        cy.get('.transaction-search').type('Mat');
+        cy.get('.table-container tbody tr').each(($el) => {
+            cy.wrap($el).contains('Mat');
+        });
+    }); */
+});
\ No newline at end of file
diff --git a/tests/e2e/specs/userDetails/importFiles.cy.js b/tests/e2e/specs/userDetails/importFiles.cy.js
new file mode 100644
index 0000000000000000000000000000000000000000..a2e8666996692412cab647c33b0f4055c8227dbb
--- /dev/null
+++ b/tests/e2e/specs/userDetails/importFiles.cy.js
@@ -0,0 +1,73 @@
+describe('Bank Statement Import Component', () => {
+    beforeEach(() => {
+        cy.login('admin@example.com', 'password');
+        cy.visit('http://localhost:8082/userDetails');
+        cy.get('#income').clear().type('10000');
+        cy.get('#savingPercentage').clear().type('20');
+        cy.contains('button', 'Videre').click();
+        cy.get('.option').first().click();
+        cy.contains('button', 'Neste').click();
+    });
+
+    it('displays bank statement import form correctly', () => {
+        cy.contains('h2', 'Kontoutskrifter');
+        cy.contains('p', 'Legg til kontoutskrifter fra de siste månedene.');
+        cy.contains('Velg bank');
+        cy.contains('Velg fil');
+        cy.contains('button', 'GÃ¥ tilbake');
+        cy.contains('button', 'Neste');
+    });
+
+    it('displays error message if no bank statement is uploaded', () => {
+        // Click Next button without uploading a file
+        cy.contains('button', 'Neste').click();
+
+        // Ensure error message is displayed
+        cy.contains('.error', 'Vennligst last opp minst en fil.');
+    });    
+
+    it('displays error message if bank statement is uploaded without selecting a bank', () => {
+        // Upload a PDF file without selecting a bank
+        cy.get('input[type="file"]').attachFile('handelsbankenExample.pdf');
+
+        // Ensure error message is displayed
+        cy.contains('.error', 'Du må velge en bank før du laster opp en fil. Vennligst prøv igjen.');
+    });    
+
+    it('allows selecting a bank and a PDF file', () => {
+        // Select a bank
+        cy.get('#pickBank').select('DNB');
+
+        // Upload a PDF file
+        cy.get('input[type="file"]').attachFile('handelsbankenExample.pdf');
+    });
+
+    it('shows error message for invalid file types', () => {
+        // Select a bank
+        cy.get('#pickBank').select('DNB');
+
+        // Upload a non-PDF file
+        cy.get('input[type="file"]').attachFile('pigrich.png');
+
+        // Ensure error message is displayed
+        cy.contains('.error', 'Vennligst last opp en gyldig PDF-fil.');
+    });
+
+    it('navigates to the previous page when back button is clicked', () => {
+        cy.contains('button', 'GÃ¥ tilbake').click();
+        cy.get('.household-main-container').should('exist');
+    });
+
+    it('should display error message if bank does not match the uploaded file', () => {
+        // Select a bank
+        cy.get('#pickBank').select('DNB');
+
+        // Upload a PDF file from a different bank
+        cy.get('input[type="file"]').attachFile('handelsbankenExample.pdf');
+
+        cy.wait(3000);
+
+        // Ensure error message is displayed
+        cy.contains('.error', 'Fikk ikke lastet opp kontoutsrift.');
+    });    
+});
\ No newline at end of file
diff --git a/tests/e2e/specs/userDetails/selectHousehold.cy.js b/tests/e2e/specs/userDetails/selectHousehold.cy.js
new file mode 100644
index 0000000000000000000000000000000000000000..734b993932de86eac676cf9975ec90156ac23459
--- /dev/null
+++ b/tests/e2e/specs/userDetails/selectHousehold.cy.js
@@ -0,0 +1,38 @@
+describe('Household Selection Component', () => {
+    beforeEach(() => {
+        cy.login('admin@example.com', 'password');
+        cy.visit('http://localhost:8082/userDetails');
+        cy.get('#income').clear().type('10000');
+        cy.get('#savingPercentage').clear().type('20');
+        cy.contains('button', 'Videre').click();
+    });
+
+    it('displays household selection options correctly', () => {
+        cy.contains('h2', 'Bosituasjon');
+        cy.get('.option').should('have.length', 4);
+    });
+
+    it('allows selecting a household option', () => {
+        // Click on the first household option
+        cy.get('.option').first().click();
+
+        // Ensure the selected option has the correct styling
+        cy.get('.option').first().should('have.class', 'selected');
+    });
+
+    it('navigates to the previous page when back button is clicked', () => {
+        cy.contains('button', 'GÃ¥ tilbake').click();
+        cy.get('.income-main-container').should('exist');
+    });
+
+    it('navigates to the next page when a valid option is selected and Next button is clicked', () => {
+        // Click on the first household option
+        cy.get('.option').first().click();
+
+        // Click Next button
+        cy.contains('button', 'Neste').click();
+
+        // Ensure the next page is displayed
+        cy.get('.importFiles-main-container').should('exist');
+    });
+});    
\ No newline at end of file
diff --git a/tests/e2e/specs/userDetails/selectIncome.cy.js b/tests/e2e/specs/userDetails/selectIncome.cy.js
new file mode 100644
index 0000000000000000000000000000000000000000..9f90863561ca415d786da98a7218e635866092c3
--- /dev/null
+++ b/tests/e2e/specs/userDetails/selectIncome.cy.js
@@ -0,0 +1,63 @@
+describe('Income Calculation Component', () => {
+    beforeEach(() => {
+        cy.login('admin@example.com', 'password');
+        cy.visit('http://localhost:8082/userDetails');
+    });
+
+    it('displays income calculation form correctly', () => {
+        cy.contains('h2', 'Beregning av månedlige utgifter');
+        cy.contains('p', 'Dette skjemaet vil regne ut hvor mye av inntekten din du har til forbruk, etter skatt og sparing');
+        cy.get('label[for="income"]').should('exist');
+        cy.get('label[for="savingPercentage"]').should('exist');
+        cy.get('label[for="consumption"]').should('exist');
+        cy.get('button').should('contain.text', 'Videre');
+    });
+
+    it('allows entering income and saving percentage', () => {
+        // Enter income
+        cy.get('#income').clear().type('10000');
+
+        // Enter saving percentage
+        cy.get('#savingPercentage').clear().type('20');
+
+        // Ensure Next button is enabled
+        cy.get('button').should('be.enabled');
+    });
+
+    it('calculates consumption correctly based on income and saving percentage', () => {
+        // Enter income
+        cy.get('#income').clear().type('10000');
+
+        // Enter saving percentage
+        cy.get('#savingPercentage').clear().type('20');
+
+        // Ensure consumption is calculated correctly
+        cy.get('#consumption').should('have.value', '8000');
+    });
+
+    it('handles invalid inputs and displays error message', () => {
+        // Leave income input empty
+        cy.get('#income').clear();
+        cy.get('#savingPercentage').clear().type('20');
+
+        // Click Next button
+        cy.contains('button', 'Videre').click();
+
+        // Ensure error message is displayed
+        cy.contains('.error-message', 'Vennligst fyll ut netto inntekt og prosentandel du ønsker å spare.');
+    });
+
+    it('navigates to the next page when valid inputs are provided and Next button is clicked', () => {
+        // Enter income
+        cy.get('#income').clear().type('10000');
+
+        // Enter saving percentage
+        cy.get('#savingPercentage').clear().type('20');
+
+        // Click Next button
+        cy.contains('button', 'Videre').click();
+
+        // Ensure the next page is displayed
+        cy.get('.household-main-container').should('exist');
+    });
+});
diff --git a/tests/e2e/support/commands.js b/tests/e2e/support/commands.js
index c1f5a772e2bcbd8a318fffcf7ba25e205a92dade..9f8e7afa4a48764d8ef721717ecbbc1c0822119b 100644
--- a/tests/e2e/support/commands.js
+++ b/tests/e2e/support/commands.js
@@ -1,25 +1,10 @@
-// ***********************************************
-// This example commands.js shows you how to
-// create various custom commands and overwrite
-// existing commands.
-//
-// For more comprehensive examples of custom
-// commands please read more here:
-// https://on.cypress.io/custom-commands
-// ***********************************************
-//
-//
-// -- This is a parent command --
-// Cypress.Commands.add("login", (email, password) => { ... })
-//
-//
-// -- This is a child command --
-// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
-//
-//
-// -- This is a dual command --
-// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
-//
-//
-// -- This is will overwrite an existing command --
-// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
+Cypress.Commands.add('login', (email, password) => {
+    cy.visit('http://localhost:8082/login');
+    cy.get('.navigation')
+    .contains('h5', 'Logg inn') 
+    .click();    
+    cy.get('#email-input-filed').type(email, { force: true });
+    cy.get('#password-input-filed').type(password, { force: true }); 
+    cy.get('#login-button').click({ force: true });
+    cy.get('.home-container').should('exist');
+});
diff --git a/tests/e2e/support/index.js b/tests/e2e/support/index.js
index d68db96df2697e0835f5c490db0c2cc81673f407..7780c9dcdab957fb3e9a0741f35bc355233f1f64 100644
--- a/tests/e2e/support/index.js
+++ b/tests/e2e/support/index.js
@@ -1,20 +1,2 @@
-// ***********************************************************
-// This example support/index.js is processed and
-// loaded automatically before your test files.
-//
-// This is a great place to put global configuration and
-// behavior that modifies Cypress.
-//
-// You can change the location of this file or turn off
-// automatically serving support files with the
-// 'supportFile' configuration option.
-//
-// You can read more here:
-// https://on.cypress.io/configuration
-// ***********************************************************
-
-// Import commands.js using ES2015 syntax:
 import './commands'
-
-// Alternatively you can use CommonJS syntax:
-// require('./commands')
+import 'cypress-file-upload';
diff --git a/tests/unit/example.spec.js b/tests/unit/example.spec.js
deleted file mode 100644
index 4b21ca7d9d18950c2e21e897f19aab2ce9aa1017..0000000000000000000000000000000000000000
--- a/tests/unit/example.spec.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import { shallowMount } from '@vue/test-utils'
-import HelloWorld from '@/components/HelloWorld.vue'
-
-describe('HelloWorld.vue', () => {
-  it('renders props.msg when passed', () => {
-    const msg = 'new message'
-    const wrapper = shallowMount(HelloWorld, {
-      props: { msg }
-    })
-    expect(wrapper.text()).toMatch(msg)
-  })
-})
diff --git a/tests/unit/router/router.spec.js b/tests/unit/router/router.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..0ef315612b571a5173fe604306df24f7d284da95
--- /dev/null
+++ b/tests/unit/router/router.spec.js
@@ -0,0 +1,35 @@
+import router from '@/router'
+
+jest.mock('@/views/HomeView.vue', () => ({ default: { template: '<div></div>' } }));
+jest.mock('@/views/VerificationView.vue', () => ({ default: { template: '<div></div>' } }));
+
+const routes = router.getRoutes();
+
+describe('Router', () => {
+    beforeEach(() => {
+        jest.clearAllMocks();
+        jest.spyOn(Storage.prototype, 'getItem').mockReturnValue(null);
+        jest.spyOn(Storage.prototype, 'setItem');
+    });
+    
+    it('redirects to login if user is not authenticated', async () => {
+        await router.push('/budget').catch(() => {});
+        await router.isReady();
+
+        expect(router.currentRoute.value.name).toBe('login');
+    });
+
+    it('requires authentication for protected routes', async () => {
+        const authRoutes = routes.find(route => route.meta.requiresAuth && route.meta);
+        expect(authRoutes).toBeTruthy();
+        expect(authRoutes.meta.requiresAuth).toBe(true);
+    });
+
+    it('allows access to protected routes if user is authenticated', async () => {
+        jest.spyOn(Storage.prototype, 'getItem').mockReturnValue('token');
+        await router.push('/budget').catch(() => {});
+        await router.isReady();
+
+        expect(router.currentRoute.value.name).toBe('budget');
+    });
+});
diff --git a/tests/unit/service/accountService.spec.js b/tests/unit/service/accountService.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..118d6c393e235a720d2ea3b2c16f2f8e72d61e92
--- /dev/null
+++ b/tests/unit/service/accountService.spec.js
@@ -0,0 +1,40 @@
+import AccountService from "@/services/internal/AccountService";
+import axios from "axios";
+
+jest.mock("axios");
+
+describe("AccountService", () => {
+    beforeEach(() => {
+        jest.clearAllMocks();
+    });
+
+    it("should add an account", async () => {
+        const mockResponse = { data: "Account added" };
+        axios.post.mockResolvedValue(mockResponse);
+
+        const response = await AccountService.addAccount("1234567890");
+
+        expect(response).toBe("Account added");
+
+        expect(axios.post).toHaveBeenCalledWith(
+            `${AccountService.baseURL}/addAccount`, 
+            expect.any(FormData),{
+                headers: {
+                    "Content-Type": "application/json"
+                },
+            }
+        );
+    });
+
+    it("should get accounts", async () => {
+        const mockResponse = { data: "Accounts" };
+        axios.get.mockResolvedValue(mockResponse);
+
+        const response = await AccountService.getAccounts();
+
+        expect(response).toBe("Accounts");
+
+        expect(axios.get).toHaveBeenCalledWith(`${AccountService.baseURL}/getAccounts`);
+    });
+
+});
\ No newline at end of file
diff --git a/tests/unit/service/emailService.spec.js b/tests/unit/service/emailService.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..78bef2a2f095ebc94897e98afca2bf26bb5eb1cd
--- /dev/null
+++ b/tests/unit/service/emailService.spec.js
@@ -0,0 +1,18 @@
+import EmailService from "@/services/internal/EmailService";
+import axios from "axios";
+
+jest.mock("axios");
+
+describe("EmailService", () => {
+    afterEach(() => {
+        jest.clearAllMocks();
+    });
+
+    it("should send register token", async () => {
+        const mockResponse = { success: true };
+        axios.get.mockResolvedValue({ data: mockResponse });
+
+        const response = await EmailService.sendRegisterToken("to@mail.com");
+        expect(response).toEqual(mockResponse);
+    });
+});
\ No newline at end of file
diff --git a/tests/unit/service/newsService.spec.js b/tests/unit/service/newsService.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..2b8cf547af42d6c1212087ef9800dbe9511aaa44
--- /dev/null
+++ b/tests/unit/service/newsService.spec.js
@@ -0,0 +1,20 @@
+import NewsService from "@/services/internal/NewsService";
+import axios from "axios";
+
+jest.mock("axios");
+
+describe("NewsService", () => {
+    afterEach(() => {
+        jest.clearAllMocks();
+    });
+
+    it("should get news", async () => {
+        const mockResponse = { success: true };
+        axios.get.mockResolvedValue({ data: mockResponse });
+
+        const response = await NewsService.getNews(1, 10);
+        expect(response).toEqual(mockResponse);
+
+        expect(axios.get).toHaveBeenCalledWith(`${NewsService.baseURL}/?page=1&pageSize=10`);
+    });
+});
\ No newline at end of file
diff --git a/tests/unit/service/streakService.spec.js b/tests/unit/service/streakService.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..9c6ccf0a6b7dc2467c25a438fb0ef7ccd078745b
--- /dev/null
+++ b/tests/unit/service/streakService.spec.js
@@ -0,0 +1,47 @@
+import axios from "axios";
+import StreakService from "@/services/internal/StreakService";
+
+// Mocking axios
+jest.mock("axios");
+
+describe("StreakService", () => {
+    afterEach(() => {
+        jest.clearAllMocks();
+    });
+
+    it("should get streak", async () => {
+        // Mock successful response from API
+        const mockResponse = { success: true };
+        axios.get.mockResolvedValue({ data: mockResponse });
+
+        const response = await StreakService.getStreak();
+
+        expect(response).toEqual(mockResponse);
+
+        // Verify that axios.get was called with the correct arguments
+        expect(axios.get).toHaveBeenCalledWith(
+            `${StreakService.baseURL}`
+        );
+    });
+
+    it("should change streak", async () => {
+        // Mock successful response from API
+        const mockResponse = { success: true };
+        axios.put.mockResolvedValue({ data: mockResponse });
+
+        const response = await StreakService.changeStreak(1);
+
+        expect(response).toEqual(mockResponse);
+
+        // Verify that axios.put was called with the correct arguments
+        expect(axios.put).toHaveBeenCalledWith(
+            `${StreakService.baseURL}`,
+            { increment: 1 },
+            {
+                headers: {
+                    'Content-Type': 'application/json'
+                }
+            }
+        );
+    });
+});
\ No newline at end of file
diff --git a/tests/unit/service/transactionService.spec.js b/tests/unit/service/transactionService.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..c7de3a8ed2b8e29c2a8807a789485342f4d0ee0c
--- /dev/null
+++ b/tests/unit/service/transactionService.spec.js
@@ -0,0 +1,31 @@
+import TransactionService from "@/services/internal/TransactionService";
+import axios from "axios";
+
+jest.mock("axios");
+
+describe("TransactionService", () => {
+    afterEach(() => {
+        jest.clearAllMocks();
+    });
+
+    it("should add transactions", async () => {
+        const mockResponse = { success: true };
+        axios.post.mockResolvedValue({ data: mockResponse });
+
+        const pdfFile = new File([""], "filename.pdf", { type: "application/pdf" });
+
+        const response = await TransactionService.addTransactions(pdfFile);
+
+        expect(response).toEqual(mockResponse);
+
+        expect(axios.post).toHaveBeenCalledWith(
+            `${TransactionService.baseURL}/addTransactions`,
+            expect.any(FormData),
+            {
+                headers: {
+                    "Content-Type": "application/json",
+                },
+            }
+        );
+    });
+});
\ No newline at end of file
diff --git a/tests/unit/service/userDetailsService.spec.js b/tests/unit/service/userDetailsService.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..967519b843457496c625b9590a3b20bd53c8ee48
--- /dev/null
+++ b/tests/unit/service/userDetailsService.spec.js
@@ -0,0 +1,70 @@
+import UserDetailsService from "@/services/internal/UserDetailsService";
+import axios from "axios";
+
+jest.mock("axios");
+
+describe("UserDetailsService", () => {
+    afterEach(() => {
+        jest.clearAllMocks();
+    });
+
+    it("should change first name", async () => {
+        const mockResponse = { success: true };
+        axios.put.mockResolvedValue({ data: mockResponse });
+
+        const changeFirstNameDTO = { newFirstName: "NewFirstName" };
+
+        const response = await UserDetailsService.changeFirstName(changeFirstNameDTO);
+
+        expect(response).toEqual(mockResponse);
+
+        expect(axios.put).toHaveBeenCalledWith(
+            `${UserDetailsService.baseURL}/first-name`,
+             changeFirstNameDTO, {
+                headers: {
+                    "Content-Type": "application/json"
+                }
+            }
+        );
+    });
+
+    it("should change last name", async () => {
+        const mockResponse = { success: true };
+        axios.put.mockResolvedValue({ data: mockResponse });
+
+        const changeLastNameDTO = { newLastName: "NewLastName" };
+
+        const response = await UserDetailsService.changeLastName(changeLastNameDTO);
+
+        expect(response).toEqual(mockResponse);
+
+        expect(axios.put).toHaveBeenCalledWith(
+            `${UserDetailsService.baseURL}/last-name`,
+            changeLastNameDTO,{
+                headers: {
+                    "Content-Type": "application/json"
+                }
+            }
+        );
+    });
+
+    it("should change password", async () => {
+        const mockResponse = { success: true };
+        axios.put.mockResolvedValue({ data: mockResponse });
+
+        const changePasswordDTO = { newPassword: "NewPassword" };
+
+        const response = await UserDetailsService.changePassword(changePasswordDTO);
+
+        expect(response).toEqual(mockResponse);
+
+        expect(axios.put).toHaveBeenCalledWith(
+            `${UserDetailsService.baseURL}/password`,
+            changePasswordDTO, {
+                headers: {
+                    "Content-Type": "application/json"
+                }
+            }
+        );
+    });
+});
\ No newline at end of file
diff --git a/tests/unit/service/userService.spec.js b/tests/unit/service/userService.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..616fc8e12e3520aca0e895d292a92ad2f71b2457
--- /dev/null
+++ b/tests/unit/service/userService.spec.js
@@ -0,0 +1,76 @@
+import UserService from "@/services/internal/UserService";
+import axios from "axios";
+
+// Mocking axios
+jest.mock("axios");
+
+
+describe("UserService", () => {
+    afterEach(() => {
+        jest.clearAllMocks();
+    });
+
+    it("should register a user", async () => {
+        // Mock successful response from API
+        const mockResponse = { success : true };
+        axios.post.mockResolvedValue({ data: mockResponse });
+
+        const registerRequestDTO = {
+            email: "test@mail.com", 
+            firstName: "FirstName", 
+            lastName: "LastName", 
+            password: "Password"};
+        
+        const response = await UserService.register(registerRequestDTO);
+
+        expect(response).toEqual(mockResponse);
+
+        // Verify that axios.post was called with the correct arguments
+        expect(axios.post).toHaveBeenCalledWith(
+            `${UserService.baseURL}/register`, 
+            registerRequestDTO
+        );
+    });
+
+    it("should login a user", async () => {
+        // Mock successful response from API
+        const mockResponse = { token: "token" };
+        axios.post.mockResolvedValue({ data: mockResponse });
+
+        const loginRequestDTO = {
+            email: "test@mail.com",
+            password: "Password"
+        };
+
+        const response = await UserService.login(loginRequestDTO);
+
+        expect(response).toEqual(mockResponse);
+
+        // Verify that axios.post was called with the correct arguments
+        expect(axios.post).toHaveBeenCalledWith(
+            `${UserService.baseURL}/login`,
+            loginRequestDTO, {
+                headers: { 
+                    "Content-Type": "application/json" 
+                } 
+            }
+        );
+    });
+
+    it("should check if email exists", async () => {
+        // Mock successful response from API
+        const mockResponse = { exists: true };
+        axios.get.mockResolvedValue({ data: mockResponse });
+
+        const email = "existing@mail.com";
+        const response = await UserService.emailExist(email);
+
+        expect(response).toEqual(mockResponse);
+
+        // Verify that axios.get was called with the correct arguments
+        expect(axios.get).toHaveBeenCalledWith(
+            `${UserService.baseURL}/emailExist`,
+            { params: { email } }
+        );
+    });
+});           
\ No newline at end of file
diff --git a/tests/unit/store/userStore.spec.js b/tests/unit/store/userStore.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..8589355ef8cb633e96675b753c56d8b0e862dfec
--- /dev/null
+++ b/tests/unit/store/userStore.spec.js
@@ -0,0 +1,48 @@
+import userStore from "@/store/UserStore";
+
+
+describe("UserStore", () => {
+    afterEach(() => {
+        userStore.mutations.resetState(userStore.state());
+    });
+    
+    it("should have initial state", () => {
+        const state = userStore.state();
+        expect(state.email).toBe("");
+        expect(state.firstName).toBe("");
+        expect(state.lastName).toBe("");
+        expect(state.authToken).toBe("");
+        expect(state.isAuthenticated).toBe(false);
+    });
+
+    it("should set auth token", () => {
+        const state = userStore.state();
+        userStore.mutations.setAuthToken(state, "12345");
+        expect(state.authToken).toBe("12345");
+    });
+
+    it("should set email", () => {
+        const state = userStore.state();
+        userStore.mutations.setEmail(state, "mail@mail.com");
+        expect(state.email).toBe("mail@mail.com");
+    });
+
+    it("should set first name", () => {
+        const state = userStore.state();
+        userStore.mutations.setFirstName(state, "John");
+        expect(state.firstName).toBe("John");
+    });
+
+    it("should set last name", () => {
+        const state = userStore.state();
+        userStore.mutations.setLastName(state, "Doe");
+        expect(state.lastName).toBe("Doe");
+    });
+
+    it("should set isAuthenticated", () => {
+        const state = userStore.state();
+        userStore.mutations.setAuthentication(state, true);
+        expect(state.isAuthenticated).toBe(true);
+    });
+
+});
\ No newline at end of file