diff --git a/backend/app/main.py b/backend/app/main.py
index 3ccd84a7dae05f69231d2a92e4503dfb17931b99..779027522cbd9d75654256ae08b765c7f19b0a35 100644
--- a/backend/app/main.py
+++ b/backend/app/main.py
@@ -83,12 +83,11 @@ def health():
 
 @authRouter.post("/login", status_code=200)
 def login(
-    email: Annotated[str, Form()],
-    password: Annotated[str, Form()],
+    login: schemas.LoginData,
     response: Response,
     db: Session = Depends(get_db),
 ):
-    db_user = crud.get_user_by_email_and_password(db, email, password)
+    db_user = crud.get_user_by_email_and_password(db, login.email, login.password)
     if db_user is None:
         raise HTTPException(status_code=401, detail="Incorrect email or password")
 
@@ -112,21 +111,22 @@ def login(
 
 @authRouter.post("/register", status_code=status.HTTP_201_CREATED)
 def register(
-    email: Annotated[str, Form()],
-    password: Annotated[str, Form()],
-    nickname: Annotated[str, Form()],
-    tutor: Annotated[bool, Form()],
+    register: schemas.RegisterData,
     db: Session = Depends(get_db),
 ):
-    db_user = crud.get_user_by_email(db, email=email)
+    db_user = crud.get_user_by_email(db, email=register.email)
     if db_user:
         raise HTTPException(status_code=400, detail="User already registered")
 
     user_data = schemas.UserCreate(
-        email=email,
-        password=password,
-        nickname=nickname,
-        type=models.UserType.TUTOR.value if tutor else models.UserType.STUDENT.value,
+        email=register.email,
+        password=register.password,
+        nickname=register.nickname,
+        type=(
+            models.UserType.TUTOR.value
+            if register.is_tutor
+            else models.UserType.STUDENT.value
+        ),
     )
 
     user = crud.create_user(db=db, user=user_data)
@@ -206,9 +206,8 @@ def update_user(
     db: Session = Depends(get_db),
     current_user: schemas.User = Depends(get_jwt_user),
 ):
-    if (
-        not check_user_level(current_user, models.UserType.ADMIN)
-        and current_user.id != user_id
+    if not check_user_level(current_user, models.UserType.ADMIN) and (
+        current_user.id != user_id or user.type is not None
     ):
         raise HTTPException(
             status_code=401, detail="You do not have permission to update this user"
diff --git a/backend/app/schemas.py b/backend/app/schemas.py
index 464eec16dc3c2cf2369c30f7bf03278b7c36272e..caf180e679e9ffd24479fffc23ab9228aa39eca8 100644
--- a/backend/app/schemas.py
+++ b/backend/app/schemas.py
@@ -3,6 +3,18 @@ from pydantic import BaseModel, NaiveDatetime
 from models import UserType
 
 
+class LoginData(BaseModel):
+    email: str
+    password: str
+
+
+class RegisterData(BaseModel):
+    email: str
+    password: str
+    nickname: str
+    is_tutor: bool
+
+
 class User(BaseModel):
     id: int
     email: str
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index a9519f482155d15532bce8aa0e79c926dbd56153..3a0ba508e603ea29e41710d3f0da07bdb9eebaac 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -12,10 +12,11 @@
 				"@sveltekit-i18n/parser-icu": "^1.0.8",
 				"dayjs": "^1.11.13",
 				"emoji-picker-element": "^1.23.0",
+				"js-sha256": "^0.11.0",
 				"linkify-html": "^4.1.3",
 				"linkifyjs": "^4.1.3",
 				"sanitize-html": "^2.13.1",
-				"svelte-gravatar": "^1.0.3",
+				"svelte-autosize": "^1.1.5",
 				"svelte-i18n": "^4.0.1",
 				"svelte-select": "^5.8.3"
 			},
@@ -31,6 +32,7 @@
 				"@tsconfig/svelte": "^5.0.4",
 				"@types/eslint": "^9.6.1",
 				"@types/js-cookie": "^3.0.6",
+				"@types/sanitize-html": "^2.13.0",
 				"@typescript-eslint/eslint-plugin": "^8.14.0",
 				"@typescript-eslint/parser": "^8.14.0",
 				"@zerodevx/svelte-toast": "^0.9.6",
@@ -1426,6 +1428,12 @@
 			"dev": true,
 			"license": "MIT"
 		},
+		"node_modules/@types/autosize": {
+			"version": "4.0.3",
+			"resolved": "https://registry.npmjs.org/@types/autosize/-/autosize-4.0.3.tgz",
+			"integrity": "sha512-o0ZyU3ePp3+KRbhHsY4ogjc+ZQWgVN5h6j8BHW5RII4cFKi6PEKK9QPAcphJVkD0dGpyFnD3VRR0WMvHVjCv9w==",
+			"license": "MIT"
+		},
 		"node_modules/@types/cookie": {
 			"version": "0.6.0",
 			"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
@@ -1471,6 +1479,16 @@
 			"dev": true,
 			"license": "MIT"
 		},
+		"node_modules/@types/sanitize-html": {
+			"version": "2.13.0",
+			"resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.13.0.tgz",
+			"integrity": "sha512-X31WxbvW9TjIhZZNyNBZ/p5ax4ti7qsNDBDEnH4zAgmEh35YnFD1UiS6z9Cd34kKm0LslFW0KPmTQzu/oGtsqQ==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"htmlparser2": "^8.0.0"
+			}
+		},
 		"node_modules/@typescript-eslint/eslint-plugin": {
 			"version": "8.17.0",
 			"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.17.0.tgz",
@@ -1871,6 +1889,12 @@
 				"postcss": "^8.1.0"
 			}
 		},
+		"node_modules/autosize": {
+			"version": "6.0.1",
+			"resolved": "https://registry.npmjs.org/autosize/-/autosize-6.0.1.tgz",
+			"integrity": "sha512-f86EjiUKE6Xvczc4ioP1JBlWG7FKrE13qe/DxBCpe8GCipCq2nFw73aO8QEBKHfSbYGDN5eB9jXWKen7tspDqQ==",
+			"license": "MIT"
+		},
 		"node_modules/axios": {
 			"version": "1.7.9",
 			"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
@@ -2053,15 +2077,6 @@
 				"url": "https://github.com/chalk/chalk?sponsor=1"
 			}
 		},
-		"node_modules/charenc": {
-			"version": "0.0.2",
-			"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
-			"integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==",
-			"license": "BSD-3-Clause",
-			"engines": {
-				"node": "*"
-			}
-		},
 		"node_modules/chokidar": {
 			"version": "4.0.1",
 			"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz",
@@ -2190,15 +2205,6 @@
 				"node": ">= 8"
 			}
 		},
-		"node_modules/crypt": {
-			"version": "0.0.2",
-			"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
-			"integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==",
-			"license": "BSD-3-Clause",
-			"engines": {
-				"node": "*"
-			}
-		},
 		"node_modules/css-selector-tokenizer": {
 			"version": "0.8.0",
 			"resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz",
@@ -3356,12 +3362,6 @@
 				"node": ">=8"
 			}
 		},
-		"node_modules/is-buffer": {
-			"version": "1.1.6",
-			"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
-			"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
-			"license": "MIT"
-		},
 		"node_modules/is-core-module": {
 			"version": "2.15.1",
 			"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
@@ -3494,6 +3494,12 @@
 				"jiti": "bin/jiti.js"
 			}
 		},
+		"node_modules/js-sha256": {
+			"version": "0.11.0",
+			"resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.11.0.tgz",
+			"integrity": "sha512-6xNlKayMZvds9h1Y1VWc0fQHQ82BxTXizWPEtEeGvmOUYpBRy4gbWroHLpzowe6xiQhHpelCQiE7HEdznyBL9Q==",
+			"license": "MIT"
+		},
 		"node_modules/js-yaml": {
 			"version": "4.1.0",
 			"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
@@ -3719,17 +3725,6 @@
 				"semver": "bin/semver"
 			}
 		},
-		"node_modules/md5": {
-			"version": "2.3.0",
-			"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
-			"integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
-			"license": "BSD-3-Clause",
-			"dependencies": {
-				"charenc": "0.0.2",
-				"crypt": "0.0.2",
-				"is-buffer": "~1.1.6"
-			}
-		},
 		"node_modules/memoizee": {
 			"version": "0.4.17",
 			"resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.17.tgz",
@@ -4899,6 +4894,19 @@
 				"node": ">=18"
 			}
 		},
+		"node_modules/svelte-autosize": {
+			"version": "1.1.5",
+			"resolved": "https://registry.npmjs.org/svelte-autosize/-/svelte-autosize-1.1.5.tgz",
+			"integrity": "sha512-whiND/GthFDG9Ansvil21qxmFhSMfuooVZPg40sbcLHYKR9srYhnfrP5qdw8MXHAm6DY9g5PawurOAWl34fK7g==",
+			"license": "MIT",
+			"dependencies": {
+				"@types/autosize": "^4.0.3",
+				"autosize": "*"
+			},
+			"peerDependencies": {
+				"svelte": ">=3.0.0"
+			}
+		},
 		"node_modules/svelte-check": {
 			"version": "4.1.1",
 			"resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.1.1.tgz",
@@ -4996,29 +5004,6 @@
 				"@floating-ui/dom": "^1.5.3"
 			}
 		},
-		"node_modules/svelte-gravatar": {
-			"version": "1.0.3",
-			"resolved": "https://registry.npmjs.org/svelte-gravatar/-/svelte-gravatar-1.0.3.tgz",
-			"integrity": "sha512-CNxIV2lAuiqwdaPrGAM/BFj5U1dNNQXzeyh+HVi/48BODFXoDy0L1CMqYyvM+aKiF4ideZUBwT0S9/C1BeL5oA==",
-			"license": "MIT",
-			"dependencies": {
-				"md5": "^2.2.1",
-				"svelte": "^3.16.0",
-				"svelte-waypoint": "^0.1.3"
-			},
-			"peerDependencies": {
-				"svelte": "*"
-			}
-		},
-		"node_modules/svelte-gravatar/node_modules/svelte": {
-			"version": "3.59.2",
-			"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.59.2.tgz",
-			"integrity": "sha512-vzSyuGr3eEoAtT/A6bmajosJZIUWySzY2CzB3w2pgPvnkUjGqlDnsNnA0PMO+mMAhuyMul6C2uuZzY6ELSkzyA==",
-			"license": "MIT",
-			"engines": {
-				"node": ">= 8"
-			}
-		},
 		"node_modules/svelte-hero-icons": {
 			"version": "5.2.0",
 			"resolved": "https://registry.npmjs.org/svelte-hero-icons/-/svelte-hero-icons-5.2.0.tgz",
@@ -5125,12 +5110,6 @@
 				"svelte-floating-ui": "1.5.8"
 			}
 		},
-		"node_modules/svelte-waypoint": {
-			"version": "0.1.4",
-			"resolved": "https://registry.npmjs.org/svelte-waypoint/-/svelte-waypoint-0.1.4.tgz",
-			"integrity": "sha512-UEqoXZjJeKj2sWlAIsBOFjxjMn+KP8aFCc/zjdmZi1cCOE59z6T2C+I6ZaAf8EmNQqNzfZVB/Lci4Ci9spzXAw==",
-			"license": "MIT"
-		},
 		"node_modules/svelte/node_modules/is-reference": {
 			"version": "3.0.3",
 			"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index c99f8cecdc3690f0f9a8ddee86148fd4c0cebd12..bb074c8ddc03fa17101e259c2ada4cd270d7a952 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -3,12 +3,13 @@
 	"version": "0.0.1",
 	"private": true,
 	"scripts": {
-		"dev": "VITE_API_URL=http://127.0.0.1:8000/api/v1 VITE_APP_URL=http://127.0.0.1:5173 VITE_WS_URL=ws://127.0.0.1:8000/api/v1/ws vite dev --host 127.0.0.1",
+		"dev": "VITE_API_URL=http://127.0.0.1:5173/api VITE_APP_URL=http://127.0.0.1:5173 VITE_WS_URL=ws://127.0.0.1:8000/api/v1/ws vite dev --host 127.0.0.1",
 		"build": "vite build",
 		"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
 		"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
-		"lint": "prettier --check .",
-		"format": "prettier --write . --log-level warn"
+		"lint": "prettier --check . && tsc --noEmit",
+		"format": "prettier --write . --log-level warn",
+		"tsc": "tsc"
 	},
 	"devDependencies": {
 		"@rollup/plugin-commonjs": "^28.0.1",
@@ -22,6 +23,7 @@
 		"@tsconfig/svelte": "^5.0.4",
 		"@types/eslint": "^9.6.1",
 		"@types/js-cookie": "^3.0.6",
+		"@types/sanitize-html": "^2.13.0",
 		"@typescript-eslint/eslint-plugin": "^8.14.0",
 		"@typescript-eslint/parser": "^8.14.0",
 		"@zerodevx/svelte-toast": "^0.9.6",
@@ -54,10 +56,11 @@
 		"@sveltekit-i18n/parser-icu": "^1.0.8",
 		"dayjs": "^1.11.13",
 		"emoji-picker-element": "^1.23.0",
+		"js-sha256": "^0.11.0",
 		"linkify-html": "^4.1.3",
 		"linkifyjs": "^4.1.3",
 		"sanitize-html": "^2.13.1",
-		"svelte-gravatar": "^1.0.3",
+		"svelte-autosize": "^1.1.5",
 		"svelte-i18n": "^4.0.1",
 		"svelte-select": "^5.8.3"
 	}
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index 80254a00fa050a8ff36803d4abd5cce6ba87e370..fa2d4e6f24020c7755103b3a391f260dcf2bee40 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -19,19 +19,22 @@ importers:
         version: 1.11.13
       emoji-picker-element:
         specifier: ^1.23.0
-        version: 1.24.0
+        version: 1.25.0
+      js-sha256:
+        specifier: ^0.11.0
+        version: 0.11.0
       linkify-html:
         specifier: ^4.1.3
-        version: 4.1.4(linkifyjs@4.1.4)
+        version: 4.2.0(linkifyjs@4.2.0)
       linkifyjs:
         specifier: ^4.1.3
-        version: 4.1.4
+        version: 4.2.0
       sanitize-html:
         specifier: ^2.13.1
         version: 2.13.1
-      svelte-gravatar:
-        specifier: ^1.0.3
-        version: 1.0.3(svelte@5.8.1)
+      svelte-autosize:
+        specifier: ^1.1.5
+        version: 1.1.5(svelte@5.8.1)
       svelte-i18n:
         specifier: ^4.0.1
         version: 4.0.1(svelte@5.8.1)
@@ -41,31 +44,28 @@ importers:
     devDependencies:
       '@rollup/plugin-commonjs':
         specifier: ^28.0.1
-        version: 28.0.1(rollup@4.27.3)
+        version: 28.0.1(rollup@4.28.1)
       '@rollup/plugin-json':
         specifier: ^6.1.0
-        version: 6.1.0(rollup@4.27.3)
-      '@rollup/plugin-node-resolve':
-        specifier: ^15.3.0
-        version: 15.3.0(rollup@4.27.3)
+        version: 6.1.0(rollup@4.28.1)
       '@sveltejs/adapter-auto':
         specifier: ^3.3.1
-        version: 3.3.1(@sveltejs/kit@2.8.1(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.0)))(svelte@5.8.1)(vite@5.4.11(less@4.2.0)))
+        version: 3.3.1(@sveltejs/kit@2.9.0(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.1)))(svelte@5.8.1)(vite@5.4.11(less@4.2.1)))
       '@sveltejs/adapter-node':
         specifier: ^5.2.9
-        version: 5.2.9(@sveltejs/kit@2.8.1(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.0)))(svelte@5.8.1)(vite@5.4.11(less@4.2.0)))
+        version: 5.2.9(@sveltejs/kit@2.9.0(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.1)))(svelte@5.8.1)(vite@5.4.11(less@4.2.1)))
       '@sveltejs/adapter-static':
         specifier: ^3.0.6
-        version: 3.0.6(@sveltejs/kit@2.8.1(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.0)))(svelte@5.8.1)(vite@5.4.11(less@4.2.0)))
+        version: 3.0.6(@sveltejs/kit@2.9.0(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.1)))(svelte@5.8.1)(vite@5.4.11(less@4.2.1)))
       '@sveltejs/kit':
         specifier: ^2.8.0
-        version: 2.8.1(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.0)))(svelte@5.8.1)(vite@5.4.11(less@4.2.0))
+        version: 2.9.0(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.1)))(svelte@5.8.1)(vite@5.4.11(less@4.2.1))
       '@sveltejs/vite-plugin-svelte':
         specifier: ^4.0.0
-        version: 4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.0))
+        version: 4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.1))
       '@tailwindcss/forms':
         specifier: ^0.5.9
-        version: 0.5.9(tailwindcss@3.4.15)
+        version: 0.5.9(tailwindcss@3.4.16)
       '@tsconfig/svelte':
         specifier: ^5.0.4
         version: 5.0.4
@@ -75,12 +75,15 @@ importers:
       '@types/js-cookie':
         specifier: ^3.0.6
         version: 3.0.6
+      '@types/sanitize-html':
+        specifier: ^2.13.0
+        version: 2.13.0
       '@typescript-eslint/eslint-plugin':
         specifier: ^8.14.0
-        version: 8.15.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)
+        version: 8.17.0(@typescript-eslint/parser@8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)
       '@typescript-eslint/parser':
         specifier: ^8.14.0
-        version: 8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)
+        version: 8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)
       '@zerodevx/svelte-toast':
         specifier: ^0.9.6
         version: 0.9.6(svelte@5.8.1)
@@ -89,64 +92,64 @@ importers:
         version: 10.4.20(postcss@8.4.49)
       axios:
         specifier: ^1.7.7
-        version: 1.7.7
+        version: 1.7.9
       axios-jwt:
         specifier: ^4.0.3
-        version: 4.0.3(axios@1.7.7)
+        version: 4.0.3(axios@1.7.9)
       daisyui:
         specifier: ^4.12.14
         version: 4.12.14(postcss@8.4.49)
       eslint:
         specifier: ^9.14.0
-        version: 9.15.0(jiti@1.21.6)
+        version: 9.16.0(jiti@1.21.6)
       eslint-config-prettier:
         specifier: ^9.1.0
-        version: 9.1.0(eslint@9.15.0(jiti@1.21.6))
+        version: 9.1.0(eslint@9.16.0(jiti@1.21.6))
       eslint-plugin-svelte:
         specifier: ^2.46.0
-        version: 2.46.0(eslint@9.15.0(jiti@1.21.6))(svelte@5.8.1)
+        version: 2.46.1(eslint@9.16.0(jiti@1.21.6))(svelte@5.8.1)
       jwt-decode:
         specifier: ^4.0.0
         version: 4.0.0
       less:
         specifier: ^4.2.0
-        version: 4.2.0
+        version: 4.2.1
       postcss:
         specifier: ^8.4.49
         version: 8.4.49
       prettier:
         specifier: ^3.3.3
-        version: 3.3.3
+        version: 3.4.2
       prettier-plugin-svelte:
         specifier: ^3.2.8
-        version: 3.2.8(prettier@3.3.3)(svelte@5.8.1)
+        version: 3.3.2(prettier@3.4.2)(svelte@5.8.1)
       svelte:
         specifier: ^5.1.15
         version: 5.8.1
       svelte-check:
         specifier: ^4.0.7
-        version: 4.0.9(picomatch@4.0.2)(svelte@5.8.1)(typescript@5.6.3)
+        version: 4.1.1(picomatch@4.0.2)(svelte@5.8.1)(typescript@5.7.2)
       svelte-hero-icons:
         specifier: ^5.2.0
         version: 5.2.0(svelte@5.8.1)
       svelte-preprocess:
         specifier: ^6.0.3
-        version: 6.0.3(less@4.2.0)(postcss-load-config@4.0.2(postcss@8.4.49))(postcss@8.4.49)(svelte@5.8.1)(typescript@5.6.3)
+        version: 6.0.3(less@4.2.1)(postcss-load-config@4.0.2(postcss@8.4.49))(postcss@8.4.49)(svelte@5.8.1)(typescript@5.7.2)
       sveltekit-i18n:
         specifier: ^2.4.2
         version: 2.4.2(svelte@5.8.1)
       tailwindcss:
         specifier: ^3.4.14
-        version: 3.4.15
+        version: 3.4.16
       tslib:
         specifier: ^2.8.1
         version: 2.8.1
       typescript:
         specifier: ^5.6.3
-        version: 5.6.3
+        version: 5.7.2
       vite:
         specifier: ^5.4.11
-        version: 5.4.11(less@4.2.0)
+        version: 5.4.11(less@4.2.1)
 
 packages:
 
@@ -444,28 +447,28 @@ packages:
     resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
     engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
 
-  '@eslint/config-array@0.19.0':
-    resolution: {integrity: sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==}
+  '@eslint/config-array@0.19.1':
+    resolution: {integrity: sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
-  '@eslint/core@0.9.0':
-    resolution: {integrity: sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==}
+  '@eslint/core@0.9.1':
+    resolution: {integrity: sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
   '@eslint/eslintrc@3.2.0':
     resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
-  '@eslint/js@9.15.0':
-    resolution: {integrity: sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==}
+  '@eslint/js@9.16.0':
+    resolution: {integrity: sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
-  '@eslint/object-schema@2.1.4':
-    resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==}
+  '@eslint/object-schema@2.1.5':
+    resolution: {integrity: sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
-  '@eslint/plugin-kit@0.2.3':
-    resolution: {integrity: sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==}
+  '@eslint/plugin-kit@0.2.4':
+    resolution: {integrity: sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
   '@floating-ui/core@1.6.8':
@@ -589,93 +592,98 @@ packages:
       rollup:
         optional: true
 
-  '@rollup/rollup-android-arm-eabi@4.27.3':
-    resolution: {integrity: sha512-EzxVSkIvCFxUd4Mgm4xR9YXrcp976qVaHnqom/Tgm+vU79k4vV4eYTjmRvGfeoW8m9LVcsAy/lGjcgVegKEhLQ==}
+  '@rollup/rollup-android-arm-eabi@4.28.1':
+    resolution: {integrity: sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==}
     cpu: [arm]
     os: [android]
 
-  '@rollup/rollup-android-arm64@4.27.3':
-    resolution: {integrity: sha512-LJc5pDf1wjlt9o/Giaw9Ofl+k/vLUaYsE2zeQGH85giX2F+wn/Cg8b3c5CDP3qmVmeO5NzwVUzQQxwZvC2eQKw==}
+  '@rollup/rollup-android-arm64@4.28.1':
+    resolution: {integrity: sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==}
     cpu: [arm64]
     os: [android]
 
-  '@rollup/rollup-darwin-arm64@4.27.3':
-    resolution: {integrity: sha512-OuRysZ1Mt7wpWJ+aYKblVbJWtVn3Cy52h8nLuNSzTqSesYw1EuN6wKp5NW/4eSre3mp12gqFRXOKTcN3AI3LqA==}
+  '@rollup/rollup-darwin-arm64@4.28.1':
+    resolution: {integrity: sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==}
     cpu: [arm64]
     os: [darwin]
 
-  '@rollup/rollup-darwin-x64@4.27.3':
-    resolution: {integrity: sha512-xW//zjJMlJs2sOrCmXdB4d0uiilZsOdlGQIC/jjmMWT47lkLLoB1nsNhPUcnoqyi5YR6I4h+FjBpILxbEy8JRg==}
+  '@rollup/rollup-darwin-x64@4.28.1':
+    resolution: {integrity: sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==}
     cpu: [x64]
     os: [darwin]
 
-  '@rollup/rollup-freebsd-arm64@4.27.3':
-    resolution: {integrity: sha512-58E0tIcwZ+12nK1WiLzHOD8I0d0kdrY/+o7yFVPRHuVGY3twBwzwDdTIBGRxLmyjciMYl1B/U515GJy+yn46qw==}
+  '@rollup/rollup-freebsd-arm64@4.28.1':
+    resolution: {integrity: sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==}
     cpu: [arm64]
     os: [freebsd]
 
-  '@rollup/rollup-freebsd-x64@4.27.3':
-    resolution: {integrity: sha512-78fohrpcVwTLxg1ZzBMlwEimoAJmY6B+5TsyAZ3Vok7YabRBUvjYTsRXPTjGEvv/mfgVBepbW28OlMEz4w8wGA==}
+  '@rollup/rollup-freebsd-x64@4.28.1':
+    resolution: {integrity: sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==}
     cpu: [x64]
     os: [freebsd]
 
-  '@rollup/rollup-linux-arm-gnueabihf@4.27.3':
-    resolution: {integrity: sha512-h2Ay79YFXyQi+QZKo3ISZDyKaVD7uUvukEHTOft7kh00WF9mxAaxZsNs3o/eukbeKuH35jBvQqrT61fzKfAB/Q==}
+  '@rollup/rollup-linux-arm-gnueabihf@4.28.1':
+    resolution: {integrity: sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==}
     cpu: [arm]
     os: [linux]
 
-  '@rollup/rollup-linux-arm-musleabihf@4.27.3':
-    resolution: {integrity: sha512-Sv2GWmrJfRY57urktVLQ0VKZjNZGogVtASAgosDZ1aUB+ykPxSi3X1nWORL5Jk0sTIIwQiPH7iE3BMi9zGWfkg==}
+  '@rollup/rollup-linux-arm-musleabihf@4.28.1':
+    resolution: {integrity: sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==}
     cpu: [arm]
     os: [linux]
 
-  '@rollup/rollup-linux-arm64-gnu@4.27.3':
-    resolution: {integrity: sha512-FPoJBLsPW2bDNWjSrwNuTPUt30VnfM8GPGRoLCYKZpPx0xiIEdFip3dH6CqgoT0RnoGXptaNziM0WlKgBc+OWQ==}
+  '@rollup/rollup-linux-arm64-gnu@4.28.1':
+    resolution: {integrity: sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==}
     cpu: [arm64]
     os: [linux]
 
-  '@rollup/rollup-linux-arm64-musl@4.27.3':
-    resolution: {integrity: sha512-TKxiOvBorYq4sUpA0JT+Fkh+l+G9DScnG5Dqx7wiiqVMiRSkzTclP35pE6eQQYjP4Gc8yEkJGea6rz4qyWhp3g==}
+  '@rollup/rollup-linux-arm64-musl@4.28.1':
+    resolution: {integrity: sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==}
     cpu: [arm64]
     os: [linux]
 
-  '@rollup/rollup-linux-powerpc64le-gnu@4.27.3':
-    resolution: {integrity: sha512-v2M/mPvVUKVOKITa0oCFksnQQ/TqGrT+yD0184/cWHIu0LoIuYHwox0Pm3ccXEz8cEQDLk6FPKd1CCm+PlsISw==}
+  '@rollup/rollup-linux-loongarch64-gnu@4.28.1':
+    resolution: {integrity: sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==}
+    cpu: [loong64]
+    os: [linux]
+
+  '@rollup/rollup-linux-powerpc64le-gnu@4.28.1':
+    resolution: {integrity: sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==}
     cpu: [ppc64]
     os: [linux]
 
-  '@rollup/rollup-linux-riscv64-gnu@4.27.3':
-    resolution: {integrity: sha512-LdrI4Yocb1a/tFVkzmOE5WyYRgEBOyEhWYJe4gsDWDiwnjYKjNs7PS6SGlTDB7maOHF4kxevsuNBl2iOcj3b4A==}
+  '@rollup/rollup-linux-riscv64-gnu@4.28.1':
+    resolution: {integrity: sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==}
     cpu: [riscv64]
     os: [linux]
 
-  '@rollup/rollup-linux-s390x-gnu@4.27.3':
-    resolution: {integrity: sha512-d4wVu6SXij/jyiwPvI6C4KxdGzuZOvJ6y9VfrcleHTwo68fl8vZC5ZYHsCVPUi4tndCfMlFniWgwonQ5CUpQcA==}
+  '@rollup/rollup-linux-s390x-gnu@4.28.1':
+    resolution: {integrity: sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==}
     cpu: [s390x]
     os: [linux]
 
-  '@rollup/rollup-linux-x64-gnu@4.27.3':
-    resolution: {integrity: sha512-/6bn6pp1fsCGEY5n3yajmzZQAh+mW4QPItbiWxs69zskBzJuheb3tNynEjL+mKOsUSFK11X4LYF2BwwXnzWleA==}
+  '@rollup/rollup-linux-x64-gnu@4.28.1':
+    resolution: {integrity: sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==}
     cpu: [x64]
     os: [linux]
 
-  '@rollup/rollup-linux-x64-musl@4.27.3':
-    resolution: {integrity: sha512-nBXOfJds8OzUT1qUreT/en3eyOXd2EH5b0wr2bVB5999qHdGKkzGzIyKYaKj02lXk6wpN71ltLIaQpu58YFBoQ==}
+  '@rollup/rollup-linux-x64-musl@4.28.1':
+    resolution: {integrity: sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==}
     cpu: [x64]
     os: [linux]
 
-  '@rollup/rollup-win32-arm64-msvc@4.27.3':
-    resolution: {integrity: sha512-ogfbEVQgIZOz5WPWXF2HVb6En+kWzScuxJo/WdQTqEgeyGkaa2ui5sQav9Zkr7bnNCLK48uxmmK0TySm22eiuw==}
+  '@rollup/rollup-win32-arm64-msvc@4.28.1':
+    resolution: {integrity: sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==}
     cpu: [arm64]
     os: [win32]
 
-  '@rollup/rollup-win32-ia32-msvc@4.27.3':
-    resolution: {integrity: sha512-ecE36ZBMLINqiTtSNQ1vzWc5pXLQHlf/oqGp/bSbi7iedcjcNb6QbCBNG73Euyy2C+l/fn8qKWEwxr+0SSfs3w==}
+  '@rollup/rollup-win32-ia32-msvc@4.28.1':
+    resolution: {integrity: sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==}
     cpu: [ia32]
     os: [win32]
 
-  '@rollup/rollup-win32-x64-msvc@4.27.3':
-    resolution: {integrity: sha512-vliZLrDmYKyaUoMzEbMTg2JkerfBjn03KmAw9CykO0Zzkzoyd7o3iZNam/TpyWNjNT+Cz2iO3P9Smv2wgrR+Eg==}
+  '@rollup/rollup-win32-x64-msvc@4.28.1':
+    resolution: {integrity: sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==}
     cpu: [x64]
     os: [win32]
 
@@ -697,14 +705,14 @@ packages:
     peerDependencies:
       '@sveltejs/kit': ^2.0.0
 
-  '@sveltejs/kit@2.8.1':
-    resolution: {integrity: sha512-uuOfFwZ4xvnfPsiTB6a4H1ljjTUksGhWnYq5X/Y9z4x5+3uM2Md8q/YVeHL+7w+mygAwoEFdgKZ8YkUuk+VKww==}
+  '@sveltejs/kit@2.9.0':
+    resolution: {integrity: sha512-W3E7ed3ChB6kPqRs2H7tcHp+Z7oiTFC6m+lLyAQQuyXeqw6LdNuuwEUla+5VM0OGgqQD+cYD6+7Xq80vVm17Vg==}
     engines: {node: '>=18.13'}
     hasBin: true
     peerDependencies:
-      '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1
+      '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0
       svelte: ^4.0.0 || ^5.0.0-next.0
-      vite: ^5.0.3
+      vite: ^5.0.3 || ^6.0.0
 
   '@sveltejs/vite-plugin-svelte-inspector@3.0.1':
     resolution: {integrity: sha512-2CKypmj1sM4GE7HjllT7UKmo4Q6L5xFRd7VMGEWhYnZ+wc6AUVU01IBd7yUi6WnFndEwWoMNOd6e8UjoN0nbvQ==}
@@ -740,6 +748,9 @@ packages:
   '@tsconfig/svelte@5.0.4':
     resolution: {integrity: sha512-BV9NplVgLmSi4mwKzD8BD/NQ8erOY/nUE/GpgWe2ckx+wIQF5RyRirn/QsSSCPeulVpc3RA/iJt6DpfTIZps0Q==}
 
+  '@types/autosize@4.0.3':
+    resolution: {integrity: sha512-o0ZyU3ePp3+KRbhHsY4ogjc+ZQWgVN5h6j8BHW5RII4cFKi6PEKK9QPAcphJVkD0dGpyFnD3VRR0WMvHVjCv9w==}
+
   '@types/cookie@0.6.0':
     resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
 
@@ -758,8 +769,11 @@ packages:
   '@types/resolve@1.20.2':
     resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
 
-  '@typescript-eslint/eslint-plugin@8.15.0':
-    resolution: {integrity: sha512-+zkm9AR1Ds9uLWN3fkoeXgFppaQ+uEVtfOV62dDmsy9QCNqlRHWNEck4yarvRNrvRcHQLGfqBNui3cimoz8XAg==}
+  '@types/sanitize-html@2.13.0':
+    resolution: {integrity: sha512-X31WxbvW9TjIhZZNyNBZ/p5ax4ti7qsNDBDEnH4zAgmEh35YnFD1UiS6z9Cd34kKm0LslFW0KPmTQzu/oGtsqQ==}
+
+  '@typescript-eslint/eslint-plugin@8.17.0':
+    resolution: {integrity: sha512-HU1KAdW3Tt8zQkdvNoIijfWDMvdSweFYm4hWh+KwhPstv+sCmWb89hCIP8msFm9N1R/ooh9honpSuvqKWlYy3w==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0
@@ -769,8 +783,8 @@ packages:
       typescript:
         optional: true
 
-  '@typescript-eslint/parser@8.15.0':
-    resolution: {integrity: sha512-7n59qFpghG4uazrF9qtGKBZXn7Oz4sOMm8dwNWDQY96Xlm2oX67eipqcblDj+oY1lLCbf1oltMZFpUso66Kl1A==}
+  '@typescript-eslint/parser@8.17.0':
+    resolution: {integrity: sha512-Drp39TXuUlD49F7ilHHCG7TTg8IkA+hxCuULdmzWYICxGXvDXmDmWEjJYZQYgf6l/TFfYNE167m7isnc3xlIEg==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       eslint: ^8.57.0 || ^9.0.0
@@ -779,12 +793,12 @@ packages:
       typescript:
         optional: true
 
-  '@typescript-eslint/scope-manager@8.15.0':
-    resolution: {integrity: sha512-QRGy8ADi4J7ii95xz4UoiymmmMd/zuy9azCaamnZ3FM8T5fZcex8UfJcjkiEZjJSztKfEBe3dZ5T/5RHAmw2mA==}
+  '@typescript-eslint/scope-manager@8.17.0':
+    resolution: {integrity: sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
-  '@typescript-eslint/type-utils@8.15.0':
-    resolution: {integrity: sha512-UU6uwXDoI3JGSXmcdnP5d8Fffa2KayOhUUqr/AiBnG1Gl7+7ut/oyagVeSkh7bxQ0zSXV9ptRh/4N15nkCqnpw==}
+  '@typescript-eslint/type-utils@8.17.0':
+    resolution: {integrity: sha512-q38llWJYPd63rRnJ6wY/ZQqIzPrBCkPdpIsaCfkR3Q4t3p6sb422zougfad4TFW9+ElIFLVDzWGiGAfbb/v2qw==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       eslint: ^8.57.0 || ^9.0.0
@@ -793,12 +807,12 @@ packages:
       typescript:
         optional: true
 
-  '@typescript-eslint/types@8.15.0':
-    resolution: {integrity: sha512-n3Gt8Y/KyJNe0S3yDCD2RVKrHBC4gTUcLTebVBXacPy091E6tNspFLKRXlk3hwT4G55nfr1n2AdFqi/XMxzmPQ==}
+  '@typescript-eslint/types@8.17.0':
+    resolution: {integrity: sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
-  '@typescript-eslint/typescript-estree@8.15.0':
-    resolution: {integrity: sha512-1eMp2JgNec/niZsR7ioFBlsh/Fk0oJbhaqO0jRyQBMgkz7RrFfkqF9lYYmBoGBaSiLnu8TAPQTwoTUiSTUW9dg==}
+  '@typescript-eslint/typescript-estree@8.17.0':
+    resolution: {integrity: sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       typescript: '*'
@@ -806,8 +820,8 @@ packages:
       typescript:
         optional: true
 
-  '@typescript-eslint/utils@8.15.0':
-    resolution: {integrity: sha512-k82RI9yGhr0QM3Dnq+egEpz9qB6Un+WLYhmoNcvl8ltMEededhh7otBVVIDDsEEttauwdY/hQoSsOv13lxrFzQ==}
+  '@typescript-eslint/utils@8.17.0':
+    resolution: {integrity: sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       eslint: ^8.57.0 || ^9.0.0
@@ -816,8 +830,8 @@ packages:
       typescript:
         optional: true
 
-  '@typescript-eslint/visitor-keys@8.15.0':
-    resolution: {integrity: sha512-h8vYOulWec9LhpwfAdZf2bjr8xIp0KNKnpgqSz0qqYYKAW/QZKw3ktRndbiAtUz4acH4QLQavwZBYCc0wulA/Q==}
+  '@typescript-eslint/visitor-keys@8.17.0':
+    resolution: {integrity: sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
   '@zerodevx/svelte-toast@0.9.6':
@@ -886,6 +900,9 @@ packages:
     peerDependencies:
       postcss: ^8.1.0
 
+  autosize@6.0.1:
+    resolution: {integrity: sha512-f86EjiUKE6Xvczc4ioP1JBlWG7FKrE13qe/DxBCpe8GCipCq2nFw73aO8QEBKHfSbYGDN5eB9jXWKen7tspDqQ==}
+
   axios-jwt@4.0.3:
     resolution: {integrity: sha512-8y2lXSG3v/AKgrwT2fjwN0+GRjOVoHJk/dlbAoaUOI9+Ym9xbptkPZd7HS+QdPynzdcDykeiJjQUoruInF+shQ==}
     peerDependencies:
@@ -895,8 +912,8 @@ packages:
       '@react-native-async-storage/async-storage':
         optional: true
 
-  axios@1.7.7:
-    resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==}
+  axios@1.7.9:
+    resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==}
 
   axobject-query@4.1.0:
     resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
@@ -932,16 +949,13 @@ packages:
     resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
     engines: {node: '>= 6'}
 
-  caniuse-lite@1.0.30001680:
-    resolution: {integrity: sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==}
+  caniuse-lite@1.0.30001687:
+    resolution: {integrity: sha512-0S/FDhf4ZiqrTUiQ39dKeUjYRjkv7lOZU1Dgif2rIqrTzX/1wV2hfKu9TOm1IHkdSijfLswxTFzl/cvir+SLSQ==}
 
   chalk@4.1.2:
     resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
     engines: {node: '>=10'}
 
-  charenc@0.0.2:
-    resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==}
-
   chokidar@3.6.0:
     resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
     engines: {node: '>= 8.10.0'}
@@ -986,9 +1000,6 @@ packages:
     resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
     engines: {node: '>= 8'}
 
-  crypt@0.0.2:
-    resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==}
-
   css-selector-tokenizer@0.8.0:
     resolution: {integrity: sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==}
 
@@ -1012,8 +1023,8 @@ packages:
   dayjs@1.11.13:
     resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
 
-  debug@4.3.7:
-    resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
+  debug@4.4.0:
+    resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
     engines: {node: '>=6.0'}
     peerDependencies:
       supports-color: '*'
@@ -1057,11 +1068,11 @@ packages:
   eastasianwidth@0.2.0:
     resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
 
-  electron-to-chromium@1.5.63:
-    resolution: {integrity: sha512-ddeXKuY9BHo/mw145axlyWjlJ1UBt4WK3AlvkT7W2AbqfRQoacVoRUCF6wL3uIx/8wT9oLKXzI+rFqHHscByaA==}
+  electron-to-chromium@1.5.71:
+    resolution: {integrity: sha512-dB68l59BI75W1BUGVTAEJy45CEVuEGy9qPVVQ8pnHyHMn36PLPPoE1mjLH+lo9rKulO3HC2OhbACI/8tCqJBcA==}
 
-  emoji-picker-element@1.24.0:
-    resolution: {integrity: sha512-dIUJ6ZXult6lsnDxG0Y0YLUhZ49mzImpg9pZjPoYFRP9wzx65y5ypS5W8I8SOyIOp4rgKZXB0fma1i7NbRJj0A==}
+  emoji-picker-element@1.25.0:
+    resolution: {integrity: sha512-UcUMxqIuneLCsEJ5KpqTD1xaHZyUpg6Oa7uCVe5AMXXpsW3C2TNegbNLXj2/rlbyr6qVMf7lXTFyzvFEarOIUg==}
 
   emoji-regex@8.0.0:
     resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@@ -1121,8 +1132,8 @@ packages:
     peerDependencies:
       eslint: '>=7.0.0'
 
-  eslint-plugin-svelte@2.46.0:
-    resolution: {integrity: sha512-1A7iEMkzmCZ9/Iz+EAfOGYL8IoIG6zeKEq1SmpxGeM5SXmoQq+ZNnCpXFVJpsxPWYx8jIVGMerQMzX20cqUl0g==}
+  eslint-plugin-svelte@2.46.1:
+    resolution: {integrity: sha512-7xYr2o4NID/f9OEYMqxsEQsCsj4KaMy4q5sANaKkAb6/QeCjYFxRmDm2S3YC3A3pl1kyPZ/syOx/i7LcWYSbIw==}
     engines: {node: ^14.17.0 || >=16.0.0}
     peerDependencies:
       eslint: ^7.0.0 || ^8.0.0-0 || ^9.0.0-0
@@ -1147,8 +1158,8 @@ packages:
     resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
-  eslint@9.15.0:
-    resolution: {integrity: sha512-7CrWySmIibCgT1Os28lUU6upBshZ+GxybLOrmRzi08kS8MBuO8QA7pXEgYgY5W8vK3e74xv0lpjo9DbaGU9Rkw==}
+  eslint@9.16.0:
+    resolution: {integrity: sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     hasBin: true
     peerDependencies:
@@ -1344,9 +1355,6 @@ packages:
     resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
     engines: {node: '>=8'}
 
-  is-buffer@1.1.6:
-    resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==}
-
   is-core-module@2.15.1:
     resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==}
     engines: {node: '>= 0.4'}
@@ -1396,6 +1404,9 @@ packages:
     resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==}
     hasBin: true
 
+  js-sha256@0.11.0:
+    resolution: {integrity: sha512-6xNlKayMZvds9h1Y1VWc0fQHQ82BxTXizWPEtEeGvmOUYpBRy4gbWroHLpzowe6xiQhHpelCQiE7HEdznyBL9Q==}
+
   js-yaml@4.1.0:
     resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
     hasBin: true
@@ -1426,8 +1437,8 @@ packages:
   known-css-properties@0.35.0:
     resolution: {integrity: sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==}
 
-  less@4.2.0:
-    resolution: {integrity: sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==}
+  less@4.2.1:
+    resolution: {integrity: sha512-CasaJidTIhWmjcqv0Uj5vccMI7pJgfD9lMkKtlnTHAdJdYK/7l8pM9tumLyJ0zhbD4KJLo/YvTj+xznQd5NBhg==}
     engines: {node: '>=6'}
     hasBin: true
 
@@ -1439,20 +1450,20 @@ packages:
     resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==}
     engines: {node: '>=10'}
 
-  lilconfig@3.1.2:
-    resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==}
+  lilconfig@3.1.3:
+    resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
     engines: {node: '>=14'}
 
   lines-and-columns@1.2.4:
     resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
 
-  linkify-html@4.1.4:
-    resolution: {integrity: sha512-6HA+YB++HrtPrSi+3gVEc0xdEGmqwLGErV0yagQta2bxfDOppjBHgadddpHWPR76LgB/Tiax22VfJtTqxkoijA==}
+  linkify-html@4.2.0:
+    resolution: {integrity: sha512-bVXuLiWmGwvlH95hq6q9DFGqTsQeFSGw/nHmvvjGMZv9T3GqkxuW2d2SOgk/a4DV2ajeS4c37EqlF16cjOj7GA==}
     peerDependencies:
       linkifyjs: ^4.0.0
 
-  linkifyjs@4.1.4:
-    resolution: {integrity: sha512-0/NxkHNpiJ0k9VrYCkAn9OtU1eu8xEr1tCCpDtSsVRm/SF0xAak2Gzv3QimSfgUgqLBCDlfhMbu73XvaEHUTPQ==}
+  linkifyjs@4.2.0:
+    resolution: {integrity: sha512-pCj3PrQyATaoTYKHrgWRF3SJwsm61udVh+vuls/Rl6SptiDhgE7ziUIudAedRY9QEfynmM7/RmLEfPUyw1HPCw==}
 
   locate-character@3.0.0:
     resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==}
@@ -1470,16 +1481,13 @@ packages:
   lru-queue@0.1.0:
     resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==}
 
-  magic-string@0.30.13:
-    resolution: {integrity: sha512-8rYBO+MsWkgjDSOvLomYnzhdwEG51olQ4zL5KXnNJWV5MNmrb4rTZdrtkhxjnD/QyZUqR/Z/XDsUs/4ej2nx0g==}
+  magic-string@0.30.14:
+    resolution: {integrity: sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==}
 
   make-dir@2.1.0:
     resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==}
     engines: {node: '>=6'}
 
-  md5@2.3.0:
-    resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==}
-
   memoizee@0.4.17:
     resolution: {integrity: sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==}
     engines: {node: '>=0.12'}
@@ -1538,8 +1546,8 @@ packages:
   mz@2.7.0:
     resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
 
-  nanoid@3.3.7:
-    resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
+  nanoid@3.3.8:
+    resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==}
     engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
     hasBin: true
 
@@ -1706,14 +1714,14 @@ packages:
     resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
     engines: {node: '>= 0.8.0'}
 
-  prettier-plugin-svelte@3.2.8:
-    resolution: {integrity: sha512-PAHmmU5cGZdnhW4mWhmvxuG2PVbbHIxUuPOdUKvfE+d4Qt2d29iU5VWrPdsaW5YqVEE0nqhlvN4eoKmVMpIF3Q==}
+  prettier-plugin-svelte@3.3.2:
+    resolution: {integrity: sha512-kRPjH8wSj2iu+dO+XaUv4vD8qr5mdDmlak3IT/7AOgGIMRG86z/EHOLauFcClKEnOUf4A4nOA7sre5KrJD4Raw==}
     peerDependencies:
       prettier: ^3.0.0
       svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0
 
-  prettier@3.3.3:
-    resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==}
+  prettier@3.4.2:
+    resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==}
     engines: {node: '>=14'}
     hasBin: true
 
@@ -1753,8 +1761,8 @@ packages:
     resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
     engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
 
-  rollup@4.27.3:
-    resolution: {integrity: sha512-SLsCOnlmGt9VoZ9Ek8yBK8tAdmPHeppkw+Xa7yDlCEhDTvwYei03JlWo1fdc7YTfLZ4tD8riJCUyAgTbszk1fQ==}
+  rollup@4.28.1:
+    resolution: {integrity: sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==}
     engines: {node: '>=18.0.0', npm: '>=8.0.0'}
     hasBin: true
 
@@ -1843,8 +1851,13 @@ packages:
     resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
     engines: {node: '>= 0.4'}
 
-  svelte-check@4.0.9:
-    resolution: {integrity: sha512-SVNCz2L+9ZELGli7G0n3B3QE5kdf0u27RtKr2ZivWQhcWIXatZxwM4VrQ6AiA2k9zKp2mk5AxkEhdjbpjv7rEw==}
+  svelte-autosize@1.1.5:
+    resolution: {integrity: sha512-whiND/GthFDG9Ansvil21qxmFhSMfuooVZPg40sbcLHYKR9srYhnfrP5qdw8MXHAm6DY9g5PawurOAWl34fK7g==}
+    peerDependencies:
+      svelte: '>=3.0.0'
+
+  svelte-check@4.1.1:
+    resolution: {integrity: sha512-NfaX+6Qtc8W/CyVGS/F7/XdiSSyXz+WGYA9ZWV3z8tso14V2vzjfXviKaTFEzB7g8TqfgO2FOzP6XT4ApSTUTw==}
     engines: {node: '>= 18.0.0'}
     hasBin: true
     peerDependencies:
@@ -1863,11 +1876,6 @@ packages:
   svelte-floating-ui@1.5.8:
     resolution: {integrity: sha512-dVvJhZ2bT+kQDHlE4Lep8t+sgEc0XD96fXLzAi2DDI2bsaegBbClxXVNMma0C2WsG+n9GJSYx292dTvA8CYRtw==}
 
-  svelte-gravatar@1.0.3:
-    resolution: {integrity: sha512-CNxIV2lAuiqwdaPrGAM/BFj5U1dNNQXzeyh+HVi/48BODFXoDy0L1CMqYyvM+aKiF4ideZUBwT0S9/C1BeL5oA==}
-    peerDependencies:
-      svelte: '*'
-
   svelte-hero-icons@5.2.0:
     resolution: {integrity: sha512-KpdMTL0bOnkxciEmDXvyVF/R5nrZ1x1uHCSt9gMrrbEd3g5HSIaaDChOutTOfeI+cZ3EJbb+OcBH/lBzJr1aEw==}
     engines: {node: '>=18.0.0'}
@@ -1921,9 +1929,6 @@ packages:
   svelte-select@5.8.3:
     resolution: {integrity: sha512-nQsvflWmTCOZjssdrNptzfD1Ok45hHVMTL5IHay5DINk7dfu5Er+8KsVJnZMJdSircqtR0YlT4YkCFlxOUhVPA==}
 
-  svelte-waypoint@0.1.4:
-    resolution: {integrity: sha512-UEqoXZjJeKj2sWlAIsBOFjxjMn+KP8aFCc/zjdmZi1cCOE59z6T2C+I6ZaAf8EmNQqNzfZVB/Lci4Ci9spzXAw==}
-
   svelte@5.8.1:
     resolution: {integrity: sha512-tqJY46Xoe+KiKvD4/guNlqpE+jco4IBcuaM6Ei9SEMETtsbLMfbak9XjTacqd6aGMmWXh7uFInfFTd4yES5r0A==}
     engines: {node: '>=18'}
@@ -1933,8 +1938,8 @@ packages:
     peerDependencies:
       svelte: '>=3.49.0'
 
-  tailwindcss@3.4.15:
-    resolution: {integrity: sha512-r4MeXnfBmSOuKUWmXe6h2CcyfzJCEk4F0pptO5jlnYSIViUkVmsawj80N5h2lO3gwcmSb4n3PuN+e+GC1Guylw==}
+  tailwindcss@3.4.16:
+    resolution: {integrity: sha512-TI4Cyx7gDiZ6r44ewaJmt0o6BrMCT5aK5e0rmJ/G9Xq3w7CX/5VXl/zIPEJZFUK5VEqwByyhqNPycPlvcK4ZNw==}
     engines: {node: '>=14.0.0'}
     hasBin: true
 
@@ -1960,8 +1965,8 @@ packages:
     resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
     engines: {node: '>=6'}
 
-  ts-api-utils@1.4.0:
-    resolution: {integrity: sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==}
+  ts-api-utils@1.4.3:
+    resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==}
     engines: {node: '>=16'}
     peerDependencies:
       typescript: '>=4.2.0'
@@ -1979,8 +1984,8 @@ packages:
   type@2.7.3:
     resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==}
 
-  typescript@5.6.3:
-    resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==}
+  typescript@5.7.2:
+    resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==}
     engines: {node: '>=14.17'}
     hasBin: true
 
@@ -2215,27 +2220,29 @@ snapshots:
   '@esbuild/win32-x64@0.21.5':
     optional: true
 
-  '@eslint-community/eslint-utils@4.4.1(eslint@9.15.0(jiti@1.21.6))':
+  '@eslint-community/eslint-utils@4.4.1(eslint@9.16.0(jiti@1.21.6))':
     dependencies:
-      eslint: 9.15.0(jiti@1.21.6)
+      eslint: 9.16.0(jiti@1.21.6)
       eslint-visitor-keys: 3.4.3
 
   '@eslint-community/regexpp@4.12.1': {}
 
-  '@eslint/config-array@0.19.0':
+  '@eslint/config-array@0.19.1':
     dependencies:
-      '@eslint/object-schema': 2.1.4
-      debug: 4.3.7
+      '@eslint/object-schema': 2.1.5
+      debug: 4.4.0
       minimatch: 3.1.2
     transitivePeerDependencies:
       - supports-color
 
-  '@eslint/core@0.9.0': {}
+  '@eslint/core@0.9.1':
+    dependencies:
+      '@types/json-schema': 7.0.15
 
   '@eslint/eslintrc@3.2.0':
     dependencies:
       ajv: 6.12.6
-      debug: 4.3.7
+      debug: 4.4.0
       espree: 10.3.0
       globals: 14.0.0
       ignore: 5.3.2
@@ -2246,11 +2253,11 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@eslint/js@9.15.0': {}
+  '@eslint/js@9.16.0': {}
 
-  '@eslint/object-schema@2.1.4': {}
+  '@eslint/object-schema@2.1.5': {}
 
-  '@eslint/plugin-kit@0.2.3':
+  '@eslint/plugin-kit@0.2.4':
     dependencies:
       levn: 0.4.1
 
@@ -2346,152 +2353,155 @@ snapshots:
 
   '@polka/url@1.0.0-next.28': {}
 
-  '@rollup/plugin-commonjs@28.0.1(rollup@4.27.3)':
+  '@rollup/plugin-commonjs@28.0.1(rollup@4.28.1)':
     dependencies:
-      '@rollup/pluginutils': 5.1.3(rollup@4.27.3)
+      '@rollup/pluginutils': 5.1.3(rollup@4.28.1)
       commondir: 1.0.1
       estree-walker: 2.0.2
       fdir: 6.4.2(picomatch@4.0.2)
       is-reference: 1.2.1
-      magic-string: 0.30.13
+      magic-string: 0.30.14
       picomatch: 4.0.2
     optionalDependencies:
-      rollup: 4.27.3
+      rollup: 4.28.1
 
-  '@rollup/plugin-json@6.1.0(rollup@4.27.3)':
+  '@rollup/plugin-json@6.1.0(rollup@4.28.1)':
     dependencies:
-      '@rollup/pluginutils': 5.1.3(rollup@4.27.3)
+      '@rollup/pluginutils': 5.1.3(rollup@4.28.1)
     optionalDependencies:
-      rollup: 4.27.3
+      rollup: 4.28.1
 
-  '@rollup/plugin-node-resolve@15.3.0(rollup@4.27.3)':
+  '@rollup/plugin-node-resolve@15.3.0(rollup@4.28.1)':
     dependencies:
-      '@rollup/pluginutils': 5.1.3(rollup@4.27.3)
+      '@rollup/pluginutils': 5.1.3(rollup@4.28.1)
       '@types/resolve': 1.20.2
       deepmerge: 4.3.1
       is-module: 1.0.0
       resolve: 1.22.8
     optionalDependencies:
-      rollup: 4.27.3
+      rollup: 4.28.1
 
-  '@rollup/pluginutils@5.1.3(rollup@4.27.3)':
+  '@rollup/pluginutils@5.1.3(rollup@4.28.1)':
     dependencies:
       '@types/estree': 1.0.6
       estree-walker: 2.0.2
       picomatch: 4.0.2
     optionalDependencies:
-      rollup: 4.27.3
+      rollup: 4.28.1
 
-  '@rollup/rollup-android-arm-eabi@4.27.3':
+  '@rollup/rollup-android-arm-eabi@4.28.1':
     optional: true
 
-  '@rollup/rollup-android-arm64@4.27.3':
+  '@rollup/rollup-android-arm64@4.28.1':
     optional: true
 
-  '@rollup/rollup-darwin-arm64@4.27.3':
+  '@rollup/rollup-darwin-arm64@4.28.1':
     optional: true
 
-  '@rollup/rollup-darwin-x64@4.27.3':
+  '@rollup/rollup-darwin-x64@4.28.1':
     optional: true
 
-  '@rollup/rollup-freebsd-arm64@4.27.3':
+  '@rollup/rollup-freebsd-arm64@4.28.1':
     optional: true
 
-  '@rollup/rollup-freebsd-x64@4.27.3':
+  '@rollup/rollup-freebsd-x64@4.28.1':
     optional: true
 
-  '@rollup/rollup-linux-arm-gnueabihf@4.27.3':
+  '@rollup/rollup-linux-arm-gnueabihf@4.28.1':
     optional: true
 
-  '@rollup/rollup-linux-arm-musleabihf@4.27.3':
+  '@rollup/rollup-linux-arm-musleabihf@4.28.1':
     optional: true
 
-  '@rollup/rollup-linux-arm64-gnu@4.27.3':
+  '@rollup/rollup-linux-arm64-gnu@4.28.1':
     optional: true
 
-  '@rollup/rollup-linux-arm64-musl@4.27.3':
+  '@rollup/rollup-linux-arm64-musl@4.28.1':
     optional: true
 
-  '@rollup/rollup-linux-powerpc64le-gnu@4.27.3':
+  '@rollup/rollup-linux-loongarch64-gnu@4.28.1':
     optional: true
 
-  '@rollup/rollup-linux-riscv64-gnu@4.27.3':
+  '@rollup/rollup-linux-powerpc64le-gnu@4.28.1':
     optional: true
 
-  '@rollup/rollup-linux-s390x-gnu@4.27.3':
+  '@rollup/rollup-linux-riscv64-gnu@4.28.1':
     optional: true
 
-  '@rollup/rollup-linux-x64-gnu@4.27.3':
+  '@rollup/rollup-linux-s390x-gnu@4.28.1':
     optional: true
 
-  '@rollup/rollup-linux-x64-musl@4.27.3':
+  '@rollup/rollup-linux-x64-gnu@4.28.1':
     optional: true
 
-  '@rollup/rollup-win32-arm64-msvc@4.27.3':
+  '@rollup/rollup-linux-x64-musl@4.28.1':
     optional: true
 
-  '@rollup/rollup-win32-ia32-msvc@4.27.3':
+  '@rollup/rollup-win32-arm64-msvc@4.28.1':
     optional: true
 
-  '@rollup/rollup-win32-x64-msvc@4.27.3':
+  '@rollup/rollup-win32-ia32-msvc@4.28.1':
+    optional: true
+
+  '@rollup/rollup-win32-x64-msvc@4.28.1':
     optional: true
 
   '@steeze-ui/heroicons@2.4.2': {}
 
-  '@sveltejs/adapter-auto@3.3.1(@sveltejs/kit@2.8.1(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.0)))(svelte@5.8.1)(vite@5.4.11(less@4.2.0)))':
+  '@sveltejs/adapter-auto@3.3.1(@sveltejs/kit@2.9.0(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.1)))(svelte@5.8.1)(vite@5.4.11(less@4.2.1)))':
     dependencies:
-      '@sveltejs/kit': 2.8.1(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.0)))(svelte@5.8.1)(vite@5.4.11(less@4.2.0))
+      '@sveltejs/kit': 2.9.0(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.1)))(svelte@5.8.1)(vite@5.4.11(less@4.2.1))
       import-meta-resolve: 4.1.0
 
-  '@sveltejs/adapter-node@5.2.9(@sveltejs/kit@2.8.1(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.0)))(svelte@5.8.1)(vite@5.4.11(less@4.2.0)))':
+  '@sveltejs/adapter-node@5.2.9(@sveltejs/kit@2.9.0(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.1)))(svelte@5.8.1)(vite@5.4.11(less@4.2.1)))':
     dependencies:
-      '@rollup/plugin-commonjs': 28.0.1(rollup@4.27.3)
-      '@rollup/plugin-json': 6.1.0(rollup@4.27.3)
-      '@rollup/plugin-node-resolve': 15.3.0(rollup@4.27.3)
-      '@sveltejs/kit': 2.8.1(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.0)))(svelte@5.8.1)(vite@5.4.11(less@4.2.0))
-      rollup: 4.27.3
+      '@rollup/plugin-commonjs': 28.0.1(rollup@4.28.1)
+      '@rollup/plugin-json': 6.1.0(rollup@4.28.1)
+      '@rollup/plugin-node-resolve': 15.3.0(rollup@4.28.1)
+      '@sveltejs/kit': 2.9.0(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.1)))(svelte@5.8.1)(vite@5.4.11(less@4.2.1))
+      rollup: 4.28.1
 
-  '@sveltejs/adapter-static@3.0.6(@sveltejs/kit@2.8.1(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.0)))(svelte@5.8.1)(vite@5.4.11(less@4.2.0)))':
+  '@sveltejs/adapter-static@3.0.6(@sveltejs/kit@2.9.0(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.1)))(svelte@5.8.1)(vite@5.4.11(less@4.2.1)))':
     dependencies:
-      '@sveltejs/kit': 2.8.1(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.0)))(svelte@5.8.1)(vite@5.4.11(less@4.2.0))
+      '@sveltejs/kit': 2.9.0(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.1)))(svelte@5.8.1)(vite@5.4.11(less@4.2.1))
 
-  '@sveltejs/kit@2.8.1(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.0)))(svelte@5.8.1)(vite@5.4.11(less@4.2.0))':
+  '@sveltejs/kit@2.9.0(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.1)))(svelte@5.8.1)(vite@5.4.11(less@4.2.1))':
     dependencies:
-      '@sveltejs/vite-plugin-svelte': 4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.0))
+      '@sveltejs/vite-plugin-svelte': 4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.1))
       '@types/cookie': 0.6.0
       cookie: 0.6.0
       devalue: 5.1.1
       esm-env: 1.2.1
       import-meta-resolve: 4.1.0
       kleur: 4.1.5
-      magic-string: 0.30.13
+      magic-string: 0.30.14
       mrmime: 2.0.0
       sade: 1.8.1
       set-cookie-parser: 2.7.1
       sirv: 3.0.0
       svelte: 5.8.1
       tiny-glob: 0.2.9
-      vite: 5.4.11(less@4.2.0)
+      vite: 5.4.11(less@4.2.1)
 
-  '@sveltejs/vite-plugin-svelte-inspector@3.0.1(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.0)))(svelte@5.8.1)(vite@5.4.11(less@4.2.0))':
+  '@sveltejs/vite-plugin-svelte-inspector@3.0.1(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.1)))(svelte@5.8.1)(vite@5.4.11(less@4.2.1))':
     dependencies:
-      '@sveltejs/vite-plugin-svelte': 4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.0))
-      debug: 4.3.7
+      '@sveltejs/vite-plugin-svelte': 4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.1))
+      debug: 4.4.0
       svelte: 5.8.1
-      vite: 5.4.11(less@4.2.0)
+      vite: 5.4.11(less@4.2.1)
     transitivePeerDependencies:
       - supports-color
 
-  '@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.0))':
+  '@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.1))':
     dependencies:
-      '@sveltejs/vite-plugin-svelte-inspector': 3.0.1(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.0)))(svelte@5.8.1)(vite@5.4.11(less@4.2.0))
-      debug: 4.3.7
+      '@sveltejs/vite-plugin-svelte-inspector': 3.0.1(@sveltejs/vite-plugin-svelte@4.0.2(svelte@5.8.1)(vite@5.4.11(less@4.2.1)))(svelte@5.8.1)(vite@5.4.11(less@4.2.1))
+      debug: 4.4.0
       deepmerge: 4.3.1
       kleur: 4.1.5
-      magic-string: 0.30.13
+      magic-string: 0.30.14
       svelte: 5.8.1
-      vite: 5.4.11(less@4.2.0)
-      vitefu: 1.0.4(vite@5.4.11(less@4.2.0))
+      vite: 5.4.11(less@4.2.1)
+      vitefu: 1.0.4(vite@5.4.11(less@4.2.1))
     transitivePeerDependencies:
       - supports-color
 
@@ -2505,13 +2515,15 @@ snapshots:
     dependencies:
       intl-messageformat: 10.7.7
 
-  '@tailwindcss/forms@0.5.9(tailwindcss@3.4.15)':
+  '@tailwindcss/forms@0.5.9(tailwindcss@3.4.16)':
     dependencies:
       mini-svg-data-uri: 1.4.4
-      tailwindcss: 3.4.15
+      tailwindcss: 3.4.16
 
   '@tsconfig/svelte@5.0.4': {}
 
+  '@types/autosize@4.0.3': {}
+
   '@types/cookie@0.6.0': {}
 
   '@types/eslint@9.6.1':
@@ -2527,86 +2539,90 @@ snapshots:
 
   '@types/resolve@1.20.2': {}
 
-  '@typescript-eslint/eslint-plugin@8.15.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)':
+  '@types/sanitize-html@2.13.0':
+    dependencies:
+      htmlparser2: 8.0.2
+
+  '@typescript-eslint/eslint-plugin@8.17.0(@typescript-eslint/parser@8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)':
     dependencies:
       '@eslint-community/regexpp': 4.12.1
-      '@typescript-eslint/parser': 8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)
-      '@typescript-eslint/scope-manager': 8.15.0
-      '@typescript-eslint/type-utils': 8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)
-      '@typescript-eslint/utils': 8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)
-      '@typescript-eslint/visitor-keys': 8.15.0
-      eslint: 9.15.0(jiti@1.21.6)
+      '@typescript-eslint/parser': 8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)
+      '@typescript-eslint/scope-manager': 8.17.0
+      '@typescript-eslint/type-utils': 8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)
+      '@typescript-eslint/utils': 8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)
+      '@typescript-eslint/visitor-keys': 8.17.0
+      eslint: 9.16.0(jiti@1.21.6)
       graphemer: 1.4.0
       ignore: 5.3.2
       natural-compare: 1.4.0
-      ts-api-utils: 1.4.0(typescript@5.6.3)
+      ts-api-utils: 1.4.3(typescript@5.7.2)
     optionalDependencies:
-      typescript: 5.6.3
+      typescript: 5.7.2
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)':
+  '@typescript-eslint/parser@8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)':
     dependencies:
-      '@typescript-eslint/scope-manager': 8.15.0
-      '@typescript-eslint/types': 8.15.0
-      '@typescript-eslint/typescript-estree': 8.15.0(typescript@5.6.3)
-      '@typescript-eslint/visitor-keys': 8.15.0
-      debug: 4.3.7
-      eslint: 9.15.0(jiti@1.21.6)
+      '@typescript-eslint/scope-manager': 8.17.0
+      '@typescript-eslint/types': 8.17.0
+      '@typescript-eslint/typescript-estree': 8.17.0(typescript@5.7.2)
+      '@typescript-eslint/visitor-keys': 8.17.0
+      debug: 4.4.0
+      eslint: 9.16.0(jiti@1.21.6)
     optionalDependencies:
-      typescript: 5.6.3
+      typescript: 5.7.2
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/scope-manager@8.15.0':
+  '@typescript-eslint/scope-manager@8.17.0':
     dependencies:
-      '@typescript-eslint/types': 8.15.0
-      '@typescript-eslint/visitor-keys': 8.15.0
+      '@typescript-eslint/types': 8.17.0
+      '@typescript-eslint/visitor-keys': 8.17.0
 
-  '@typescript-eslint/type-utils@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)':
+  '@typescript-eslint/type-utils@8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)':
     dependencies:
-      '@typescript-eslint/typescript-estree': 8.15.0(typescript@5.6.3)
-      '@typescript-eslint/utils': 8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)
-      debug: 4.3.7
-      eslint: 9.15.0(jiti@1.21.6)
-      ts-api-utils: 1.4.0(typescript@5.6.3)
+      '@typescript-eslint/typescript-estree': 8.17.0(typescript@5.7.2)
+      '@typescript-eslint/utils': 8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)
+      debug: 4.4.0
+      eslint: 9.16.0(jiti@1.21.6)
+      ts-api-utils: 1.4.3(typescript@5.7.2)
     optionalDependencies:
-      typescript: 5.6.3
+      typescript: 5.7.2
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/types@8.15.0': {}
+  '@typescript-eslint/types@8.17.0': {}
 
-  '@typescript-eslint/typescript-estree@8.15.0(typescript@5.6.3)':
+  '@typescript-eslint/typescript-estree@8.17.0(typescript@5.7.2)':
     dependencies:
-      '@typescript-eslint/types': 8.15.0
-      '@typescript-eslint/visitor-keys': 8.15.0
-      debug: 4.3.7
+      '@typescript-eslint/types': 8.17.0
+      '@typescript-eslint/visitor-keys': 8.17.0
+      debug: 4.4.0
       fast-glob: 3.3.2
       is-glob: 4.0.3
       minimatch: 9.0.5
       semver: 7.6.3
-      ts-api-utils: 1.4.0(typescript@5.6.3)
+      ts-api-utils: 1.4.3(typescript@5.7.2)
     optionalDependencies:
-      typescript: 5.6.3
+      typescript: 5.7.2
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/utils@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)':
+  '@typescript-eslint/utils@8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)':
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0(jiti@1.21.6))
-      '@typescript-eslint/scope-manager': 8.15.0
-      '@typescript-eslint/types': 8.15.0
-      '@typescript-eslint/typescript-estree': 8.15.0(typescript@5.6.3)
-      eslint: 9.15.0(jiti@1.21.6)
+      '@eslint-community/eslint-utils': 4.4.1(eslint@9.16.0(jiti@1.21.6))
+      '@typescript-eslint/scope-manager': 8.17.0
+      '@typescript-eslint/types': 8.17.0
+      '@typescript-eslint/typescript-estree': 8.17.0(typescript@5.7.2)
+      eslint: 9.16.0(jiti@1.21.6)
     optionalDependencies:
-      typescript: 5.6.3
+      typescript: 5.7.2
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/visitor-keys@8.15.0':
+  '@typescript-eslint/visitor-keys@8.17.0':
     dependencies:
-      '@typescript-eslint/types': 8.15.0
+      '@typescript-eslint/types': 8.17.0
       eslint-visitor-keys: 4.2.0
 
   '@zerodevx/svelte-toast@0.9.6(svelte@5.8.1)':
@@ -2658,20 +2674,22 @@ snapshots:
   autoprefixer@10.4.20(postcss@8.4.49):
     dependencies:
       browserslist: 4.24.2
-      caniuse-lite: 1.0.30001680
+      caniuse-lite: 1.0.30001687
       fraction.js: 4.3.7
       normalize-range: 0.1.2
       picocolors: 1.1.1
       postcss: 8.4.49
       postcss-value-parser: 4.2.0
 
-  axios-jwt@4.0.3(axios@1.7.7):
+  autosize@6.0.1: {}
+
+  axios-jwt@4.0.3(axios@1.7.9):
     dependencies:
-      axios: 1.7.7
+      axios: 1.7.9
       jwt-decode: 3.1.2
       ms: 3.0.0-canary.1
 
-  axios@1.7.7:
+  axios@1.7.9:
     dependencies:
       follow-redirects: 1.15.9
       form-data: 4.0.1
@@ -2700,8 +2718,8 @@ snapshots:
 
   browserslist@4.24.2:
     dependencies:
-      caniuse-lite: 1.0.30001680
-      electron-to-chromium: 1.5.63
+      caniuse-lite: 1.0.30001687
+      electron-to-chromium: 1.5.71
       node-releases: 2.0.18
       update-browserslist-db: 1.1.1(browserslist@4.24.2)
 
@@ -2709,15 +2727,13 @@ snapshots:
 
   camelcase-css@2.0.1: {}
 
-  caniuse-lite@1.0.30001680: {}
+  caniuse-lite@1.0.30001687: {}
 
   chalk@4.1.2:
     dependencies:
       ansi-styles: 4.3.0
       supports-color: 7.2.0
 
-  charenc@0.0.2: {}
-
   chokidar@3.6.0:
     dependencies:
       anymatch: 3.1.3
@@ -2770,8 +2786,6 @@ snapshots:
       shebang-command: 2.0.0
       which: 2.0.2
 
-  crypt@0.0.2: {}
-
   css-selector-tokenizer@0.8.0:
     dependencies:
       cssesc: 3.0.0
@@ -2797,7 +2811,7 @@ snapshots:
 
   dayjs@1.11.13: {}
 
-  debug@4.3.7:
+  debug@4.4.0:
     dependencies:
       ms: 2.1.3
 
@@ -2833,9 +2847,9 @@ snapshots:
 
   eastasianwidth@0.2.0: {}
 
-  electron-to-chromium@1.5.63: {}
+  electron-to-chromium@1.5.71: {}
 
-  emoji-picker-element@1.24.0: {}
+  emoji-picker-element@1.25.0: {}
 
   emoji-regex@8.0.0: {}
 
@@ -2929,21 +2943,21 @@ snapshots:
 
   escape-string-regexp@4.0.0: {}
 
-  eslint-compat-utils@0.5.1(eslint@9.15.0(jiti@1.21.6)):
+  eslint-compat-utils@0.5.1(eslint@9.16.0(jiti@1.21.6)):
     dependencies:
-      eslint: 9.15.0(jiti@1.21.6)
+      eslint: 9.16.0(jiti@1.21.6)
       semver: 7.6.3
 
-  eslint-config-prettier@9.1.0(eslint@9.15.0(jiti@1.21.6)):
+  eslint-config-prettier@9.1.0(eslint@9.16.0(jiti@1.21.6)):
     dependencies:
-      eslint: 9.15.0(jiti@1.21.6)
+      eslint: 9.16.0(jiti@1.21.6)
 
-  eslint-plugin-svelte@2.46.0(eslint@9.15.0(jiti@1.21.6))(svelte@5.8.1):
+  eslint-plugin-svelte@2.46.1(eslint@9.16.0(jiti@1.21.6))(svelte@5.8.1):
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0(jiti@1.21.6))
+      '@eslint-community/eslint-utils': 4.4.1(eslint@9.16.0(jiti@1.21.6))
       '@jridgewell/sourcemap-codec': 1.5.0
-      eslint: 9.15.0(jiti@1.21.6)
-      eslint-compat-utils: 0.5.1(eslint@9.15.0(jiti@1.21.6))
+      eslint: 9.16.0(jiti@1.21.6)
+      eslint-compat-utils: 0.5.1(eslint@9.16.0(jiti@1.21.6))
       esutils: 2.0.3
       known-css-properties: 0.35.0
       postcss: 8.4.49
@@ -2971,15 +2985,15 @@ snapshots:
 
   eslint-visitor-keys@4.2.0: {}
 
-  eslint@9.15.0(jiti@1.21.6):
+  eslint@9.16.0(jiti@1.21.6):
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0(jiti@1.21.6))
+      '@eslint-community/eslint-utils': 4.4.1(eslint@9.16.0(jiti@1.21.6))
       '@eslint-community/regexpp': 4.12.1
-      '@eslint/config-array': 0.19.0
-      '@eslint/core': 0.9.0
+      '@eslint/config-array': 0.19.1
+      '@eslint/core': 0.9.1
       '@eslint/eslintrc': 3.2.0
-      '@eslint/js': 9.15.0
-      '@eslint/plugin-kit': 0.2.3
+      '@eslint/js': 9.16.0
+      '@eslint/plugin-kit': 0.2.4
       '@humanfs/node': 0.16.6
       '@humanwhocodes/module-importer': 1.0.1
       '@humanwhocodes/retry': 0.4.1
@@ -2988,7 +3002,7 @@ snapshots:
       ajv: 6.12.6
       chalk: 4.1.2
       cross-spawn: 7.0.6
-      debug: 4.3.7
+      debug: 4.4.0
       escape-string-regexp: 4.0.0
       eslint-scope: 8.2.0
       eslint-visitor-keys: 4.2.0
@@ -3196,8 +3210,6 @@ snapshots:
     dependencies:
       binary-extensions: 2.3.0
 
-  is-buffer@1.1.6: {}
-
   is-core-module@2.15.1:
     dependencies:
       hasown: 2.0.2
@@ -3238,6 +3250,8 @@ snapshots:
 
   jiti@1.21.6: {}
 
+  js-sha256@0.11.0: {}
+
   js-yaml@4.1.0:
     dependencies:
       argparse: 2.0.1
@@ -3260,7 +3274,7 @@ snapshots:
 
   known-css-properties@0.35.0: {}
 
-  less@4.2.0:
+  less@4.2.1:
     dependencies:
       copy-anything: 2.0.6
       parse-node-version: 1.0.1
@@ -3281,15 +3295,15 @@ snapshots:
 
   lilconfig@2.1.0: {}
 
-  lilconfig@3.1.2: {}
+  lilconfig@3.1.3: {}
 
   lines-and-columns@1.2.4: {}
 
-  linkify-html@4.1.4(linkifyjs@4.1.4):
+  linkify-html@4.2.0(linkifyjs@4.2.0):
     dependencies:
-      linkifyjs: 4.1.4
+      linkifyjs: 4.2.0
 
-  linkifyjs@4.1.4: {}
+  linkifyjs@4.2.0: {}
 
   locate-character@3.0.0: {}
 
@@ -3305,7 +3319,7 @@ snapshots:
     dependencies:
       es5-ext: 0.10.64
 
-  magic-string@0.30.13:
+  magic-string@0.30.14:
     dependencies:
       '@jridgewell/sourcemap-codec': 1.5.0
 
@@ -3315,12 +3329,6 @@ snapshots:
       semver: 5.7.2
     optional: true
 
-  md5@2.3.0:
-    dependencies:
-      charenc: 0.0.2
-      crypt: 0.0.2
-      is-buffer: 1.1.6
-
   memoizee@0.4.17:
     dependencies:
       d: 1.0.2
@@ -3374,7 +3382,7 @@ snapshots:
       object-assign: 4.1.1
       thenify-all: 1.6.0
 
-  nanoid@3.3.7: {}
+  nanoid@3.3.8: {}
 
   natural-compare@1.4.0: {}
 
@@ -3468,7 +3476,7 @@ snapshots:
 
   postcss-load-config@4.0.2(postcss@8.4.49):
     dependencies:
-      lilconfig: 3.1.2
+      lilconfig: 3.1.3
       yaml: 2.6.1
     optionalDependencies:
       postcss: 8.4.49
@@ -3495,18 +3503,18 @@ snapshots:
 
   postcss@8.4.49:
     dependencies:
-      nanoid: 3.3.7
+      nanoid: 3.3.8
       picocolors: 1.1.1
       source-map-js: 1.2.1
 
   prelude-ls@1.2.1: {}
 
-  prettier-plugin-svelte@3.2.8(prettier@3.3.3)(svelte@5.8.1):
+  prettier-plugin-svelte@3.3.2(prettier@3.4.2)(svelte@5.8.1):
     dependencies:
-      prettier: 3.3.3
+      prettier: 3.4.2
       svelte: 5.8.1
 
-  prettier@3.3.3: {}
+  prettier@3.4.2: {}
 
   proxy-from-env@1.1.0: {}
 
@@ -3537,28 +3545,29 @@ snapshots:
 
   reusify@1.0.4: {}
 
-  rollup@4.27.3:
+  rollup@4.28.1:
     dependencies:
       '@types/estree': 1.0.6
     optionalDependencies:
-      '@rollup/rollup-android-arm-eabi': 4.27.3
-      '@rollup/rollup-android-arm64': 4.27.3
-      '@rollup/rollup-darwin-arm64': 4.27.3
-      '@rollup/rollup-darwin-x64': 4.27.3
-      '@rollup/rollup-freebsd-arm64': 4.27.3
-      '@rollup/rollup-freebsd-x64': 4.27.3
-      '@rollup/rollup-linux-arm-gnueabihf': 4.27.3
-      '@rollup/rollup-linux-arm-musleabihf': 4.27.3
-      '@rollup/rollup-linux-arm64-gnu': 4.27.3
-      '@rollup/rollup-linux-arm64-musl': 4.27.3
-      '@rollup/rollup-linux-powerpc64le-gnu': 4.27.3
-      '@rollup/rollup-linux-riscv64-gnu': 4.27.3
-      '@rollup/rollup-linux-s390x-gnu': 4.27.3
-      '@rollup/rollup-linux-x64-gnu': 4.27.3
-      '@rollup/rollup-linux-x64-musl': 4.27.3
-      '@rollup/rollup-win32-arm64-msvc': 4.27.3
-      '@rollup/rollup-win32-ia32-msvc': 4.27.3
-      '@rollup/rollup-win32-x64-msvc': 4.27.3
+      '@rollup/rollup-android-arm-eabi': 4.28.1
+      '@rollup/rollup-android-arm64': 4.28.1
+      '@rollup/rollup-darwin-arm64': 4.28.1
+      '@rollup/rollup-darwin-x64': 4.28.1
+      '@rollup/rollup-freebsd-arm64': 4.28.1
+      '@rollup/rollup-freebsd-x64': 4.28.1
+      '@rollup/rollup-linux-arm-gnueabihf': 4.28.1
+      '@rollup/rollup-linux-arm-musleabihf': 4.28.1
+      '@rollup/rollup-linux-arm64-gnu': 4.28.1
+      '@rollup/rollup-linux-arm64-musl': 4.28.1
+      '@rollup/rollup-linux-loongarch64-gnu': 4.28.1
+      '@rollup/rollup-linux-powerpc64le-gnu': 4.28.1
+      '@rollup/rollup-linux-riscv64-gnu': 4.28.1
+      '@rollup/rollup-linux-s390x-gnu': 4.28.1
+      '@rollup/rollup-linux-x64-gnu': 4.28.1
+      '@rollup/rollup-linux-x64-musl': 4.28.1
+      '@rollup/rollup-win32-arm64-msvc': 4.28.1
+      '@rollup/rollup-win32-ia32-msvc': 4.28.1
+      '@rollup/rollup-win32-x64-msvc': 4.28.1
       fsevents: 2.3.3
 
   run-parallel@1.2.0:
@@ -3648,7 +3657,13 @@ snapshots:
 
   supports-preserve-symlinks-flag@1.0.0: {}
 
-  svelte-check@4.0.9(picomatch@4.0.2)(svelte@5.8.1)(typescript@5.6.3):
+  svelte-autosize@1.1.5(svelte@5.8.1):
+    dependencies:
+      '@types/autosize': 4.0.3
+      autosize: 6.0.1
+      svelte: 5.8.1
+
+  svelte-check@4.1.1(picomatch@4.0.2)(svelte@5.8.1)(typescript@5.7.2):
     dependencies:
       '@jridgewell/trace-mapping': 0.3.25
       chokidar: 4.0.1
@@ -3656,7 +3671,7 @@ snapshots:
       picocolors: 1.1.1
       sade: 1.8.1
       svelte: 5.8.1
-      typescript: 5.6.3
+      typescript: 5.7.2
     transitivePeerDependencies:
       - picomatch
 
@@ -3675,12 +3690,6 @@ snapshots:
       '@floating-ui/core': 1.6.8
       '@floating-ui/dom': 1.6.12
 
-  svelte-gravatar@1.0.3(svelte@5.8.1):
-    dependencies:
-      md5: 2.3.0
-      svelte: 5.8.1
-      svelte-waypoint: 0.1.4
-
   svelte-hero-icons@5.2.0(svelte@5.8.1):
     dependencies:
       '@steeze-ui/heroicons': 2.4.2
@@ -3697,21 +3706,19 @@ snapshots:
       svelte: 5.8.1
       tiny-glob: 0.2.9
 
-  svelte-preprocess@6.0.3(less@4.2.0)(postcss-load-config@4.0.2(postcss@8.4.49))(postcss@8.4.49)(svelte@5.8.1)(typescript@5.6.3):
+  svelte-preprocess@6.0.3(less@4.2.1)(postcss-load-config@4.0.2(postcss@8.4.49))(postcss@8.4.49)(svelte@5.8.1)(typescript@5.7.2):
     dependencies:
       svelte: 5.8.1
     optionalDependencies:
-      less: 4.2.0
+      less: 4.2.1
       postcss: 8.4.49
       postcss-load-config: 4.0.2(postcss@8.4.49)
-      typescript: 5.6.3
+      typescript: 5.7.2
 
   svelte-select@5.8.3:
     dependencies:
       svelte-floating-ui: 1.5.8
 
-  svelte-waypoint@0.1.4: {}
-
   svelte@5.8.1:
     dependencies:
       '@ampproject/remapping': 2.3.0
@@ -3725,7 +3732,7 @@ snapshots:
       esrap: 1.2.3
       is-reference: 3.0.3
       locate-character: 3.0.0
-      magic-string: 0.30.13
+      magic-string: 0.30.14
       zimmerframe: 1.1.2
 
   sveltekit-i18n@2.4.2(svelte@5.8.1):
@@ -3734,7 +3741,7 @@ snapshots:
       '@sveltekit-i18n/parser-default': 1.1.1
       svelte: 5.8.1
 
-  tailwindcss@3.4.15:
+  tailwindcss@3.4.16:
     dependencies:
       '@alloc/quick-lru': 5.2.0
       arg: 5.0.2
@@ -3745,7 +3752,7 @@ snapshots:
       glob-parent: 6.0.2
       is-glob: 4.0.3
       jiti: 1.21.6
-      lilconfig: 2.1.0
+      lilconfig: 3.1.3
       micromatch: 4.0.8
       normalize-path: 3.0.0
       object-hash: 3.0.0
@@ -3785,9 +3792,9 @@ snapshots:
 
   totalist@3.0.1: {}
 
-  ts-api-utils@1.4.0(typescript@5.6.3):
+  ts-api-utils@1.4.3(typescript@5.7.2):
     dependencies:
-      typescript: 5.6.3
+      typescript: 5.7.2
 
   ts-interface-checker@0.1.13: {}
 
@@ -3799,7 +3806,7 @@ snapshots:
 
   type@2.7.3: {}
 
-  typescript@5.6.3: {}
+  typescript@5.7.2: {}
 
   update-browserslist-db@1.1.1(browserslist@4.24.2):
     dependencies:
@@ -3813,18 +3820,18 @@ snapshots:
 
   util-deprecate@1.0.2: {}
 
-  vite@5.4.11(less@4.2.0):
+  vite@5.4.11(less@4.2.1):
     dependencies:
       esbuild: 0.21.5
       postcss: 8.4.49
-      rollup: 4.27.3
+      rollup: 4.28.1
     optionalDependencies:
       fsevents: 2.3.3
-      less: 4.2.0
+      less: 4.2.1
 
-  vitefu@1.0.4(vite@5.4.11(less@4.2.0)):
+  vitefu@1.0.4(vite@5.4.11(less@4.2.1)):
     optionalDependencies:
-      vite: 5.4.11(less@4.2.0)
+      vite: 5.4.11(less@4.2.1)
 
   which@2.0.2:
     dependencies:
diff --git a/frontend/src/app.css b/frontend/src/app.css
index 124f156456817ec77b5827a6d553136d341070f8..4368154fd9428955de870d0fb25e757022944fe6 100644
--- a/frontend/src/app.css
+++ b/frontend/src/app.css
@@ -3,7 +3,6 @@
 @tailwind utilities;
 
 .button {
-	/*@apply bg-secondary text-white font-bold py-2 px-4 rounded hover:bg-secondaryHover hover:cursor-pointer disabled:border-secondary disabled:bg-transparent disabled:border-2 disabled:text-secondary disabled:hover:cursor-default;*/
 	@apply btn btn-primary;
 }
 
diff --git a/frontend/src/app.d.ts b/frontend/src/app.d.ts
index 4850c241bfc53afe235f11b08416e4050016bf7f..cdfa4356b6cabedbb1a2b3037a047aa57615b6bb 100644
--- a/frontend/src/app.d.ts
+++ b/frontend/src/app.d.ts
@@ -5,12 +5,15 @@ declare global {
 		// interface Error {}
 		interface Locals {
 			user: User?;
-			session: string?;
+			jwt: string?;
 			locale: string;
 		}
 		// interface PageData {}
 		// interface PageState {}
 		// interface Platform {}
+		interface FormData {
+			message: string;
+		}
 	}
 }
 
diff --git a/frontend/src/hooks.server.ts b/frontend/src/hooks.server.ts
index f5b9b640ef1951176f4898ffc2ea8d7a57c3d0e3..1d4d7ef276af03dc34685951d50613380d57b6be 100644
--- a/frontend/src/hooks.server.ts
+++ b/frontend/src/hooks.server.ts
@@ -1,21 +1,42 @@
-import type { Handle } from '@sveltejs/kit';
+import { type Handle, type RequestEvent } from '@sveltejs/kit';
 import { jwtDecode } from 'jwt-decode';
 import { type JWTContent } from '$lib/utils/login';
-import { getUserAPI } from '$lib/api/users';
-import User from '$lib/types/user';
-import { access_cookie } from '$lib/api/apiInstance';
+
+const API_BASE_URL = 'http://127.0.0.1:8000/api/v1';
+const PROXY_PATH = '/api';
+
+const handleApiProxy = async (event: RequestEvent, cookies: { name: string; value: string }[]) => {
+	const strippedPath = event.url.pathname.substring(PROXY_PATH.length);
+
+	const urlPath = `${API_BASE_URL}${strippedPath}${event.url.search}`;
+	const proxiedUrl = new URL(urlPath);
+
+	event.request.headers.delete('connection');
+	event.request.headers.set('cookie', cookies.map((c) => `${c.name}=${c.value}`).join('; '));
+
+	return event.fetch(proxiedUrl.toString(), event.request).catch((err: any) => {
+		console.log('Could not proxy API request: ', err);
+		throw err;
+	});
+};
 
 export const handle: Handle = async ({ event, resolve }) => {
 	event.locals.user = null;
-	event.locals.session = null;
+	event.locals.jwt = null;
 	event.locals.locale = 'fr';
 
-	const session = event.cookies.get('access_token_cookie');
-	if (!session) {
+	const cookies = event.cookies.getAll();
+
+	if (event.url.pathname.startsWith(PROXY_PATH)) {
+		return await handleApiProxy(event, cookies);
+	}
+
+	const jwt = event.cookies.get('access_token_cookie');
+	if (!jwt) {
 		return resolve(event);
 	}
 
-	const decoded = jwtDecode<JWTContent>(session);
+	const decoded = jwtDecode<JWTContent>(jwt);
 	if (!decoded) {
 		return resolve(event);
 	}
@@ -25,8 +46,12 @@ export const handle: Handle = async ({ event, resolve }) => {
 		return resolve(event);
 	}
 
-	access_cookie.set(session);
-	const user = User.parse(await getUserAPI(id));
+	const response = await event.fetch(`/api/users/${id}`);
+	if (!response.ok) {
+		return resolve(event);
+	}
+
+	const user = await response.json();
 	if (!user) {
 		return resolve(event);
 	}
@@ -34,8 +59,8 @@ export const handle: Handle = async ({ event, resolve }) => {
 	const localeCookie = event.cookies.get('locale');
 	const initLocale = localeCookie || event.locals.locale;
 
-	event.locals.user = user.toJson();
-	event.locals.session = session;
+	event.locals.user = user;
+	event.locals.jwt = jwt;
 	event.locals.locale = initLocale;
 	return resolve(event);
 };
diff --git a/frontend/src/lang/fr.json b/frontend/src/lang/fr.json
index e090487ee4cd13082d44103044b14e6e2f2ed8c9..5385dd1364e6adcae9696f527d18e8e93270b3cf 100644
--- a/frontend/src/lang/fr.json
+++ b/frontend/src/lang/fr.json
@@ -43,7 +43,9 @@
 		"bookingSuccessful": "Session réservée avec succès",
 		"bookingFailed": "Erreur lors de la réservation de la session",
 		"noCurrentOrFutureSessions": "Aucune session en cours ou planifiée",
-		"noSessions": "Aucune session"
+		"noSessions": "Aucune session",
+		"noContact": "Ajoutez un contact pour commencer",
+		"newFirstContact": "Ajouter un premier contact"
 	},
 	"login": {
 		"email": "E-mail",
@@ -259,10 +261,10 @@
 				"title": "Questionnaire hebdomadaire",
 				"description": "Au cours des 7 derniers jours...",
 				"questions": [
-					"Combien d'heures de <span class='font-bold'>cours</span> de {lang} avez vous suivies ?",
-					"Combien d'heures avez-vous <span class='font-bold'>regardé des vidéos</span> en {lang} (films, séries, Youtube...) ou <span class='font-bold'>écouté des contenus</span> en {lang} (podcasts, radio, cours universitaires...) ?",
-					"Combien d'heures avez-vous <span class='font-bold'>lu des textes</span> en {lang} (livre, journal, BD, sites web...) ?",
-					"Combien d'heures avez-vous <span class='font-bold'>parlé</span> en {lang} (discussions avec amis, famille, collègues...) ?"
+					"Combien d'heures de <b>cours</b> de {lang} avez vous suivies ?",
+					"Combien d'heures avez-vous <b>regardé des vidéos</b> en {lang} (films, séries, Youtube...) ou <b>écouté des contenus</b> en {lang} (podcasts, radio, cours universitaires...) ?",
+					"Combien d'heures avez-vous <b>lu des textes</b> en {lang} (livre, journal, BD, sites web...) ?",
+					"Combien d'heures avez-vous <b>parlé</b> en {lang} (discussions avec amis, famille, collègues...) ?"
 				],
 				"answers": {
 					"placeholder": "",
@@ -281,14 +283,24 @@
 				},
 				"errors": {
 					"null": "Veuillez répondre à toutes les questions",
-					"submit": "Erreur lors de l'envoi du questionnaire"
+					"submit": "Erreur lors de l'envoi du questionnaire",
+					"toggle": "Erreur lors de l'activation ou de la désactivation de la session"
 				},
 				"success": "Questionnaire envoyé, merci !"
 			}
 		},
 		"downloadAllMessages": "Télécharger toutes les conversations",
 		"downloadAllMetadata": "Télécharger toutes les métadonnées",
-		"downloadAllFeedbacks": "Télécharger tous les feedbacks"
+		"downloadAllFeedbacks": "Télécharger tous les feedbacks",
+		"errors": {
+			"create": "Erreur lors de la création de la session",
+			"delete": "Erreur lors de la suppression de la session",
+			"addUser": "Erreur lors de l'ajout d'un utilisateur à la session",
+			"removeUser": "Erreur lors de la suppression d'un utilisateur de la session",
+			"presence": "Erreur lors de l'envoi de la présence",
+			"typing": "Erreur lors de l'envoi de l'indicateur de saisie"
+		},
+		"noTopic": "Aucun topic disponible"
 	},
 	"button": {
 		"create": "Créer",
@@ -378,7 +390,8 @@
 			"date": "Date",
 			"programed": "Programmée",
 			"inProgress": "En cours",
-			"finished": "Terminée"
+			"finished": "Terminée",
+			"topics": "Topics"
 		}
 	},
 	"inputs": {
diff --git a/frontend/src/lib/api/apiInstance.ts b/frontend/src/lib/api/apiInstance.ts
deleted file mode 100644
index 2053a846812acc86bf5554e370cba59dd07e97ce..0000000000000000000000000000000000000000
--- a/frontend/src/lib/api/apiInstance.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import axios from 'axios';
-import config from '$lib/config';
-import { writable, get } from 'svelte/store';
-
-export const access_cookie = writable('');
-
-export const axiosPublicInstance = axios.create({
-	...axios.defaults,
-	baseURL: config.API_URL,
-	withCredentials: true,
-	validateStatus: () => true,
-	headers: {
-		'Content-Type': 'application/json',
-		Authorization: `Bearer ${get(access_cookie)}`
-	}
-});
-
-export const axiosInstance = axiosPublicInstance;
diff --git a/frontend/src/lib/api/auth.ts b/frontend/src/lib/api/auth.ts
deleted file mode 100644
index 5d3ba02dbac044a628f5b471498b113b0651f6b6..0000000000000000000000000000000000000000
--- a/frontend/src/lib/api/auth.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import { axiosPublicInstance } from './apiInstance';
-
-export async function loginAPI(email: string, password: string): Promise<string> {
-	return axiosPublicInstance
-		.post(
-			`/auth/login`,
-			{
-				email,
-				username: email,
-				password
-			},
-			{
-				headers: {
-					'Content-Type': 'application/x-www-form-urlencoded'
-				}
-			}
-		)
-		.then((response) => {
-			if (response.status === 401) {
-				return response.data.detail ?? 'Unauthorized';
-			} else if (response.status === 422) {
-				return 'Invalid request';
-			} else if (response.status === 200) {
-				return 'OK';
-			}
-
-			return 'Unknown error occurred: ' + response.status;
-		})
-		.catch((error) => {
-			return error.toString();
-		});
-}
-
-export async function registerAPI(
-	email: string,
-	password: string,
-	nickname: string,
-	tutor: boolean = false
-): Promise<string> {
-	return axiosPublicInstance
-		.post(
-			`/auth/register`,
-			{
-				email,
-				username: email,
-				password,
-				nickname,
-				tutor
-			},
-			{
-				headers: {
-					'Content-Type': 'application/x-www-form-urlencoded'
-				}
-			}
-		)
-		.then((response) => {
-			if (response.status === 401) {
-				return response.data.detail ?? 'Unauthorized';
-			} else if (response.status === 422) {
-				return 'Invalid request';
-			} else if (response.status === 201) {
-				return 'OK';
-			}
-
-			return 'Error ' + response.status + ': ' + response.data.detail;
-		})
-		.catch((error) => {
-			return error.toString();
-		});
-}
diff --git a/frontend/src/lib/api/sessions.ts b/frontend/src/lib/api/sessions.ts
index c49b667d7a231788eb2378c9d7f9cf3fb72c4333..9b861150861266e6cba1ed36cab501dbf28c023e 100644
--- a/frontend/src/lib/api/sessions.ts
+++ b/frontend/src/lib/api/sessions.ts
@@ -1,165 +1,195 @@
 import { formatToUTCDate } from '$lib/utils/date';
-import { toastAlert } from '$lib/utils/toasts';
-import { axiosInstance } from './apiInstance';
+import type { fetchType } from '$lib/utils/types';
 
-export async function getSessionsAPI() {
-	const response = await axiosInstance.get(`/sessions`);
+export async function getSessionsAPI(fetch: fetchType): Promise<any[]> {
+	const response = await fetch(`/api/sessions`);
+	if (!response.ok) return [];
 
-	return response.data;
+	return await response.json();
 }
 
-export async function createSessionAPI() {
-	const response = await axiosInstance.post(`/sessions`);
+export async function createSessionAPI(fetch: fetchType): Promise<any | null> {
+	const response = await fetch(`/api/sessions`, { method: 'POST' });
+	if (!response.ok) return null;
 
-	if (response.status !== 200) {
-		toastAlert('Failed to create session');
-	}
+	return await response.json();
 }
 
-export async function getSessionAPI(id: number) {
-	const response = await axiosInstance.get(`/sessions/${id}`);
+export async function getSessionAPI(fetch: fetchType, id: number): Promise<any | null> {
+	const response = await fetch(`/api/sessions/${id}`);
+	if (!response.ok) return null;
 
-	if (response.status !== 200) {
-		toastAlert('Failed to get session');
-		return null;
-	}
-
-	return response.data;
+	return await response.json();
 }
 
-export async function deleteSessionAPI(id: number) {
-	const response = await axiosInstance.delete(`/sessions/${id}`);
+export async function deleteSessionAPI(fetch: fetchType, id: number): Promise<boolean> {
+	const response = await fetch(`/api/sessions/${id}`, { method: 'DELETE' });
+	if (!response.ok) return false;
 
-	if (response.status !== 204) {
-		toastAlert('Failed to delete session');
-	}
+	return true;
 }
 
-export async function getMessagesAPI(id: number) {
-	const response = await axiosInstance.get(`/sessions/${id}/messages`);
+export async function getMessagesAPI(fetch: fetchType, id: number): Promise<any | null> {
+	const response = await fetch(`/api/sessions/${id}/messages`);
+	if (!response.ok) return null;
 
-	return response.data;
+	return await response.json();
 }
 
 export async function createMessageAPI(
+	fetch: fetchType,
 	id: number,
 	content: string,
 	metadata: { message: string; date: number }[],
 	replyTo: number | null
 ): Promise<any | null> {
-	const response = await axiosInstance.post(`/sessions/${id}/messages`, {
-		content,
-		metadata,
-		reply_to_message_id: replyTo
+	const response = await fetch(`/api/sessions/${id}/messages`, {
+		method: 'POST',
+		headers: { 'Content-Type': 'application/json' },
+		body: JSON.stringify({ content, metadata, reply_to_message_id: replyTo })
 	});
+	if (!response.ok) return null;
 
-	if (response.status !== 201) {
-		toastAlert('Failed to send message');
-		return null;
-	}
-
-	return response.data;
+	return await response.json();
 }
 
 export async function updateMessageAPI(
+	fetch: fetchType,
 	id: number,
 	message_id: string,
 	content: string,
 	metadata: { message: string; date: number }[]
-): Promise<number | null> {
-	const response = await axiosInstance.post(`/sessions/${id}/messages`, {
-		message_id,
-		content,
-		metadata
+): Promise<any | null> {
+	const response = await fetch(`/api/sessions/${id}/messages`, {
+		method: 'POST',
+		headers: { 'Content-Type': 'application/json' },
+		body: JSON.stringify({ message_id, content, metadata })
 	});
+	if (!response.ok) return null;
 
-	if (response.status !== 201) {
-		toastAlert('Failed to update message');
-		return null;
-	}
-
-	return response.data;
+	return await response.json();
 }
 
 export async function createMessageFeedbackAPI(
+	fetch: fetchType,
 	id: number,
 	message_id: number,
 	start: number,
 	end: number,
 	content: string | null
-): Promise<number> {
-	const response = await axiosInstance.post(`/sessions/${id}/messages/${message_id}/feedback`, {
-		start,
-		end,
-		content
+): Promise<number | null> {
+	const response = await fetch(`/api/sessions/${id}/messages/${message_id}/feedback`, {
+		method: 'POST',
+		headers: { 'Content-Type': 'application/json' },
+		body: JSON.stringify({ start, end, content })
 	});
-	if (response.status !== 201) {
-		toastAlert('Failed to add feedback');
-		return -1;
-	}
-	return response.data;
+	if (!response.ok) return null;
+
+	return parseInt(await response.text());
 }
 
 export async function deleteMessageFeedbackAPI(
+	fetch: fetchType,
 	id: number,
 	message_id: number,
 	feedback_id: number
-) {
-	const response = await axiosInstance.delete(
-		`/sessions/${id}/messages/${message_id}/feedback/${feedback_id}`
+): Promise<boolean> {
+	const response = await fetch(
+		`/api/sessions/${id}/messages/${message_id}/feedback/${feedback_id}`,
+		{
+			method: 'DELETE'
+		}
 	);
-	if (response.status !== 204) {
-		toastAlert('Failed to delete feedback');
-		return false;
-	}
-	return true;
-}
 
-export async function patchLanguageAPI(id: number, language: string) {
-	const response = await axiosInstance.patch(`/sessions/${id}`, { language });
+	return response.ok;
+}
 
-	if (response.status !== 204) {
-		toastAlert('Failed to change language');
-		return false;
-	}
+export async function patchLanguageAPI(
+	fetch: fetchType,
+	id: number,
+	language: string
+): Promise<boolean> {
+	const response = await fetch(`/api/sessions/${id}`, {
+		method: 'PATCH',
+		headers: { 'Content-Type': 'application/json' },
+		body: JSON.stringify({ language })
+	});
 
-	return true;
+	return response.ok;
 }
 
 export async function createSessionSatisfyAPI(
+	fetch: fetchType,
 	id: number,
 	usefullness: number,
 	easiness: number,
 	remarks: string
 ): Promise<boolean> {
-	const response = await axiosInstance.post(`/sessions/${id}/satisfy`, {
-		usefullness,
-		easiness,
-		remarks
+	const response = await fetch(`/api/sessions/${id}/satisfy`, {
+		method: 'POST',
+		headers: { 'Content-Type': 'application/json' },
+		body: JSON.stringify({ usefullness, easiness, remarks })
 	});
 
-	if (response.status !== 204) {
-		toastAlert('Failed to satisfy session');
-		return false;
-	}
-
-	return true;
+	return response.ok;
 }
 
 export async function createSessionFromCalComAPI(
+	fetch: fetchType,
 	user_id: number,
 	contact_id: number,
 	start: Date,
 	end: Date
 ): Promise<number | null> {
-	const response = await axiosInstance.post(`/users/${user_id}/contacts/${contact_id}/bookings`, {
-		start_time: formatToUTCDate(start),
-		end_time: formatToUTCDate(end)
+	const response = await fetch(`/api/users/${user_id}/contacts/${contact_id}/bookings`, {
+		method: 'POST',
+		headers: { 'Content-Type': 'application/json' },
+		body: JSON.stringify({
+			start_time: formatToUTCDate(start),
+			end_time: formatToUTCDate(end)
+		})
+	});
+	if (!response.ok) return null;
+
+	return await response.json();
+}
+
+export async function addUserToSessionAPI(
+	fetch: fetchType,
+	session_id: number,
+	user_id: number
+): Promise<boolean> {
+	const response = await fetch(`/api/sessions/${session_id}/users/${user_id}`, { method: 'POST' });
+
+	return response.ok;
+}
+
+export async function patchSessionAPI(fetch: fetchType, id: number, data: any): Promise<boolean> {
+	const response = await fetch(`/api/sessions/${id}`, {
+		method: 'PATCH',
+		headers: { 'Content-Type': 'application/json' },
+		body: JSON.stringify(data)
 	});
-	if (response.status !== 201) {
-		toastAlert('Failed to create cal.com session');
-		return null;
-	}
+	return response.ok;
+}
+
+export async function sendTypingAPI(fetch: fetchType, id: number): Promise<boolean> {
+	const response = await fetch(`/api/sessions/${id}/typing`, { method: 'POST' });
+	return response.ok;
+}
 
-	return response.data;
+export async function sendPresenceAPI(fetch: fetchType, id: number): Promise<boolean> {
+	const response = await fetch(`/api/sessions/${id}/presence`, { method: 'POST' });
+	return response.ok;
+}
+
+export async function removeUserFromSessionAPI(
+	fetch: fetchType,
+	session_id: number,
+	user_id: number
+): Promise<boolean> {
+	const response = await fetch(`/api/sessions/${session_id}/users/${user_id}`, {
+		method: 'DELETE'
+	});
+	return response.ok;
 }
diff --git a/frontend/src/lib/api/survey.ts b/frontend/src/lib/api/survey.ts
index a4f3c956e22ab43af33859fa6c9752fe5cc626e5..52b17a7f00a2c14ecca744aff23e2faa4696784f 100644
--- a/frontend/src/lib/api/survey.ts
+++ b/frontend/src/lib/api/survey.ts
@@ -1,18 +1,14 @@
-import { toastAlert } from '$lib/utils/toasts';
-import { axiosInstance } from './apiInstance';
+import type { fetchType } from '$lib/utils/types';
 
-export async function getSurveyAPI(survey_id: number) {
-	const response = await axiosInstance.get(`/surveys/${survey_id}`);
+export async function getSurveyAPI(fetch: fetchType, survey_id: number) {
+	const response = await fetch(`/api/surveys/${survey_id}`);
+	if (!response.ok) return null;
 
-	if (response.status !== 200) {
-		toastAlert('Failed to get survey');
-		return null;
-	}
-
-	return response.data;
+	return await response.json();
 }
 
 export async function sendSurveyResponseAPI(
+	fetch: fetchType,
 	code: string,
 	sid: string,
 	uid: number | null,
@@ -23,36 +19,34 @@ export async function sendSurveyResponseAPI(
 	response_time: number,
 	text: string = ''
 ) {
-	const response = await axiosInstance.post(`/surveys/responses`, {
-		code,
-		sid,
-		uid,
-		survey_id,
-		question_id,
-		group_id,
-		selected_id: option_id,
-		response_time,
-		text
+	const response = await fetch(`/api/surveys/responses`, {
+		method: 'POST',
+		headers: { 'Content-Type': 'application/json' },
+		body: JSON.stringify({
+			code,
+			sid,
+			uid,
+			survey_id,
+			question_id,
+			group_id,
+			selected_id: option_id,
+			response_time,
+			text
+		})
 	});
 
-	if (response.status !== 201) {
-		toastAlert('Failed to send survey response');
-		return false;
-	}
-
-	return true;
+	return response.ok;
 }
 
-export async function getSurveyScoreAPI(survey_id: number, sid: string) {
-	const response = await axiosInstance.get(`/surveys/${survey_id}/score/${sid}`);
-	if (response.status !== 200) {
-		toastAlert('Failed to retrieve survey score');
-		return null;
-	}
-	return response.data;
+export async function getSurveyScoreAPI(fetch: fetchType, survey_id: number, sid: string) {
+	const response = await fetch(`/api/surveys/${survey_id}/score/${sid}`);
+	if (!response.ok) return null;
+
+	return await response.json();
 }
 
 export async function sendSurveyResponseInfoAPI(
+	fetch: fetchType,
 	survey_id: number,
 	sid: string,
 	birthyear: number,
@@ -60,18 +54,17 @@ export async function sendSurveyResponseInfoAPI(
 	primary_language: string,
 	education: string
 ) {
-	const response = await axiosInstance.post(`/surveys/info/${survey_id}`, {
-		sid,
-		birthyear,
-		gender,
-		primary_language,
-		education
+	const response = await fetch(`/api/surveys/info/${survey_id}`, {
+		method: 'POST',
+		headers: { 'Content-Type': 'application/json' },
+		body: JSON.stringify({
+			sid,
+			birthyear,
+			gender,
+			primary_language,
+			education
+		})
 	});
 
-	if (response.status !== 201) {
-		toastAlert('Failed to send survey response info');
-		return false;
-	}
-
-	return true;
+	return response.ok;
 }
diff --git a/frontend/src/lib/api/tests.ts b/frontend/src/lib/api/tests.ts
index f95de8df0bb4e3569162c8a4e72233d332972a5d..f0019c47f651f4beaa74893563d25b623fbc55e4 100644
--- a/frontend/src/lib/api/tests.ts
+++ b/frontend/src/lib/api/tests.ts
@@ -1,12 +1,9 @@
-import { axiosInstance } from './apiInstance';
-
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
 export async function sendTestVocabularyAPI(data: any): Promise<boolean> {
-	const response = await axiosInstance.post(`/tests/vocabulary`, { content: data });
-
-	if (response.status !== 201) {
-		return false;
-	}
+	const response = await fetch(`/api/tests/vocabulary`, {
+		method: 'POST',
+		headers: { 'Content-Type': 'application/json' },
+		body: JSON.stringify(data)
+	});
 
-	return true;
+	return response.ok;
 }
diff --git a/frontend/src/lib/api/users.ts b/frontend/src/lib/api/users.ts
index 0d6a1bbc3a9bd705cb2f71896518e8bcab527329..19f124d7787d07e724092ebe50159898a7f00aad 100644
--- a/frontend/src/lib/api/users.ts
+++ b/frontend/src/lib/api/users.ts
@@ -1,168 +1,118 @@
-import { toastAlert } from '$lib/utils/toasts';
-import { axiosInstance, access_cookie } from './apiInstance';
-import { get } from 'svelte/store';
+import type { fetchType } from '$lib/utils/types';
 
-export async function getUsersAPI() {
-	const response = await axiosInstance.get(`/users`);
+export async function getUsersAPI(fetch: fetchType): Promise<any[]> {
+	const response = await fetch(`/api/users`);
+	if (!response.ok) return [];
 
-	if (response.status !== 200) {
-		toastAlert('Failed to get users');
-		return [];
-	}
-
-	return response.data;
+	return await response.json();
 }
 
-export async function getUserAPI(user_id: number) {
-	const response = await axiosInstance.get(`/users/${user_id}`, {
-		headers: {
-			Authorization: `Bearer ${get(access_cookie)}`
-		}
-	});
-
-	if (response.status !== 200) {
-		toastAlert('Failed to get user');
-		return null;
-	}
+export async function getUserAPI(fetch: fetchType, user_id: number): Promise<any | null> {
+	const response = await fetch(`/api/users/${user_id}`);
+	if (!response.ok) return null;
 
-	return response.data;
+	return await response.json();
 }
 
-export async function createUserContactAPI(user_id: number, contact_id: number) {
-	const response = await axiosInstance.post(`/users/${user_id}/contacts/${contact_id}`);
-
-	if (response.status !== 201) {
-		toastAlert('Failed to create user contact');
-		return null;
-	}
+export async function createUserContactAPI(
+	fetch: fetchType,
+	user_id: number,
+	contact_id: number
+): Promise<any | null> {
+	const response = await fetch(`/api/users/${user_id}/contacts/${contact_id}`);
+	if (!response.ok) return null;
 
-	return response.data;
+	return await response.json();
 }
 
-export async function createUserContactFromEmailAPI(user_id: number, email: string) {
-	const response = await axiosInstance.post(`/users/${user_id}/contacts-email/${email}`);
-
-	if (response.status === 404) {
-		toastAlert('User not found');
-		return null;
-	}
-
-	if (response.status === 400) {
-		toastAlert('User already has this contact');
-		return null;
-	}
-
-	if (response.status !== 201) {
-		toastAlert('Failed to create user contact');
-		return null;
-	}
+export async function createUserContactFromEmailAPI(
+	fetch: fetchType,
+	user_id: number,
+	email: string
+): Promise<any | null> {
+	const response = await fetch(`/api/users/${user_id}/contacts-email/${email}`, { method: 'POST' });
+	if (!response.ok) return null;
 
-	return response.data;
+	return await response.json();
 }
 
-export async function getUserContactsAPI(user_id: number) {
-	const response = await axiosInstance.get(`/users/${user_id}/contacts`, {
-		headers: {
-			Authorization: `Bearer ${get(access_cookie)}`
-		}
-	});
-
-	if (response.status !== 200) {
-		toastAlert('Failed to get user contacts');
-		return [];
-	}
+export async function getUserContactsAPI(fetch: fetchType, user_id: number): Promise<any[]> {
+	const response = await fetch(`/api/users/${user_id}/contacts`);
+	if (!response.ok) return [];
 
-	return response.data;
+	return await response.json();
 }
 
-export async function getUserContactSessionsAPI(user_id: number, contact_id: number) {
-	const response = await axiosInstance.get(`/users/${user_id}/contacts/${contact_id}/sessions`, {
-		headers: {
-			Authorization: `Bearer ${get(access_cookie)}`
-		}
-	});
-
-	if (response.status !== 200) {
-		toastAlert('Failed to get user contact sessions');
-		return [];
-	}
+export async function getUserContactSessionsAPI(
+	fetch: fetchType,
+	user_id: number,
+	contact_id: number
+): Promise<any[]> {
+	const response = await fetch(`/api/users/${user_id}/contacts/${contact_id}/sessions`);
+	if (!response.ok) return [];
 
-	return response.data;
+	return await response.json();
 }
 
 export async function createUserAPI(
+	fetch: fetchType,
 	nickname: string,
 	email: string,
 	password: string,
 	type: number,
 	is_active: boolean
 ): Promise<number | null> {
-	const response = await axiosInstance.post(`/users`, {
-		nickname,
-		email,
-		password,
-		type,
-		is_active
+	const response = await fetch(`/api/users`, {
+		method: 'POST',
+		headers: { 'Content-Type': 'application/json' },
+		body: JSON.stringify({ nickname, email, password, type, is_active })
 	});
 
-	if (response.status !== 201) {
-		toastAlert('Failed to create user');
-		return null;
-	}
+	if (!response.ok) return null;
 
-	return response.data;
+	return parseInt(await response.text());
 }
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-export async function patchUserAPI(user_id: number, data: any): Promise<boolean> {
-	try {
-		const response = await axiosInstance.patch(`/users/${user_id}`, data);
-
-		if (response.status !== 204) {
-			toastAlert('Failed to update user');
-			return false;
-		}
-
-		return true;
-	} catch (e) {
-		console.error(e);
-		toastAlert('Failed to update user due to unknown error');
-		return false;
-	}
+export async function patchUserAPI(fetch: fetchType, user_id: number, data: any) {
+	const response = await fetch(`/api/users/${user_id}`, {
+		method: 'PATCH',
+		headers: { 'Content-Type': 'application/json' },
+		body: JSON.stringify(data)
+	});
+	return response.ok;
 }
 
 export async function createTestTypingAPI(
+	fetch: fetchType,
 	user_id: number,
 	entries: typingEntry[]
 ): Promise<number | null> {
-	const response = await axiosInstance.post(`/users/${user_id}/tests/typing`, {
-		entries
+	const response = await fetch(`/api/users/${user_id}/tests/typing`, {
+		method: 'POST',
+		headers: { 'Content-Type': 'application/json' },
+		body: JSON.stringify({ entries })
 	});
 
-	if (response.status !== 201) {
-		toastAlert('Failed to create test');
-		return null;
-	}
+	if (!response.ok) return null;
 
-	return response.data;
+	return parseInt(await response.text());
 }
 
 export async function createWeeklySurveyAPI(
+	fetch: fetchType,
 	user_id: number,
 	q1: number,
 	q2: number,
 	q3: number,
 	q4: number
 ): Promise<number | null> {
-	const response = await axiosInstance.post(`/users/${user_id}/surveys/weekly`, {
-		q1,
-		q2,
-		q3,
-		q4
+	const response = await fetch(`/api/users/${user_id}/surveys/weekly`, {
+		method: 'POST',
+		headers: { 'Content-Type': 'application/json' },
+		body: JSON.stringify({ q1, q2, q3, q4 })
 	});
-	if (response.status !== 201) {
-		toastAlert('Failed to create weekly survey');
-		return null;
-	}
-	return response.data;
+
+	if (!response.ok) return null;
+
+	return parseInt(await response.text());
 }
diff --git a/frontend/src/lib/components/sessions/emojiPicker.svelte b/frontend/src/lib/components/sessions/emojiPicker.svelte
deleted file mode 100644
index daebc8809febe54805825c2235fa88ca327b63a2..0000000000000000000000000000000000000000
--- a/frontend/src/lib/components/sessions/emojiPicker.svelte
+++ /dev/null
@@ -1,27 +0,0 @@
-<script lang="ts">
-	import { EmojiButton } from '@joeattardi/emoji-button';
-	import { createEventDispatcher } from 'svelte';
-	import { FaceSmile, Icon } from 'svelte-hero-icons';
-
-	let class_: string = '';
-	export { class_ as class };
-
-	const dispatch = createEventDispatcher();
-
-	const picker = new EmojiButton({ autoHide: false });
-	let trigger: HTMLButtonElement;
-
-	picker.on('emoji', (selection) => {
-		dispatch('change', selection);
-	});
-
-	function togglePicker() {
-		picker.togglePicker(trigger);
-	}
-</script>
-
-<button bind:this={trigger} class={class_} on:click={togglePicker}>
-	<kbd class="kbd">
-		<Icon src={FaceSmile} class="w-6" />
-	</kbd>
-</button>
diff --git a/frontend/src/lib/components/tests/typingbox.svelte b/frontend/src/lib/components/tests/typingbox.svelte
index fd6b5d5560dfd2b04b4a3cacfeed0360584a6918..b6e8de1129996159a89be22b015ad5914bb19b60 100644
--- a/frontend/src/lib/components/tests/typingbox.svelte
+++ b/frontend/src/lib/components/tests/typingbox.svelte
@@ -102,7 +102,7 @@
 			bind:this={textArea}
 			spellcheck="false"
 			disabled={isDone}
-			on:keyup={(e) => {
+			on:keyup={() => {
 				if (inProgress) {
 					data[data.length - 1].uptime = new Date().getTime() - startTime;
 				}
@@ -134,6 +134,6 @@
 				}
 			}}
 			class="absolute top-0 resize-none font-mono p-4 w-full h-full bg-transparent select-none text-transparent"
-		/>
+		></textarea>
 	</div>
 </div>
diff --git a/frontend/src/lib/components/tests/typingtest.svelte b/frontend/src/lib/components/tests/typingtest.svelte
index 4f79c9ab0ec199d5c93066e844e481b8b9a92e96..103aba2f0d8976d05d740723a9051a73392d2d58 100644
--- a/frontend/src/lib/components/tests/typingtest.svelte
+++ b/frontend/src/lib/components/tests/typingtest.svelte
@@ -3,16 +3,15 @@
 	import Typingbox from '$lib/components/tests/typingbox.svelte';
 	import { get } from 'svelte/store';
 	import { createTestTypingAPI } from '$lib/api/users';
-	import { user } from '$lib/types/user';
-	import { toastAlert } from '$lib/utils/toasts';
+	import type User from '$lib/types/user';
 
-	export let onFinish: Function;
+	let { user, onFinish }: { user: User; onFinish: Function } = $props();
 
-	let data: typingEntry[] = [];
+	let data: typingEntry[] = $state([]);
 
-	$: currentExercice = 0;
+	let currentExercice = $state(0);
 
-	let inProgress = false;
+	let inProgress = $state(false);
 
 	let exercices = [
 		{
@@ -33,14 +32,7 @@
 	];
 
 	async function submit() {
-		const user_id = $user?.id;
-
-		if (!user_id) {
-			toastAlert('Failed to get user');
-			return;
-		}
-
-		const res = await createTestTypingAPI(user_id, data);
+		const res = await createTestTypingAPI(fetch, user.id, data);
 
 		if (!res) return;
 
@@ -71,7 +63,7 @@
 	{#if currentExercice < exercices.length - 1}
 		<button
 			class="button m-auto"
-			on:click={() => {
+			onclick={() => {
 				currentExercice++;
 				inProgress = false;
 			}}
@@ -80,7 +72,7 @@
 			{$t('button.next')}
 		</button>
 	{:else}
-		<button class="button m-auto" disabled={inProgress} on:click={submit}
+		<button class="button m-auto" disabled={inProgress} onclick={submit}
 			>{$t('button.submit')}</button
 		>
 	{/if}
diff --git a/frontend/src/lib/config.ts b/frontend/src/lib/config.ts
index 10b3cf95a5b512c8e378e20cc4c96d3534b14573..ca3b4dd92665f41385f9f1ca930157bf3db87ca8 100644
--- a/frontend/src/lib/config.ts
+++ b/frontend/src/lib/config.ts
@@ -1,5 +1,5 @@
 export default {
-	API_URL: import.meta.env.VITE_API_URL || 'https://languagelab.sipr.ucl.ac.be/api/v1',
+	API_URL: import.meta.env.VITE_API_URL || 'https://languagelab.sipr.ucl.ac.be/api',
 	API_PROXY: import.meta.env.VITE_API_PROXY || 'https://languagelab.sipr.ucl.ac.be:8000',
 	APP_URL: import.meta.env.VITE_APP_URL || 'https://languagelab.sipr.ucl.ac.be',
 	WS_URL: import.meta.env.VITE_WS_URL || 'wss://languagelab.sipr.ucl.ac.be/api/v1/ws',
diff --git a/frontend/src/lib/index.ts b/frontend/src/lib/index.ts
deleted file mode 100644
index 856f2b6c38aec1085db88189bcf492dbb49a1c45..0000000000000000000000000000000000000000
--- a/frontend/src/lib/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-// place files you want to import through the `$lib` alias in this folder.
diff --git a/frontend/src/lib/services/auth.ts b/frontend/src/lib/services/auth.ts
deleted file mode 100644
index eaab6fd477b62672c0305b0fd6648ad261059ce9..0000000000000000000000000000000000000000
--- a/frontend/src/lib/services/auth.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { type ServerLoad, redirect } from '@sveltejs/kit';
-import { loadTranslations } from '$lib/services/i18n';
-
-export const requireLogin: ServerLoad = async ({ params, url, cookies }) => {
-	const initLocale = params.locale || 'fr';
-	const { pathname } = url;
-
-	await loadTranslations(initLocale, pathname);
-
-	const session = cookies.get('token');
-
-	if (!session) {
-		redirect(302, `/${initLocale}/login`);
-	}
-};
-
-export const getLogin: ServerLoad = async ({ cookies }) => {
-	const session = cookies.get('token');
-
-	if (!session) {
-		return null;
-	}
-};
diff --git a/frontend/src/lib/types/feedback.ts b/frontend/src/lib/types/feedback.ts
index bfebd112f949b9aad89bc9a969a2313b499e6634..708256717809435ab0f54b5c6432e40b6381a5ea 100644
--- a/frontend/src/lib/types/feedback.ts
+++ b/frontend/src/lib/types/feedback.ts
@@ -83,7 +83,12 @@ export default class Feedback {
 	}
 
 	async delete(): Promise<boolean> {
-		return await deleteMessageFeedbackAPI(this._message.session.id, this._message.id, this._id);
+		return await deleteMessageFeedbackAPI(
+			fetch,
+			this._message.session.id,
+			this._message.id,
+			this._id
+		);
 	}
 
 	static parseAll(json: any, message: Message): Feedback[] {
diff --git a/frontend/src/lib/types/message.ts b/frontend/src/lib/types/message.ts
index 12ee21756174cff581c03e9e1da438a6e513f098..3e7bcd1f4cc2f46aff3e36111caea7d46c4b638b 100644
--- a/frontend/src/lib/types/message.ts
+++ b/frontend/src/lib/types/message.ts
@@ -16,7 +16,7 @@ export default class Message {
 	private _edited: boolean = false;
 	private _versions = writable([] as { content: string; date: Date }[]);
 	private _feedbacks = writable([] as Feedback[]);
-	private _replyTo: string;
+	private _replyTo: number;
 
 	public constructor(
 		id: number,
@@ -25,7 +25,7 @@ export default class Message {
 		created_at: Date,
 		user: User,
 		session: Session,
-		replyTo: string
+		replyTo: number
 	) {
 		this._id = id;
 		this._message_id = message_id;
@@ -77,8 +77,26 @@ export default class Message {
 		return `message-${this._message_id}`;
 	}
 
+	get replyTo(): number {
+		return this._replyTo;
+	}
+
+	get replyToMessage(): Message | undefined {
+		if (this._replyTo == null) return undefined;
+
+		return get(this._session.messages).find(
+			(m) => m instanceof Message && m.id == this._replyTo
+		) as Message | undefined;
+	}
+
 	async update(content: string, metadata: { message: string; date: number }[]): Promise<boolean> {
-		const response = await updateMessageAPI(this._session.id, this._message_id, content, metadata);
+		const response = await updateMessageAPI(
+			fetch,
+			this._session.id,
+			this._message_id,
+			content,
+			metadata
+		);
 		if (response == null || response.id == null) return false;
 
 		this._versions.update((v) => [...v, { content: content, date: new Date() }]);
@@ -91,7 +109,7 @@ export default class Message {
 
 	async getMessageById(id: number): Promise<Message | null> {
 		try {
-			const response = await getMessagesAPI(this._session.id); // Fetch all messages for the session
+			const response = await getMessagesAPI(fetch, this._session.id); // Fetch all messages for the session
 			if (!response) {
 				toastAlert('Failed to retrieve messages from the server.');
 				return null;
@@ -132,13 +150,17 @@ export default class Message {
 
 	async addFeedback(start: number, end: number, content: string | null = null): Promise<boolean> {
 		const response = await createMessageFeedbackAPI(
+			fetch,
 			this._session.id,
 			this._id,
 			start,
 			end,
 			content
 		);
-		if (response == -1) return false;
+		if (!response) {
+			toastAlert('Failed to create feedback');
+			return false;
+		}
 
 		const feedback = new Feedback(response, this, start, end, content);
 		this.localFeedback(feedback);
diff --git a/frontend/src/lib/types/session.ts b/frontend/src/lib/types/session.ts
index d7b465fab3729a0624154336683410c6db34a4db..04cfb8fd773275fea2815161d0898059f0db8ac9 100644
--- a/frontend/src/lib/types/session.ts
+++ b/frontend/src/lib/types/session.ts
@@ -1,17 +1,25 @@
 import { toastAlert } from '$lib/utils/toasts';
 import { get, writable, type Writable } from 'svelte/store';
-import User, { user } from './user';
-import { axiosInstance } from '$lib/api/apiInstance';
+import User from './user';
 import {
+	addUserToSessionAPI,
 	createMessageAPI,
+	createSessionAPI,
 	createSessionSatisfyAPI,
+	deleteSessionAPI,
 	getMessagesAPI,
-	patchLanguageAPI
+	patchLanguageAPI,
+	patchSessionAPI,
+	removeUserFromSessionAPI,
+	sendPresenceAPI,
+	sendTypingAPI
 } from '$lib/api/sessions';
 import Message from './message';
 import config from '$lib/config';
 import Feedback from './feedback';
 import { parseToLocalDate } from '$lib/utils/date';
+import { t } from '$lib/services/i18n';
+import type { fetchType } from '$lib/utils/types';
 
 const { subscribe, set, update } = writable<Session[]>([]);
 
@@ -40,6 +48,7 @@ export default class Session {
 	private _onlineUsers: Writable<Set<number>> = writable(new Set());
 	private _onlineTimers: Map<number, number> = new Map();
 	private _length: number;
+	private _user: User | null = null;
 
 	private constructor(
 		id: number,
@@ -118,7 +127,7 @@ export default class Session {
 
 	usersList(maxLength = 30): string {
 		const users = this._users
-			.filter((u) => u.id != get(user)?.id)
+			.filter((u) => u.id != this._user?.id)
 			.map((user) => user.nickname)
 			.join(', ');
 		if (users.length < maxLength) {
@@ -129,7 +138,7 @@ export default class Session {
 
 	otherUsersList(maxLength = 30): string {
 		const users = this._users
-			.filter((u) => u.id != get(user)?.id)
+			.filter((u) => u.id != this._user?.id)
 			.map((user) => user.nickname)
 			.join(', ');
 		if (users.length < maxLength) {
@@ -139,10 +148,9 @@ export default class Session {
 	}
 
 	async delete(): Promise<boolean> {
-		const response = await axiosInstance.delete(`/sessions/${this.id}`);
-
-		if (response.status !== 204) {
-			toastAlert('Failed to delete session');
+		const response = await deleteSessionAPI(fetch, this.id);
+		if (!response) {
+			toastAlert(get(t)('session.errors.delete'));
 			return false;
 		}
 
@@ -151,12 +159,12 @@ export default class Session {
 	}
 
 	async toggleDisable(): Promise<boolean> {
-		const response = await axiosInstance.patch(`/sessions/${this.id}`, {
+		const response = await patchSessionAPI(fetch, this.id, {
 			is_active: !this.is_active
 		});
 
-		if (response.status !== 204) {
-			toastAlert('Failed to toggle activite session');
+		if (!response) {
+			toastAlert(get(t)('session.errors.toggle'));
 			return false;
 		}
 
@@ -166,10 +174,9 @@ export default class Session {
 	}
 
 	async addUser(user: User): Promise<boolean> {
-		const response = await axiosInstance.post(`/sessions/${this.id}/users/${user.id}`);
-
-		if (response.status !== 201) {
-			toastAlert('Failed to add user to session');
+		const response = await addUserToSessionAPI(fetch, this.id, user.id);
+		if (!response) {
+			toastAlert(get(t)('session.errors.addUser'));
 			return false;
 		}
 
@@ -183,8 +190,8 @@ export default class Session {
 		return this._users.some((u) => u.equals(user));
 	}
 
-	async loadMessages(): Promise<boolean> {
-		const messagesStr = await getMessagesAPI(this.id);
+	async loadMessages(f: fetchType = fetch): Promise<boolean> {
+		const messagesStr = await getMessagesAPI(f, this.id);
 
 		this._messages.set(Message.parseAll(messagesStr));
 		return true;
@@ -196,9 +203,8 @@ export default class Session {
 		metadata: { message: string; date: number }[],
 		replyTo: number | null
 	): Promise<Message | null> {
-		const json = await createMessageAPI(this.id, content, metadata, replyTo);
-
-		if (!json || !json.id || !json.message_id) {
+		const json = await createMessageAPI(fetch, this.id, content, metadata, replyTo);
+		if (json == null || json.id == null || json.message_id == null) {
 			toastAlert('Failed to parse message');
 			return null;
 		}
@@ -216,9 +222,9 @@ export default class Session {
 	}
 
 	async sendTyping(): Promise<boolean> {
-		const response = await axiosInstance.post(`/sessions/${this.id}/typing`);
-		if (response.status !== 204) {
-			console.log('Failed to send typing data', response);
+		const response = await sendTypingAPI(fetch, this.id);
+		if (!response) {
+			toastAlert(get(t)('session.errors.typing'));
 			return false;
 		}
 
@@ -226,9 +232,9 @@ export default class Session {
 	}
 
 	async sendPresence(): Promise<boolean> {
-		const response = await axiosInstance.post(`/sessions/${this.id}/presence`);
-		if (response.status !== 204) {
-			console.log('Failed to send presence data', response);
+		const response = await sendPresenceAPI(fetch, this.id);
+		if (!response) {
+			toastAlert(get(t)('session.errors.presence'));
 			return false;
 		}
 
@@ -236,11 +242,11 @@ export default class Session {
 	}
 
 	async sendSatisfy(usefullness: number, easiness: number, remarks: string): Promise<boolean> {
-		return await createSessionSatisfyAPI(this.id, usefullness, easiness, remarks);
+		return await createSessionSatisfyAPI(fetch, this.id, usefullness, easiness, remarks);
 	}
 
 	async changeLanguage(language: string): Promise<boolean> {
-		const res = await patchLanguageAPI(this.id, language);
+		const res = await patchLanguageAPI(fetch, this.id, language);
 		if (!res) return false;
 		this._language = language;
 		return true;
@@ -368,10 +374,9 @@ export default class Session {
 	}
 
 	async removeUser(user: User): Promise<boolean> {
-		const response = await axiosInstance.delete(`/sessions/${this.id}/users/${user.id}`);
-
-		if (response.status !== 204) {
-			toastAlert('Failed to remove user from session');
+		const response = await removeUserFromSessionAPI(fetch, this.id, user.id);
+		if (!response) {
+			toastAlert(get(t)('session.errors.removeUser'));
 			return false;
 		}
 
@@ -435,13 +440,12 @@ export default class Session {
 	}
 
 	static async create(): Promise<Session | null> {
-		const response = await axiosInstance.post('/sessions');
-
-		if (response.status !== 200) {
-			toastAlert('Failed to create session');
+		const response = await createSessionAPI(fetch);
+		if (!response) {
+			toastAlert(get(t)('session.errors.create'));
 			return null;
 		}
 
-		return Session.parse(response.data);
+		return Session.parse(response);
 	}
 }
diff --git a/frontend/src/lib/types/user.ts b/frontend/src/lib/types/user.ts
index cb287f7600d5ef855c6b17ee202089069fb6a46b..1d9e25291992044c526ac5f42ea4980103b4d4ce 100644
--- a/frontend/src/lib/types/user.ts
+++ b/frontend/src/lib/types/user.ts
@@ -1,12 +1,11 @@
 import { createUserAPI, getUsersAPI, patchUserAPI } from '$lib/api/users';
 import { parseToLocalDate } from '$lib/utils/date';
 import { toastAlert } from '$lib/utils/toasts';
+import { sha256 } from 'js-sha256';
 import { get, writable } from 'svelte/store';
 
 const { subscribe, set, update } = writable<User[]>([]);
 
-export const user = writable<User | null>(null);
-
 export const users = {
 	subscribe,
 	set,
@@ -15,7 +14,7 @@ export const users = {
 	add: (user: User) => update((users) => [...users, user]),
 	delete: (id: number) => update((users) => users.filter((user) => user.id !== id)),
 	search: (email: string) => get(users).find((user) => user.email.includes(email)),
-	fetch: async () => User.parseAll(await getUsersAPI())
+	fetch: async () => User.parseAll(await getUsersAPI(fetch))
 };
 
 export default class User {
@@ -23,7 +22,6 @@ export default class User {
 	private _email: string;
 	private _nickname: string;
 	private _type: number;
-	private _availability: bigint;
 	private _is_active: boolean;
 	private _ui_language: string | null;
 	private _home_language: string | null;
@@ -39,7 +37,6 @@ export default class User {
 		email: string,
 		nickname: string,
 		type: number,
-		availability: bigint,
 		is_active: boolean,
 		ui_language: string | null,
 		home_language: string | null,
@@ -54,7 +51,6 @@ export default class User {
 		this._email = email;
 		this._nickname = nickname;
 		this._type = type;
-		this._availability = availability;
 		this._is_active = is_active;
 		this._ui_language = ui_language;
 		this._home_language = home_language;
@@ -74,6 +70,10 @@ export default class User {
 		return this._email;
 	}
 
+	get emailHash(): string {
+		return sha256(this._email.toLowerCase());
+	}
+
 	get nickname(): string {
 		return this._nickname;
 	}
@@ -94,10 +94,6 @@ export default class User {
 		return this._type === 1;
 	}
 
-	get availability(): bigint {
-		return this._availability;
-	}
-
 	get ui_language(): string | null {
 		return this._ui_language;
 	}
@@ -139,7 +135,7 @@ export default class User {
 	}
 
 	async setAvailability(availability: bigint, calcom_link: string): Promise<boolean> {
-		return await patchUserAPI(this.id, {
+		return await patchUserAPI(fetch, this.id, {
 			availability: availability.toString(),
 			calcom_link: calcom_link
 		});
@@ -155,7 +151,6 @@ export default class User {
 			email: this.email,
 			nickname: this.nickname,
 			type: this.type,
-			availability: this.availability.toString(),
 			is_active: this.is_active,
 			ui_language: this.ui_language,
 			home_language: this.home_language,
@@ -169,12 +164,11 @@ export default class User {
 	}
 
 	async patch(data: any): Promise<boolean> {
-		const res = await patchUserAPI(this.id, data);
+		const res = await patchUserAPI(fetch, this.id, data);
 		if (res) {
 			if (data.email) this._email = data.email;
 			if (data.nickname) this._nickname = data.nickname;
 			if (data.type) this._type = data.type;
-			if (data.availability) this._availability = BigInt(data.availability);
 			if (data.is_active) this._is_active = data.is_active;
 			if (data.ui_language) this._ui_language = data.ui_language;
 			if (data.home_language) this._home_language = data.home_language;
@@ -199,7 +193,7 @@ export default class User {
 		type: number,
 		is_active: boolean
 	): Promise<User | null> {
-		const id = await createUserAPI(nickname, email, password, type, is_active);
+		const id = await createUserAPI(fetch, nickname, email, password, type, is_active);
 		if (id == null) return null;
 
 		const user = new User(
@@ -207,7 +201,6 @@ export default class User {
 			email,
 			nickname,
 			type,
-			BigInt(0),
 			is_active,
 			null,
 			null,
@@ -232,8 +225,6 @@ export default class User {
 		const userFinal = User.parse(userObject);
 		if (userFinal == null || userFinal.id == null || userFinal.id == undefined) return null;
 
-		user.set(userFinal);
-
 		return userFinal;
 	}
 
@@ -249,7 +240,6 @@ export default class User {
 			json.email,
 			json.nickname,
 			json.type,
-			BigInt(json.availability),
 			json.is_active,
 			json.ui_language,
 			json.home_language,
diff --git a/frontend/src/lib/utils/replyUtils.ts b/frontend/src/lib/utils/replyUtils.ts
deleted file mode 100644
index 9efff3672179a1ffefc647908152a5166ec4be49..0000000000000000000000000000000000000000
--- a/frontend/src/lib/utils/replyUtils.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import type { Writable } from 'svelte/store';
-import type Message from '$lib/types/message';
-import { writable } from 'svelte/store';
-
-export const replyToMessage: Writable<Message | null> = writable(null);
-
-export function initiateReply(message: Message): void {
-	replyToMessage.set(message);
-}
-
-export function clearReplyToMessage(): void {
-	replyToMessage.set(null);
-}
diff --git a/frontend/src/lib/utils/security.ts b/frontend/src/lib/utils/security.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9d8bb0848f395da91a0346892433d4e36617ffca
--- /dev/null
+++ b/frontend/src/lib/utils/security.ts
@@ -0,0 +1,41 @@
+import { redirect } from '@sveltejs/kit';
+
+export function isRedirectPathValid(path: string, origin: string): boolean {
+	try {
+		const url = new URL(path, origin);
+		return url.origin == origin;
+	} catch (e) {
+		return false;
+	}
+}
+
+export function safeRedirect(path: string | null | undefined, origin: string) {
+	if (!path || !isRedirectPathValid(path, origin)) path = '/';
+	return redirect(302, path);
+}
+
+export function safeRedirectAuto(url: URL) {
+	return safeRedirect(url.searchParams.get('redirect'), url.origin);
+}
+
+export function validateEmail(email: unknown): email is string {
+	return (
+		typeof email === 'string' &&
+		email.length >= 3 &&
+		email.length <= 255 &&
+		/^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/.test(email)
+	);
+}
+
+export function validateUsername(username: unknown): username is string {
+	return (
+		typeof username === 'string' &&
+		username.length >= 3 &&
+		username.length <= 31 &&
+		/^[a-z0-9_-]+$/.test(username)
+	);
+}
+
+export function validatePassword(password: unknown): password is string {
+	return typeof password === 'string' && password.length >= 8 && password.length <= 255;
+}
diff --git a/frontend/src/lib/utils/types.ts b/frontend/src/lib/utils/types.ts
new file mode 100644
index 0000000000000000000000000000000000000000..479544ba236a92006d803a94d426b5654307c22a
--- /dev/null
+++ b/frontend/src/lib/utils/types.ts
@@ -0,0 +1 @@
+export type fetchType = typeof fetch;
diff --git a/frontend/src/routes/+layout.server.ts b/frontend/src/routes/+layout.server.ts
index a621bd9cee4de615e70a753670322e7e8aed3daa..b3aecd87510c735f98e5cb872178617dbaeaccd9 100644
--- a/frontend/src/routes/+layout.server.ts
+++ b/frontend/src/routes/+layout.server.ts
@@ -14,13 +14,12 @@ const isPublic = (path: string) => {
 export const load: ServerLoad = async ({ locals, url }) => {
 	if (locals.user == null || locals.user == undefined) {
 		if (!isPublic(url.pathname)) {
-			redirect(307, `/login`);
+			redirect(303, `/login`);
 		}
 	}
 
 	return {
 		user: locals.user,
-		session: locals.session,
 		locale: locals.locale
 	};
 };
diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte
index a7c90acd7077955dfa01f2d16d89d3691062ec50..d200c9f18528a75f137b466ad91c8d936bd2c8fd 100644
--- a/frontend/src/routes/+layout.svelte
+++ b/frontend/src/routes/+layout.svelte
@@ -1,23 +1,20 @@
 <script lang="ts">
 	import { SvelteToast } from '@zerodevx/svelte-toast';
-	import Header from '$lib/components/header.svelte';
 	import '../app.css';
 	import { t } from '$lib/services/i18n';
-	import User from '$lib/types/user.js';
+	import Header from './Header.svelte';
+	import type { PageData } from './$types';
 
-	export let data;
-
-	User.parseFromServer(data);
-
-	console.log(2);
+	let { data, children }: { data: PageData; children: any } = $props();
+	let user = data.user;
 </script>
 
 <svelte:head>
 	<title>{$t('header.appName')}</title>
 </svelte:head>
 
-<Header />
+<Header {user} />
 
-<slot />
+{@render children()}
 
 <SvelteToast />
diff --git a/frontend/src/routes/+layout.ts b/frontend/src/routes/+layout.ts
index 8ec665a89c83295e236d219427d8a3b1b0c96a4d..0b0140c24beaa6b54ce5506d96ea71d22696fb49 100644
--- a/frontend/src/routes/+layout.ts
+++ b/frontend/src/routes/+layout.ts
@@ -1,16 +1,20 @@
 export const ssr = true;
 
-import type { Load } from '@sveltejs/kit';
+import { error, type Load } from '@sveltejs/kit';
 import { loadTranslations } from '$lib/services/i18n';
+import User from '$lib/types/user';
 
 export const load: Load = async ({ url, data }) => {
-	const { user, session, locale } = data;
+	if (!data) {
+		return error(500, 'No data');
+	}
+
+	const { user, locale } = data!;
 	const { pathname } = url;
 
 	await loadTranslations(locale, pathname);
 
 	return {
-		user,
-		token: session
+		user: user ? User.parse(user) : null
 	};
 };
diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte
index ed9f6fef645c15796ec431ddc8aaa58ffe989371..4b5ece68436f1e598bffc54c1b8e5858feb87b37 100644
--- a/frontend/src/routes/+page.svelte
+++ b/frontend/src/routes/+page.svelte
@@ -11,7 +11,7 @@
 		ArrowRightCircle
 	} from 'svelte-hero-icons';
 	import { t } from '$lib/services/i18n';
-	import User, { user } from '$lib/types/user';
+	import User from '$lib/types/user';
 	import {
 		createUserContactFromEmailAPI,
 		getUserContactsAPI,
@@ -21,16 +21,18 @@
 	import { toastAlert, toastSuccess, toastWarning } from '$lib/utils/toasts';
 	import { get } from 'svelte/store';
 
-	let ready = false;
-	$: contacts = [] as User[];
-	$: contact = null as User | null;
-	$: contactSessions = [] as Session[];
-	let modalNew = false;
-	let nickname = '';
+	let { data } = $props();
+	let user = data.user!;
+	let contacts: User[] = $state(data.contacts);
+	let contact: User | undefined = $state(data.contact);
+	let contactSessions: Session[] = $state(data.sessions);
 
-	let showTerminatedSessions = false;
+	let modalNew = $state(false);
+	let nickname = $state('');
 
-	async function selectContact(c: User | null) {
+	let showTerminatedSessions = $state(false);
+
+	async function selectContact(c: User | undefined) {
 		showTerminatedSessions = false;
 		contact = c;
 		if (!contact) {
@@ -38,20 +40,12 @@
 			return;
 		}
 
-		contactSessions = Session.parseAll(await getUserContactSessionsAPI($user!.id, contact.id)).sort(
-			(a, b) => b.start_time.getTime() - a.start_time.getTime()
-		);
+		contactSessions = Session.parseAll(
+			await getUserContactSessionsAPI(fetch, user.id, contact.id)
+		).sort((a, b) => b.start_time.getTime() - a.start_time.getTime());
 	}
 
 	onMount(async () => {
-		if (!$user) return;
-		contacts = User.parseAll(await getUserContactsAPI($user.id));
-		if (contacts.length > 0) {
-			selectContact(contacts[0]);
-		}
-
-		ready = true;
-
 		(function (C: any, A: any, L: any) {
 			let p = function (a: any, ar: any) {
 				a.q.push(ar);
@@ -90,7 +84,7 @@
 		Cal('on', {
 			action: 'bookingSuccessful',
 			callback: async (e: any) => {
-				if (!contact || !$user || !e.detail.data) {
+				if (!contact || !user || !e.detail.data) {
 					toastAlert(get(t)('home.bookingFailed'));
 					return;
 				}
@@ -99,7 +93,8 @@
 				const duration = e.detail.data.duration;
 				const end = new Date(date.getTime() + duration * 60000);
 				const sess_id: number | null = await createSessionFromCalComAPI(
-					$user.id,
+					fetch,
+					user.id,
 					contact.id,
 					date,
 					end
@@ -110,7 +105,7 @@
 				}
 				toastSuccess(get(t)('home.bookingSuccessful'));
 				contactSessions = Session.parseAll(
-					await getUserContactSessionsAPI($user!.id, contact.id)
+					await getUserContactSessionsAPI(fetch, user!.id, contact.id)
 				).sort((a, b) => b.start_time.getTime() - a.start_time.getTime());
 			}
 		});
@@ -127,172 +122,185 @@
 	}
 
 	async function searchNickname() {
-		if (!$user || !nickname || !nickname.includes('@')) {
+		if (!user || !nickname || !nickname.includes('@')) {
 			toastWarning('Please enter a valid email address');
 			return;
 		}
 
-		const res = await createUserContactFromEmailAPI($user.id, nickname);
+		const res = await createUserContactFromEmailAPI(fetch, user.id, nickname);
 		if (!res) return;
 
 		modalNew = false;
-		contacts = User.parseAll(await getUserContactsAPI($user.id));
+		contacts = User.parseAll(await getUserContactsAPI(fetch, user.id));
 	}
 </script>
 
-{#if ready}
-	<div class="flex-row h-full flex py-4 flex-grow overflow-y-hidden">
-		<div class="flex flex-col border shadow-[0_0_6px_0_rgba(0,14,156,.2)] min-w-72 rounded-r-xl">
-			<div class="flex-grow">
-				{#each contacts as c (c.id)}
-					<div
-						class="h-24 flex border-gray-300 border-b-2 hover:bg-gray-200 hover:cursor-pointer p-4"
-						class:bg-gray-200={c.id === contact?.id}
-						on:click={() => selectContact(c)}
-						role="button"
-						aria-label={c.nickname}
-						tabindex="0"
-						on:keydown={(e) => e.key === 'Enter' && selectContact(c)}
-					>
-						<div class="w-16 ml-2 mr-4 p-4 bg-gray-300 rounded-2xl">
-							{#if c.type == 0}
-								<Icon src={Sparkles} class="mask mask-squircle" />
-							{:else if c.type == 1}
-								<Icon src={AcademicCap} class="" />
-							{:else}
-								<Icon src={UserIcon} />
-							{/if}
-						</div>
-						<div class="text-lg font-bold capitalize flex items-center">
-							{c.nickname}
-						</div>
+<div class="flex-row h-full flex py-4 flex-grow overflow-y-hidden">
+	<div class="flex flex-col border shadow-[0_0_6px_0_rgba(0,14,156,.2)] min-w-72 rounded-r-xl">
+		<div class="flex-grow">
+			{#each contacts as c (c.id)}
+				<div
+					class="h-24 flex border-gray-300 border-b-2 hover:bg-gray-200 hover:cursor-pointer p-4"
+					class:bg-gray-200={c.id === contact?.id}
+					onclick={() => selectContact(c)}
+					role="button"
+					aria-label={c.nickname}
+					tabindex="0"
+					onkeydown={(e) => e.key === 'Enter' && selectContact(c)}
+				>
+					<div class="w-16 ml-2 mr-4 p-4 bg-gray-300 rounded-2xl">
+						{#if c.type == 0}
+							<Icon src={Sparkles} class="mask mask-squircle" />
+						{:else if c.type == 1}
+							<Icon src={AcademicCap} class="" />
+						{:else}
+							<Icon src={UserIcon} />
+						{/if}
+					</div>
+					<div class="text-lg font-bold capitalize flex items-center">
+						{c.nickname}
 					</div>
-				{/each}
-			</div>
-			<button
-				class="h-20 w-full flex justify-center items-center text-lg border-gray-200 border-t hover:bg-gray-200"
-				on:click={() => (modalNew = true)}
-			>
-				+
-			</button>
-		</div>
-		{#if contact}
-			<div class="flex flex-col xl:mx-auto xl:w-[60rem] m-4">
-				<div>
-					<button on:click|preventDefault={createSession} class="button float-start mr-2">
-						{$t('home.createSession')}
-					</button>
-					<button
-						class="button float-start"
-						class:btn-disabled={!contact || !contact.calcom_link}
-						data-cal-link={`${contact.calcom_link}?email=${$user?.email}&name=${$user?.nickname}`}
-					>
-						{$t('home.bookSession')}
-					</button>
 				</div>
-				<div
-					class="border p-4 mt-4 rounded-xl shadow-[0_0_6px_0_rgba(0,14,156,.2)] overflow-y-scroll no-scrollbar"
+			{/each}
+		</div>
+		<button
+			class="h-20 w-full flex justify-center items-center text-lg border-gray-200 border-t hover:bg-gray-200"
+			onclick={() => (modalNew = true)}
+		>
+			+
+		</button>
+	</div>
+	{#if contact}
+		<div class="flex flex-col xl:mx-auto xl:w-[60rem] m-4">
+			<div>
+				<button
+					onclick={(e) => {
+						e.preventDefault();
+						createSession();
+					}}
+					class="button float-start mr-2"
 				>
-					<table class="divide-y divide-neutral-300 text-center w-full table-fixed">
-						<thead>
+					{$t('home.createSession')}
+				</button>
+				<button
+					class="button float-start"
+					class:btn-disabled={!contact || !contact.calcom_link}
+					data-cal-link={`${contact.calcom_link}?email=${user?.email}&name=${user?.nickname}`}
+				>
+					{$t('home.bookSession')}
+				</button>
+			</div>
+			<div
+				class="border p-4 mt-4 rounded-xl shadow-[0_0_6px_0_rgba(0,14,156,.2)] overflow-y-scroll no-scrollbar"
+			>
+				<table class="divide-y divide-neutral-300 text-center w-full table-fixed">
+					<thead>
+						<tr>
+							<th scope="col" class="text-left">{$t('utils.words.date')}</th>
+							<th scope="col">{$t('utils.words.status')}</th>
+							<th scope="col"># {$t('utils.words.messages').toLowerCase()}</th>
+							<th scope="col">{$t('utils.words.actions')}</th>
+						</tr>
+					</thead>
+					<tbody class="divide-y divide-neutral-200">
+						{#if contactSessions.length === 0}
 							<tr>
-								<th scope="col" class="text-left">{$t('utils.words.date')}</th>
-								<th scope="col">{$t('utils.words.status')}</th>
-								<th scope="col"># {$t('utils.words.messages').toLowerCase()}</th>
-								<th scope="col">{$t('utils.words.actions')}</th>
+								<td colspan="4" class="py-5 text-gray-500">{$t('home.noSessions')}</td>
 							</tr>
-						</thead>
-						<tbody class="divide-y divide-neutral-200">
-							{#if contactSessions.length === 0}
+						{:else}
+							{#if !showTerminatedSessions && contactSessions.filter((s) => s.end_time >= new Date()).length === 0}
 								<tr>
-									<td colspan="4" class="py-5 text-gray-500">{$t('home.noSessions')}</td>
+									<td colspan="4" class="py-5 text-gray-500"
+										>{$t('home.noCurrentOrFutureSessions')}</td
+									>
 								</tr>
-							{:else}
-								{#if !showTerminatedSessions && contactSessions.filter((s) => s.end_time >= new Date()).length === 0}
+							{/if}
+							{#each contactSessions as s (s.id)}
+								{#if showTerminatedSessions || s.end_time >= new Date()}
 									<tr>
-										<td colspan="4" class="py-5 text-gray-500"
-											>{$t('home.noCurrentOrFutureSessions')}</td
-										>
+										<td class="py-2 text-left space-y-1">
+											<div>
+												{displayShortTime(s.start_time)}
+											</div>
+											<div class="text-sm italic text-gray-600">
+												{displayTimeSince(s.start_time)}
+											</div>
+										</td>
+										<td class="py-2">
+											{#if s.start_time <= new Date() && s.end_time >= new Date()}
+												<span class="bg-green-200 rounded-lg px-2 py-1"
+													>{$t('utils.words.inProgress')}</span
+												>
+											{:else if s.start_time > new Date()}
+												<span class="bg-orange-200 rounded-lg px-2 py-1"
+													>{$t('utils.words.programed')}</span
+												>
+											{:else}
+												<span class="bg-red-200 rounded-lg px-2 py-1"
+													>{$t('utils.words.finished')}</span
+												>
+											{/if}
+										</td>
+										<td class="py-2">{s.length} {$t('utils.words.messages').toLowerCase()}</td>
+										<td class="py-2">
+											<a href="/sessions/{s.id}" class="group">
+												<Icon
+													src={ArrowRightCircle}
+													size="32"
+													class="text-accent mx-auto group-hover:text-white group-hover:bg-accent rounded-full"
+												/>
+											</a>
+										</td>
 									</tr>
 								{/if}
-								{#each contactSessions as s (s.id)}
-									{#if showTerminatedSessions || s.end_time >= new Date()}
-										<tr>
-											<td class="py-2 text-left space-y-1">
-												<div>
-													{displayShortTime(s.start_time)}
-												</div>
-												<div class="text-sm italic text-gray-600">
-													{displayTimeSince(s.start_time)}
-												</div>
-											</td>
-											<td class="py-2">
-												{#if s.start_time <= new Date() && s.end_time >= new Date()}
-													<span class="bg-green-200 rounded-lg px-2 py-1"
-														>{$t('utils.words.inProgress')}</span
-													>
-												{:else if s.start_time > new Date()}
-													<span class="bg-orange-200 rounded-lg px-2 py-1"
-														>{$t('utils.words.programed')}</span
-													>
-												{:else}
-													<span class="bg-red-200 rounded-lg px-2 py-1"
-														>{$t('utils.words.finished')}</span
-													>
-												{/if}
-											</td>
-											<td class="py-2">{s.length} {$t('utils.words.messages').toLowerCase()}</td>
-											<td class="py-2">
-												<a href="/session?id={s.id}" class="group">
-													<Icon
-														src={ArrowRightCircle}
-														size="32"
-														class="text-accent mx-auto group-hover:text-white group-hover:bg-accent rounded-full"
-													/>
-												</a>
-											</td>
-										</tr>
-									{/if}
-								{/each}
-								<tr>
-									<td
-										class="py-2 hover:cursor-pointer"
-										colspan="4"
-										on:click={() => (showTerminatedSessions = !showTerminatedSessions)}
-									>
-										<button aria-label={showTerminatedSessions ? 'Hide' : 'Show'}>
-											<svg
-												class="size-3 ms-3"
-												aria-hidden="true"
-												xmlns="http://www.w3.org/2000/svg"
-												fill="none"
-												viewBox="0 0 10 6"
-												class:rotate-180={showTerminatedSessions}
-											>
-												<path
-													stroke="currentColor"
-													stroke-linecap="round"
-													stroke-linejoin="round"
-													stroke-width="2"
-													d="m1 1 4 4 4-4"
-												/>
-											</svg>
-										</button>
-									</td>
-								</tr>
-							{/if}
-						</tbody>
-					</table>
-				</div>
+							{/each}
+							<tr>
+								<td
+									class="py-2 hover:cursor-pointer"
+									colspan="4"
+									onclick={() => (showTerminatedSessions = !showTerminatedSessions)}
+								>
+									<button aria-label={showTerminatedSessions ? 'Hide' : 'Show'}>
+										<svg
+											class="size-3 ms-3"
+											aria-hidden="true"
+											xmlns="http://www.w3.org/2000/svg"
+											fill="none"
+											viewBox="0 0 10 6"
+											class:rotate-180={showTerminatedSessions}
+										>
+											<path
+												stroke="currentColor"
+												stroke-linecap="round"
+												stroke-linejoin="round"
+												stroke-width="2"
+												d="m1 1 4 4 4-4"
+											/>
+										</svg>
+									</button>
+								</td>
+							</tr>
+						{/if}
+					</tbody>
+				</table>
 			</div>
-		{/if}
-	</div>
-{/if}
+		</div>
+	{:else}
+		<div class="flex-grow text-center mt-16">
+			<div class="text-lg text-gray-500 pt-4 italic">{$t('home.noContact')}</div>
+			<div>
+				<button class="mx-auto mt-8 button" onclick={() => (modalNew = true)}>
+					+ {$t('home.newFirstContact')}
+				</button>
+			</div>
+		</div>
+	{/if}
+</div>
 
 <dialog
 	class="modal bg-black bg-opacity-50"
 	open={modalNew}
-	on:close={() => (modalNew = false)}
+	onclose={() => (modalNew = false)}
 	tabindex="-1"
 >
 	<div class="modal-box">
@@ -303,9 +311,9 @@
 				placeholder={$t('home.email')}
 				bind:value={nickname}
 				class="input flex-grow mr-2"
-				on:keydown={(e) => e.key === 'Escape' && (modalNew = false)}
+				onkeydown={(e) => e.key === 'Escape' && (modalNew = false)}
 			/>
-			<button class="button w-16" on:click={searchNickname}>
+			<button class="button w-16" onclick={searchNickname}>
 				<Icon src={MagnifyingGlass} />
 			</button>
 		</div>
diff --git a/frontend/src/routes/+page.ts b/frontend/src/routes/+page.ts
new file mode 100644
index 0000000000000000000000000000000000000000..62a009b9964eab8a83544ae481bf25066b59087d
--- /dev/null
+++ b/frontend/src/routes/+page.ts
@@ -0,0 +1,29 @@
+import { getUserContactsAPI, getUserContactSessionsAPI } from '$lib/api/users';
+import Session from '$lib/types/session';
+import User from '$lib/types/user';
+import type { Load } from '@sveltejs/kit';
+
+export const load: Load = async ({ parent, fetch }) => {
+	const { user } = await parent();
+
+	const contacts = User.parseAll(await getUserContactsAPI(fetch, user.id));
+
+	if (contacts.length === 0) {
+		return {
+			contacts,
+			contact: undefined,
+			sessions: []
+		};
+	}
+
+	const contact = contacts[0];
+	const sessions = Session.parseAll(
+		await getUserContactSessionsAPI(fetch, user.id, contact.id)
+	).sort((a, b) => b.start_time.getTime() - a.start_time.getTime());
+
+	return {
+		contacts,
+		contact,
+		sessions
+	};
+};
diff --git a/frontend/src/lib/components/header.svelte b/frontend/src/routes/Header.svelte
similarity index 71%
rename from frontend/src/lib/components/header.svelte
rename to frontend/src/routes/Header.svelte
index 6f8db00fa5e12bd1f0960983a65c6ebef0710648..2860508ee6c8087caf91af12e86e2dd2ca6811aa 100644
--- a/frontend/src/lib/components/header.svelte
+++ b/frontend/src/routes/Header.svelte
@@ -1,30 +1,22 @@
 <script lang="ts">
-	import LocalSelector from './header/localSelector.svelte';
+	import LocalSelector from '$lib/components/header/localSelector.svelte';
 	import { t } from '$lib/services/i18n';
-	import {
-		ExclamationTriangle,
-		Icon,
-		Bars3,
-		UserCircle,
-		Language,
-		Cog6Tooth
-	} from 'svelte-hero-icons';
-	import { onMount } from 'svelte';
-	import { user } from '$lib/types/user';
+	import { ExclamationTriangle, Icon, Bars3, Language, Cog6Tooth } from 'svelte-hero-icons';
 	import { page } from '$app/stores';
+	import type User from '$lib/types/user';
 
-	$: displayMetadataWarning = false;
+	let { user }: { user: User | null } = $props();
 
-	onMount(async () => {
-		if ($user) {
-			if (!$user.home_language || !$user.target_language || !$user.birthdate || !$user.gender) {
-				displayMetadataWarning = true;
-			}
+	let displayMetadataWarning = $state(false);
+
+	if (user) {
+		if (!user.home_language || !user.target_language || !user.birthdate || !user.gender) {
+			displayMetadataWarning = true;
 		}
-	});
+	}
 </script>
 
-<header class="navbar shadow">
+<header class="navbar shadow bg-gray-200">
 	<div class="navbar-start">
 		<div class="dropdown sm:hidden p-0">
 			<div tabindex="0" role="button" class="btn btn-ghost">
@@ -34,7 +26,7 @@
 				tabindex="-1"
 				class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52"
 			>
-				{#if $user}
+				{#if user}
 					<li><a href="/">Item 1</a></li>
 					<li>
 						<a href="/">Parent</a>
@@ -58,29 +50,19 @@
 		</h1>
 	</div>
 	<div class="navbar-end hidden sm:flex">
-		<ul class="menu menu-horizontal p-0">
-			{#if $user}
+		<ul class="menu menu-horizontal p-0 flex items-center">
+			{#if user}
 				<li>
 					<details>
-						<summary class="p-3">
-							<Icon src={UserCircle} class="h-5 w-5" />
-							{$user.nickname}
+						<summary class="px-3">
+							<img
+								src={`https://gravatar.com/avatar/${user.emailHash}?d=identicon`}
+								alt={''}
+								class="rounded-full border text-sm size-8 border-neutral-400"
+							/>
+							{user.nickname}
 						</summary>
 						<ul class="menu menu-sm dropdown-content absolute right-0">
-							{#if $user?.type === 0 || $user?.type === 1}
-								<li>
-									<a data-sveltekit-reload href="/tutor/timeslots">
-										{$t('header.availability')}
-									</a>
-								</li>
-							{/if}
-							{#if $user?.type === 2}
-								<li>
-									<a data-sveltekit-reload href="/timeslots">
-										{$t('header.tutorSelection')}
-									</a>
-								</li>
-							{/if}
 							<li>
 								<a data-sveltekit-reload href="/logout" class="whitespace-nowrap">
 									{$t('header.signout')}
@@ -89,7 +71,7 @@
 						</ul>
 					</details>
 				</li>
-				{#if $user?.type === 0}
+				{#if user?.type === 0}
 					<li>
 						<details>
 							<summary class="p-3">
diff --git a/frontend/src/routes/admin/+layout.server.ts b/frontend/src/routes/admin/+layout.server.ts
index 28121ae92f7596cb99f9eba69fc0ed1624eaac95..fc19640b69189443952b6ec6678c2137e9c930fe 100644
--- a/frontend/src/routes/admin/+layout.server.ts
+++ b/frontend/src/routes/admin/+layout.server.ts
@@ -2,11 +2,10 @@ import { type ServerLoad, error, redirect } from '@sveltejs/kit';
 
 export const load: ServerLoad = async ({ locals }) => {
 	if (locals.user == null || locals.user == undefined) {
-		redirect(307, '/login');
+		redirect(303, '/login');
 	}
 
-	const user = JSON.parse(locals.user);
-	if (user == null || user == undefined || user.type != 0) {
+	if (locals.user == null || locals.user == undefined || locals.user.type != 0) {
 		error(403, 'Forbidden');
 	}
 };
diff --git a/frontend/src/routes/admin/+page.svelte b/frontend/src/routes/admin/+page.svelte
index e3a114a93ded997e4e4d428edd024247aab0cb2a..8f5550b099a9d9a3a0228e560e97f8c7bcb8dafc 100644
--- a/frontend/src/routes/admin/+page.svelte
+++ b/frontend/src/routes/admin/+page.svelte
@@ -1,24 +1,18 @@
 <script lang="ts">
-	import { onMount } from 'svelte';
-	import User, { users } from '$lib/types/user';
-	import { getUsersAPI } from '$lib/api/users';
+	import User from '$lib/types/user';
 	import { t } from '$lib/services/i18n';
 	import UserItem from '$lib/components/users/userItem.svelte';
+	import type { PageData } from './$types';
 
-	let ready = false;
+	let { data }: { data: PageData } = $props();
+	let users = data.users;
 
-	onMount(async () => {
-		User.parseAll(await getUsersAPI());
+	let nickname = $state('');
+	let email = $state('');
+	let type = $state('2');
+	let is_active = $state(true);
 
-		ready = true;
-	});
-
-	$: nickname = '';
-	$: email = '';
-	$: type = '2';
-	$: is_active = true;
-
-	$: canCreate = nickname !== '' && email !== '' && type !== '';
+	let canCreate = $derived(nickname !== '' && email !== '' && type !== '');
 
 	async function createUser() {
 		if (!canCreate) return;
@@ -40,54 +34,45 @@
 	}
 </script>
 
-{#if ready}
-	<div class="min-w-fit max-w-3xl mx-auto">
-		<h1 class="text-xl font-bold m-5 text-center">Users</h1>
-		<table class="table">
-			<thead>
-				<tr>
-					<th>#</th>
-					<th>{$t('users.nickname')}</th>
-					<th>{$t('users.email')}</th>
-					<th>{$t('users.category')}</th>
-					<th>{$t('users.isActive')}</th>
-					<th>{$t('admin.actions')}</th>
-				</tr>
-			</thead>
-			<tbody>
-				{#each $users as user (user.id)}
-					<UserItem {user} />
-				{/each}
-			</tbody>
-			<tfoot class="">
-				<tr class="">
-					<td>+</td>
-					<td><input type="text" class="input input-sm" bind:value={nickname} /></td>
-					<td><input type="text" class="input input-sm" bind:value={email} /></td>
-					<td>
-						<select class="select select-sm select-bordered" bind:value={type}>
-							<option value="2">{$t('users.type.student')}</option>
-							<option value="1">{$t('users.type.tutor')}</option>
-							<option value="0">{$t('users.type.admin')}</option>
-						</select>
-					</td>
-					<td>
-						<input type="checkbox" class="checkbox" bind:value={is_active} checked />
-					</td>
-					<td>
-						<button class="btn btn-sm" disabled={!canCreate} on:click={createUser}>
-							{$t('button.create')}
-						</button>
-					</td>
-				</tr>
-			</tfoot>
-		</table>
-	</div>
-{/if}
-
-<style lang="postcss">
-	/* input,
-	select {
-		@apply w-full border-2 h-8 text-center border-gray-400 rounded bg-transparent;
-	} */
-</style>
+<div class="min-w-fit max-w-3xl mx-auto">
+	<h1 class="text-xl font-bold m-5 text-center">Users</h1>
+	<table class="table">
+		<thead>
+			<tr>
+				<th>#</th>
+				<th>{$t('users.nickname')}</th>
+				<th>{$t('users.email')}</th>
+				<th>{$t('users.category')}</th>
+				<th>{$t('users.isActive')}</th>
+				<th>{$t('admin.actions')}</th>
+			</tr>
+		</thead>
+		<tbody>
+			{#each users as user (user.id)}
+				<UserItem {user} />
+			{/each}
+		</tbody>
+		<tfoot class="">
+			<tr class="">
+				<td>+</td>
+				<td><input type="text" class="input input-sm" bind:value={nickname} /></td>
+				<td><input type="text" class="input input-sm" bind:value={email} /></td>
+				<td>
+					<select class="select select-sm select-bordered" bind:value={type}>
+						<option value="2">{$t('users.type.student')}</option>
+						<option value="1">{$t('users.type.tutor')}</option>
+						<option value="0">{$t('users.type.admin')}</option>
+					</select>
+				</td>
+				<td>
+					<input type="checkbox" class="checkbox" bind:checked={is_active} />
+				</td>
+				<td>
+					<button class="btn btn-sm" disabled={!canCreate} onclick={createUser}>
+						{$t('button.create')}
+					</button>
+				</td>
+			</tr>
+		</tfoot>
+	</table>
+</div>
diff --git a/frontend/src/routes/admin/+page.ts b/frontend/src/routes/admin/+page.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b9469731e5a6f922c0d7b269e6c162a198d75ae1
--- /dev/null
+++ b/frontend/src/routes/admin/+page.ts
@@ -0,0 +1,11 @@
+import { getUsersAPI } from '$lib/api/users';
+import User from '$lib/types/user';
+import { type Load } from '@sveltejs/kit';
+
+export const load: Load = async ({ fetch }) => {
+	const users = User.parseAll(await getUsersAPI(fetch));
+
+	return {
+		users
+	};
+};
diff --git a/frontend/src/routes/admin/sessions/+page.svelte b/frontend/src/routes/admin/sessions/+page.svelte
index d50db59d3bd4d476d6b20ab7f92fca747d2eb3a1..dcf1ebbd398b606c3a3c95e8eb10ab06c2fd327d 100644
--- a/frontend/src/routes/admin/sessions/+page.svelte
+++ b/frontend/src/routes/admin/sessions/+page.svelte
@@ -1,17 +1,13 @@
 <script lang="ts">
-	import { getSessionsAPI } from '$lib/api/sessions';
 	import Session from '$lib/types/session';
-	import { onMount } from 'svelte';
 	import { t } from '$lib/services/i18n';
 	import { displayTime } from '$lib/utils/date';
 	import { ArrowDownTray, ArrowRightStartOnRectangle, Icon } from 'svelte-hero-icons';
 	import config from '$lib/config';
+	import type { PageData } from './$types';
 
-	let sessions: Session[] = [];
-
-	onMount(async () => {
-		sessions = Session.parseAll(await getSessionsAPI());
-	});
+	let { data }: { data: PageData } = $props();
+	let sessions: Session[] = data.sessions;
 </script>
 
 <h1 class="text-xl font-bold m-5 text-center">{$t('header.admin.sessions')}</h1>
diff --git a/frontend/src/routes/admin/sessions/+page.ts b/frontend/src/routes/admin/sessions/+page.ts
new file mode 100644
index 0000000000000000000000000000000000000000..de44a7d380e6c4137f91faba212534f13edb56b5
--- /dev/null
+++ b/frontend/src/routes/admin/sessions/+page.ts
@@ -0,0 +1,11 @@
+import { getSessionsAPI } from '$lib/api/sessions';
+import Session from '$lib/types/session';
+import { type Load } from '@sveltejs/kit';
+
+export const load: Load = async ({ fetch }) => {
+	const sessions = Session.parseAll(await getSessionsAPI(fetch));
+
+	return {
+		sessions
+	};
+};
diff --git a/frontend/src/routes/login/+page.server.ts b/frontend/src/routes/login/+page.server.ts
index 4a5396591590ac2ed219f30bfd6e6acde8a43e04..cdb6f040ccf0eb7162462b60842a5c1a6e4521e2 100644
--- a/frontend/src/routes/login/+page.server.ts
+++ b/frontend/src/routes/login/+page.server.ts
@@ -1,14 +1,44 @@
-import { type ServerLoad, redirect } from '@sveltejs/kit';
+import { safeRedirectAuto } from '$lib/utils/security';
+import { type Actions, type ServerLoad, redirect } from '@sveltejs/kit';
 
 export const load: ServerLoad = async ({ locals, url }) => {
 	if (locals.user != null && locals.user != undefined) {
 		const path = url.searchParams.get('redirect') || '/';
-		redirect(307, path);
+		redirect(303, path);
 	}
 
 	return {
 		user: locals.user,
-		session: locals.session,
+		jwt: locals.jwt,
 		locale: locals.locale
 	};
 };
+
+export const actions: Actions = {
+	default: async ({ request, url, fetch }) => {
+		const formData = await request.formData();
+
+		const email = formData.get('email');
+		const password = formData.get('password');
+
+		if (!email || !password) {
+			return {
+				message: 'Invalid request'
+			};
+		}
+
+		const response = await fetch(`/api/auth/login`, {
+			headers: {
+				'Content-Type': 'application/json'
+			},
+			method: 'POST',
+			body: JSON.stringify({ email, password })
+		});
+
+		if (response.status === 401) return { message: 'Incorrect email or password' };
+		if (response.status === 422) return { message: 'Invalid request' };
+		if (response.status !== 200) return { message: 'Unknown error occurred' };
+
+		return safeRedirectAuto(url);
+	}
+};
diff --git a/frontend/src/routes/login/+page.svelte b/frontend/src/routes/login/+page.svelte
index ea3e8952e443644da761d83ebb5961ab01ec9909..cf8fa60202f3045f45e7d2e6ec739f36f0bb71fd 100644
--- a/frontend/src/routes/login/+page.svelte
+++ b/frontend/src/routes/login/+page.svelte
@@ -1,26 +1,7 @@
 <script lang="ts">
-	import { loginAPI } from '$lib/api/auth';
-	import { getBaseURL } from '$lib/utils/login';
 	import { t } from '$lib/services/i18n';
 
-	let email = '';
-	let password = '';
-	$: message = '';
-
-	async function login() {
-		message = '';
-		const result = await loginAPI(email, password);
-		if (result !== 'OK') {
-			message = result;
-			return;
-		}
-
-		const redirect = decodeURIComponent(
-			new URLSearchParams(window.location.search).get('redirect') ?? getBaseURL()
-		);
-
-		window.location.href = redirect;
-	}
+	let { form }: { form: FormData } = $props();
 </script>
 
 <div class="flex justify-center items-center h-screen">
@@ -29,22 +10,16 @@
 
 		<p class="self-center text-sm">
 			{$t('login.noAccountText')}
-			<a data-sveltekit-reload href="/register" class="link link-secondary">
+			<a href="/register" class="link link-secondary">
 				{$t('login.noAccountLink')}
 			</a>
 		</p>
-
-		<!-- <a class="btn btn-neutral">
-				<i class="fa-brands fa-google text-primary"></i>
-				Log in with Google
-		</a>
-		
-		<div class="divider">OR</div>  -->
-
-		<form action="#">
-			{#if message}
-				<div class="alert alert-error">
-					{message}
+		<form method="POST">
+			{#if form?.message}
+				<div
+					class="border-2-lg rounded border border-red-600 bg-red-200 p-2 text-center text-red-900"
+				>
+					{form?.message}
 				</div>
 			{/if}
 
@@ -53,14 +28,7 @@
 					<span class="label-text">{$t('login.email')}</span>
 				</div>
 
-				<input
-					type="text"
-					id="email"
-					name="email"
-					class="input input-bordered"
-					bind:value={email}
-					required
-				/>
+				<input type="text" id="email" name="email" class="input input-bordered" required />
 			</label>
 
 			<label class="form-control">
@@ -75,34 +43,15 @@
 					id="password"
 					name="password"
 					class="input input-bordered"
-					bind:value={password}
 					required
 				/>
 			</label>
 
-			<!-- <div class="form-control">
-				<label class="cursor-pointer label self-start gap-2">
-					TODO: remember me
-					<input type="checkbox" class="checkbox" />
-					<span class="label-text">{$t('login.rememberMe')}</span> 
-				</label>
-			</div> -->
-
 			<div class="form-control mt-4">
-				<button type="submit" on:click|preventDefault={login} class="btn btn-primary">
+				<button type="submit" class="btn btn-primary">
 					{$t('login.login')}
 				</button>
 			</div>
 		</form>
 	</div>
 </div>
-
-<style lang="postcss">
-	/* label {
-		@apply font-bold pr-4 w-1/3 flex items-center justify-end;
-	}
-
-	input {
-		@apply input bg-base-200 w-[400px] py-2 px-4;
-	} */
-</style>
diff --git a/frontend/src/routes/logout/+page.server.ts b/frontend/src/routes/logout/+page.server.ts
index da26188eafc7e050b85f02adaabd57d29b413da0..7790fb9c026c13a8dfafae0d1e2b38ee1fb553f1 100644
--- a/frontend/src/routes/logout/+page.server.ts
+++ b/frontend/src/routes/logout/+page.server.ts
@@ -1,10 +1,8 @@
 import { type ServerLoad, redirect } from '@sveltejs/kit';
-import { access_cookie } from '$lib/api/apiInstance';
 
 export const load: ServerLoad = async ({ cookies }) => {
 	cookies.set('access_token_cookie', '', { maxAge: -1, path: '/' });
 	cookies.set('refresh_token_cookie', '', { maxAge: -1, path: '/' });
-	access_cookie.set('');
 
-	redirect(307, '/login');
+	redirect(303, '/login');
 };
diff --git a/frontend/src/routes/register/+page.server.ts b/frontend/src/routes/register/+page.server.ts
new file mode 100644
index 0000000000000000000000000000000000000000..12e004dbfd1149fc827697a68fbdc2a12e403a60
--- /dev/null
+++ b/frontend/src/routes/register/+page.server.ts
@@ -0,0 +1,85 @@
+import { patchUserAPI } from '$lib/api/users';
+import { formatToUTCDate } from '$lib/utils/date';
+import { validateEmail, validatePassword, validateUsername } from '$lib/utils/security';
+import { redirect, type Actions } from '@sveltejs/kit';
+
+export const actions: Actions = {
+	register: async ({ request, fetch }) => {
+		const formData = await request.formData();
+
+		const email = formData.get('email');
+		const nickname = formData.get('nickname');
+		const password = formData.get('password');
+		const confirmPassword = formData.get('confirmPassword');
+
+		if (!email || !nickname || !password || !confirmPassword) {
+			return { message: 'Invalid request' };
+		}
+
+		if (!validateEmail(email)) return { message: 'Invalid email' };
+		if (!validateUsername(nickname)) return { message: 'Invalid username' };
+		if (!validatePassword(password)) return { message: 'Invalid password' };
+
+		if (password !== confirmPassword) return { message: 'Passwords do not match' };
+
+		let response = await fetch(`/api/auth/register`, {
+			headers: {
+				'Content-Type': 'application/json'
+			},
+			method: 'POST',
+			body: JSON.stringify({ email, nickname, password, is_tutor: false })
+		});
+
+		if (response.status === 400) return { message: 'User already exists' };
+		if (response.status === 401) return { message: 'Failed to create user' };
+		if (response.status === 422) return { message: 'Invalid request' };
+		if (!response.ok) return { message: 'Unknown error occurred' };
+
+		response = await fetch(`/api/auth/login`, {
+			headers: {
+				'Content-Type': 'application/json'
+			},
+			method: 'POST',
+			body: JSON.stringify({ email, password })
+		});
+
+		if (response.status === 401) return { message: 'Incorrect email or password' };
+		if (response.status === 422) return { message: 'Invalid request' };
+		if (!response.ok) return { message: 'Unknown error occurred' };
+
+		return redirect(303, '/register');
+	},
+	data: async ({ request, fetch, locals }) => {
+		if (!locals.user) {
+			return { message: 'Unauthorized' };
+		}
+
+		const formData = await request.formData();
+
+		const homeLanguage = formData.get('homeLanguage');
+		const targetLanguage = formData.get('targetLanguage');
+		const birthyear = formData.get('birthyear');
+		const gender = formData.get('gender');
+
+		if (!homeLanguage || !targetLanguage || !birthyear || !gender) {
+			return { message: 'Invalid request' };
+		}
+
+		let birthdate;
+		try {
+			birthdate = formatToUTCDate(new Date(parseInt(birthyear.toString()), 0, 30));
+		} catch (e) {
+			return { message: 'Invalid request' };
+		}
+
+		const response = await patchUserAPI(fetch, locals.user.id, {
+			home_language: homeLanguage,
+			target_language: targetLanguage,
+			gender,
+			birthdate
+		});
+		if (!response) return { message: 'Unknown error occurred' };
+
+		redirect(303, '/register');
+	}
+};
diff --git a/frontend/src/routes/register/+page.svelte b/frontend/src/routes/register/+page.svelte
index 1debb565e26144d23e48ac912340039f41815187..4f63297d305c84dfa6f397efc566134d988563e9 100644
--- a/frontend/src/routes/register/+page.svelte
+++ b/frontend/src/routes/register/+page.svelte
@@ -1,178 +1,48 @@
 <script lang="ts">
-	import { loginAPI, registerAPI } from '$lib/api/auth';
 	import config from '$lib/config';
-	import { locale, t } from '$lib/services/i18n';
-	import { user } from '$lib/types/user';
-	import { toastAlert } from '$lib/utils/toasts';
-	import { onMount } from 'svelte';
-	import { get } from 'svelte/store';
-	import Timeslots from '$lib/components/users/timeslots.svelte';
-	import User, { users } from '$lib/types/user';
-	import {
-		getUsersAPI,
-		patchUserAPI,
-		createUserContactAPI,
-		getUserContactsAPI
-	} from '$lib/api/users';
+	import { t } from '$lib/services/i18n';
 	import { Icon, Envelope, Key, UserCircle } from 'svelte-hero-icons';
 	import Typingtest from '$lib/components/tests/typingtest.svelte';
-	import AvailableTutors from '$lib/components/users/availableTutors.svelte';
 	import { browser } from '$app/environment';
+	import type { PageData } from './$types';
 	import { formatToUTCDate } from '$lib/utils/date';
 	import Consent from '$lib/components/surveys/consent.svelte';
 
-	let current_step = 0;
+	let { data, form }: { data: PageData; form: FormData } = $props();
+	let user = $state(data.user);
+	let message = $state('');
+
+	let current_step = $state(
+		(() => {
+			if (user == null) {
+				if (form?.message) return 2;
+				return 1;
+			} else if (!user.home_language || !user.target_language || !user.birthdate || !user.gender) {
+				return 3;
+			} else {
+				return 5;
+			}
+		})()
+	);
 
-	$: message = '';
-
-	onMount(async () => {
-		const u = get(user);
-
-		if (u == null) {
-			current_step = 1;
-			return;
-		}
-		User.parseAll(await getUsersAPI());
-
-		if (!u.home_language || !u.target_language || !u.birthdate || !u.gender) {
-			current_step = 3;
-			return;
-		}
-
-		const contacts = User.parseAll(await getUserContactsAPI(u.id));
-
-		if (contacts.length == 0) {
-			current_step = 4;
-			return;
-		}
-
-		current_step = 5;
-	});
-
-	let nickname = '';
-	let email = '';
-	let password = '';
-	let confirmPassword = '';
-
-	let ui_language: string = $locale;
-	let home_language: string;
-	let target_language: string;
-	let birthdate: string;
-	let gender: string;
 	let study_id: number | null = (() => {
 		if (!browser) return null;
 		let study_id_str = new URLSearchParams(window.location.search).get('study');
 		if (!study_id_str) return null;
 		return parseInt(study_id_str) || null;
 	})();
-
-	let timeslots = 0n;
-	$: filteredUsers = $users.filter((user) => {
-		if (user.availability === 0n) return false;
-		if (timeslots === 0n) return true;
-
-		return user.availability & timeslots;
-	});
-
-	async function onRegister() {
-		if (nickname == '' || email == '' || password == '' || confirmPassword == '') {
-			message = $t('register.error.emptyFields');
-			return;
-		}
-		if (password.length < 8) {
-			message = $t('register.error.passwordRules');
-			return;
-		}
-		if (password != confirmPassword) {
-			message = $t('register.error.differentPasswords');
-			return;
-		}
-		const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/;
-		if (!emailRegex.test(email)) {
-			message = $t('register.error.emailRules');
-			return;
-		}
-		message = '';
-
-		const registerRes = await registerAPI(email, password, nickname);
-
-		if (registerRes !== 'OK') {
-			message = registerRes;
-			return;
-		}
-
-		const loginRes = await loginAPI(email, password);
-
-		if (loginRes !== 'OK') {
-			toastAlert('Failed to login: ' + loginRes);
-			document.location.href = '/login';
-			return;
-		}
-
-		if (study_id === null) document.location.href = '/register';
-		else document.location.href = `/register?study=${study_id}`;
-
-		message = 'OK';
-	}
-
-	async function onData() {
-		const user_id = get(user)?.id;
-
-		if (!user_id) {
-			toastAlert('Failed to get current user ID');
-			return;
-		}
-
-		if (!ui_language || !home_language || !target_language || !birthdate || !gender) {
-			message = $t('register.error.emptyFields');
-			return;
-		}
-
-		const res = await patchUserAPI(user_id, {
-			ui_language,
-			home_language,
-			target_language,
-			birthdate,
-			gender,
-			study_id
-		});
-
-		if (!res) {
-			message = $t('register.error.metadata');
-			return;
-		}
-
-		current_step++;
-	}
-
-	async function onTutor(tutor: User) {
-		const user_id = get(user)?.id;
-
-		if (!user_id) {
-			toastAlert('Failed to get current user ID');
-			return;
-		}
-
-		if (confirm($t('register.confirmTutor').replaceAll('{NAME}', tutor.nickname)) === false) return;
-
-		const res = await createUserContactAPI(user_id, tutor.id);
-
-		if (!res) {
-			message = $t('register.error.tutor');
-			return;
-		}
-		current_step++;
-	}
-
-	async function onTyping() {
-		current_step++;
-	}
 </script>
 
 <div class="header mx-auto my-5">
 	<ul class="steps text-xs">
 		<li class="step" class:step-primary={current_step >= 1}>
-			{$t('register.tab.consent')}
+			{#if current_step >= 1 && current_step <= 2}
+				<button onclick={() => (current_step = 1)}>
+					{$t('register.tab.consent')}
+				</button>
+			{:else}
+				{$t('register.tab.consent')}
+			{/if}
 		</li>
 		<li class="step" class:step-primary={current_step >= 2}>
 			{$t('register.tab.signup')}
@@ -196,7 +66,11 @@
 </div>
 
 <div class="max-w-screen-md mx-auto p-5">
-	{#if message}
+	{#if form?.message}
+		<div class="alert alert-error text-content text-base-100 py-2 mb-4">
+			{form.message}
+		</div>
+	{:else if message}
 		<div class="alert alert-error text-content text-base-100 py-2 mb-4">
 			{message}
 		</div>
@@ -211,74 +85,80 @@
 			rights={$t('register.consent.rights')}
 		/>
 		<div class="form-control">
-			<button class="button mt-4" on:click={() => current_step++}>
+			<button class="button mt-4" onclick={() => current_step++}>
 				{$t('register.consent.ok')}
 			</button>
 		</div>
 	{:else if current_step == 2}
 		<div class="space-y-2">
-			<label for="email" class="form-control">
-				<div class="label">
-					<span class="label-text">{$t('register.email')}</span>
-					<span class="label-text-alt">{$t('register.email.note')}</span>
-				</div>
-				<div class="input flex items-center">
-					<Icon src={Envelope} class="w-4 mr-2 opacity-70" solid />
-					<input
-						type="text"
-						class="grow"
-						bind:value={email}
-						placeholder={$t('register.email.ph')}
-					/>
-				</div>
-			</label>
-			<label for="nickname" class="form-control">
-				<div class="label">
-					<span class="label-text">{$t('register.nickname')}</span>
-					<span class="label-text-alt">{$t('register.nickname.note')}</span>
-				</div>
-				<div class="input flex items-center">
-					<Icon src={UserCircle} class="w-4 mr-2 opacity-70" solid />
-					<input
-						type="text"
-						class="grow"
-						bind:value={nickname}
-						placeholder={$t('register.nickname.ph')}
-					/>
-				</div>
-			</label>
-			<label for="password" class="form-control">
-				<div class="label">
-					<span class="label-text">{$t('register.password')}</span>
-					<span class="label-text-alt">{$t('register.password.note')}</span>
-				</div>
-				<div class="input flex items-center">
-					<Icon src={Key} class="w-4 mr-2 opacity-70" solid />
-					<input
-						type="password"
-						class="grow"
-						bind:value={password}
-						placeholder={$t('register.password')}
-					/>
-				</div>
-			</label>
-			<label for="confirmPassword" class="form-control">
-				<div class="input flex items-center">
-					<Icon src={Key} class="w-4 mr-2 opacity-70" solid />
-					<input
-						type="password"
-						class="grow"
-						bind:value={confirmPassword}
-						placeholder={$t('register.confirmPassword')}
-					/>
+			<form method="POST" action="?/register">
+				<label for="email" class="form-control">
+					<div class="label">
+						<span class="label-text">{$t('register.email')}</span>
+						<span class="label-text-alt">{$t('register.email.note')}</span>
+					</div>
+					<div class="input flex items-center">
+						<Icon src={Envelope} class="w-4 mr-2 opacity-70" solid />
+						<input
+							type="email"
+							name="email"
+							class="grow"
+							placeholder={$t('register.email.ph')}
+							required
+						/>
+					</div>
+				</label>
+				<label for="nickname" class="form-control">
+					<div class="label">
+						<span class="label-text">{$t('register.nickname')}</span>
+						<span class="label-text-alt">{$t('register.nickname.note')}</span>
+					</div>
+					<div class="input flex items-center">
+						<Icon src={UserCircle} class="w-4 mr-2 opacity-70" solid />
+						<input
+							type="text"
+							name="nickname"
+							class="grow"
+							placeholder={$t('register.nickname.ph')}
+							required
+						/>
+					</div>
+				</label>
+				<label for="password" class="form-control">
+					<div class="label">
+						<span class="label-text">{$t('register.password')}</span>
+						<span class="label-text-alt">{$t('register.password.note')}</span>
+					</div>
+					<div class="input flex items-center">
+						<Icon src={Key} class="w-4 mr-2 opacity-70" solid />
+						<input
+							type="password"
+							name="password"
+							class="grow"
+							placeholder={$t('register.password')}
+							required
+						/>
+					</div>
+				</label>
+				<label for="confirmPassword" class="form-control">
+					<div class="input flex items-center">
+						<Icon src={Key} class="w-4 mr-2 opacity-70" solid />
+						<input
+							type="password"
+							name="confirmPassword"
+							class="grow"
+							placeholder={$t('register.confirmPassword')}
+							required
+						/>
+					</div>
+				</label>
+				<div class="form-control">
+					<button class="button mt-2">{$t('register.signup')}</button>
 				</div>
-			</label>
-			<div class="form-control">
-				<button class="button mt-2" on:click={onRegister}>{$t('register.signup')}</button>
-			</div>
+			</form>
 		</div>
 	{:else if current_step == 3}
-		<div class="space-y-2">
+		<form class="space-y-2" method="POST" action="?/data">
 			<div class="p-5 text-sm text-prose">
 				{@html $t('register.welcome')}
 			</div>
@@ -287,13 +167,7 @@
 					<span class="label-text">{$t('register.homeLanguage')}</span>
 					<span class="label-text-alt">{$t('register.homeLanguage.note')}</span>
 				</label>
-				<select
-					class="select select-bordered"
-					id="homeLanguage"
-					name="homeLanguage"
-					required
-					bind:value={home_language}
-				>
+				<select class="select select-bordered" id="homeLanguage" name="homeLanguage" required>
 					<option disabled selected value="">{$t('register.homeLanguage')}</option>
 					{#each Object.entries(config.PRIMARY_LANGUAGE) as [code, name]}
 						<option value={code}>{name}</option>
@@ -305,13 +179,7 @@
 					<span class="label-text">{$t('register.targetLanguage')}</span>
 					<span class="label-text-alt">{$t('register.targetLanguage.note')}</span>
 				</label>
-				<select
-					class="select select-bordered"
-					id="targetLanguage"
-					name="targetLanguage"
-					required
-					bind:value={target_language}
-				>
+				<select class="select select-bordered" id="targetLanguage" name="targetLanguage" required>
 					{#each Object.entries(config.LEARNING_LANGUAGES) as [code, name]}
 						<option value={code}>{name}</option>
 					{/each}
@@ -321,15 +189,9 @@
 				<label for="birthyear" class="label">
 					<span class="label-text">{$t('register.birthyear')}</span>
 				</label>
-				<select
-					class="select select-bordered"
-					id="birthyear"
-					name="birthyear"
-					required
-					on:change={(e) => (birthdate = formatToUTCDate(new Date(e.target.value, 1, 1)))}
-				>
+				<select class="select select-bordered" id="birthyear" name="birthyear" required>
 					<option disabled selected value="">{$t('register.birthyear')}</option>
-					{#each Array.from({ length: 82 }, (_, i) => i + 1931).reverse() as year}
+					{#each Array.from({ length: 90 }, (_, i) => i + 1931).reverse() as year}
 						<option value={year}>{year}</option>
 					{/each}
 				</select>
@@ -340,87 +202,59 @@
 					<span class="label-text-alt">{$t('register.gender.note')}</span>
 				</label>
 				<div class="label justify-normal gap-2 py-0">
-					<input
-						type="radio"
-						class="radio"
-						id="male"
-						name="gender"
-						value="male"
-						on:change={() => (gender = 'male')}
-					/>
+					<input type="radio" class="radio" id="male" name="gender" value="male" required />
 					<label for="male" class="label-text cursor-pointer">
 						{$t('register.genders.male')}
 					</label>
 				</div>
 				<div class="label justify-normal gap-2 py-0">
-					<input
-						type="radio"
-						class="radio"
-						id="female"
-						name="gender"
-						value="female"
-						on:change={() => (gender = 'female')}
-					/>
+					<input type="radio" class="radio" id="female" name="gender" value="female" required />
 					<label for="female" class="label-text cursor-pointer">
 						{$t('register.genders.female')}
 					</label>
 				</div>
 				<div class="label justify-normal gap-2 py-0">
-					<input
-						type="radio"
-						class="radio"
-						id="other"
-						name="gender"
-						value="other"
-						on:change={() => (gender = 'other')}
-					/>
+					<input type="radio" class="radio" id="other" name="gender" value="other" required />
 					<label for="other" class="label-text cursor-pointer">
 						{$t('register.genders.other')}
 					</label>
 				</div>
 				<div class="label justify-normal gap-2 py-0">
-					<input
-						type="radio"
-						class="radio"
-						id="na"
-						name="gender"
-						value="na"
-						on:change={() => (gender = 'na')}
-					/>
+					<input type="radio" class="radio" id="na" name="gender" value="na" required />
 					<label for="na" class="label-text cursor-pointer">
 						{$t('register.genders.na')}
 					</label>
 				</div>
 			</div>
 			<div class="form-control">
-				<button class="button mt-4" on:click={onData}>{$t('button.submit')}</button>
+				<button class="button mt-4">{$t('button.submit')}</button>
 			</div>
-		</div>
+		</form>
 	{:else if current_step == 4}
-		<!--{#if get(user)}-->
-		<h2 class="my-4 text-xl">{$t('timeslots.availabilities')}</h2>
-		<Timeslots bind:timeslots />
-		<AvailableTutors users={filteredUsers} {timeslots} onSelect={onTutor} />
+		<h2 class="my-4 text-xl">This page is disabled. Please continue.</h2>
+		<button onclick={() => current_step++}>{$t('button.continue')}</button>
 	{:else if current_step == 5}
 		<div class="text-center">
 			<p class="text-center">
 				{@html $t('register.continue')}
 			</p>
-			<button class="button mt-4 w-full" on:click={() => (current_step = 6)}>
+			<button class="button mt-4 w-full" onclick={() => (current_step = 6)}>
 				{$t('register.continueButton')}
 			</button>
-			<button class="button mt-4 w-full" on:click={() => (document.location.href = '/')}>
+			<button class="button mt-4 w-full" onclick={() => (document.location.href = '/')}>
 				{$t('register.startFastButton')}
 			</button>
 		</div>
 	{:else if current_step == 6}
-		<Typingtest onFinish={onTyping} />
+		{#if user}
+			<Typingtest onFinish={() => current_step++} {user} />
+		{/if}
 	{:else if current_step == 7}
 		<div class="text-center">
 			<p class="text-center">
 				{@html $t('register.start')}
 			</p>
-			<button class="button mt-4 m-auto" on:click={() => (document.location.href = '/')}>
+			<button class="button mt-4 m-auto" onclick={() => (document.location.href = '/')}>
 				{$t('register.startButton')}
 			</button>
 		</div>
diff --git a/frontend/src/routes/session/+page.svelte b/frontend/src/routes/session/+page.svelte
deleted file mode 100644
index 9791e4807987da8efc14689b66312dcd7599edbf..0000000000000000000000000000000000000000
--- a/frontend/src/routes/session/+page.svelte
+++ /dev/null
@@ -1,69 +0,0 @@
-<script lang="ts">
-	import { page } from '$app/stores';
-	import { getSessionAPI } from '$lib/api/sessions';
-	import Chatbox from '$lib/components/sessions/chatbox.svelte';
-	import Session from '$lib/types/session';
-	import { getBaseURL } from '$lib/utils/login';
-	import { onMount } from 'svelte';
-	import { user } from '$lib/types/user';
-	import Gravatar from 'svelte-gravatar';
-	import WeeklySurvey from '$lib/components/users/weeklySurvey.svelte';
-	import config from '$lib/config.js';
-	export let data;
-
-	let session: Session | null = null;
-	$: onlineUsers = session ? session.onlineUsers : null;
-
-	onMount(async () => {
-		const param = $page.url.searchParams.get('id');
-		if (!param) {
-			window.location.href = getBaseURL();
-			return;
-		}
-
-		const id = parseInt(param);
-
-		if (!id) return;
-		else {
-			session = Session.parse(await getSessionAPI(id));
-		}
-	});
-</script>
-
-{#if session}
-	<div class="h-full grid lg:grid-cols-7">
-		<div class="justify-evenly h-full p-2">
-			<ul class="ml-2">
-				{#each session.users as sessionUser (sessionUser.id)}
-					<li
-						class="list-disc list-inside {sessionUser.id == $user?.id ||
-						$onlineUsers?.has(sessionUser.id)
-							? 'marker:text-green-500'
-							: 'marker:text-red-500'} marker:text-3xl"
-					>
-						<div class="inline-flex space-x-2">
-							<div class="rounded-full mx-2 chat-image size-6" title={sessionUser.nickname}>
-								<Gravatar
-									email={sessionUser.email}
-									size={64}
-									title={sessionUser.nickname}
-									class="rounded-full"
-								/>
-							</div>
-
-							<span class:font-bold={sessionUser === $user}>{sessionUser.nickname}</span>
-						</div>
-					</li>
-				{/each}
-			</ul>
-		</div>
-		<div class="flex flex-row flex-grow col-span-5">
-			<Chatbox {session} token={data.token} />
-		</div>
-		<div class=""></div>
-	</div>
-{/if}
-
-{#if $user}
-	<WeeklySurvey />
-{/if}
diff --git a/frontend/src/routes/sessions/[id]/+page.server.ts b/frontend/src/routes/sessions/[id]/+page.server.ts
new file mode 100644
index 0000000000000000000000000000000000000000..03b3624e6109b7d4bf7996a2b940889c1858c567
--- /dev/null
+++ b/frontend/src/routes/sessions/[id]/+page.server.ts
@@ -0,0 +1,9 @@
+import { type ServerLoad, redirect } from '@sveltejs/kit';
+
+export const load: ServerLoad = async ({ params, locals }) => {
+	if (locals.user == null || locals.user == undefined) {
+		redirect(303, '/login?redirect=/sessions/' + params.id);
+	}
+
+	return { jwt: locals.jwt, user: locals.user };
+};
diff --git a/frontend/src/routes/sessions/[id]/+page.svelte b/frontend/src/routes/sessions/[id]/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..6f53193a995279fac3963f4a03bafce9e1a34d2e
--- /dev/null
+++ b/frontend/src/routes/sessions/[id]/+page.svelte
@@ -0,0 +1,45 @@
+<script lang="ts">
+	import { t } from '$lib/services/i18n';
+	import type { PageData } from './$types.js';
+	import WeeklySurvey from './WeeklySurvey.svelte';
+	import Chatbox from './Chatbox.svelte';
+
+	let { data }: { data: PageData } = $props();
+	let user = data.user!;
+
+	let { session, jwt } = data;
+	let { onlineUsers } = session;
+</script>
+
+<div class="h-full flex lg:flex-row flex-col pt-2 lg:pt-0">
+	<div
+		class="border rounded-xl p-4 shadow-[0_0_6px_0_rgba(0,14,156,.2)] m-2 my-0 lg:mt-2 h-fit lg:w-96 text-lg space-y-2"
+	>
+		<h2 class="text-center font-bold">{$t('utils.words.participants')}</h2>
+		<div class="pb-2 space-y-2">
+			{#each session.users as sessionUser (sessionUser.id)}
+				<div class="flex space-x-2">
+					<div class="rounded-full mx-2 chat-image size-6" title={sessionUser.nickname}>
+						<img
+							src={`https://gravatar.com/avatar/${sessionUser.emailHash}?d=identicon`}
+							alt={sessionUser.nickname}
+							class="rounded-full border-2 text-sm {sessionUser.id == user?.id ||
+							$onlineUsers.has(sessionUser.id)
+								? 'border-green-500'
+								: 'border-red-500'}"
+						/>
+					</div>
+
+					<span class:font-bold={sessionUser === user}>{sessionUser.nickname}</span>
+				</div>
+			{/each}
+		</div>
+		<h2 class="text-center font-bold border-t pt-2">{$t('utils.words.topics')}</h2>
+		<p class="text-center text-sm text-neutral-500 italic">{$t('session.noTopic')}</p>
+	</div>
+	<div class="flex flex-row flex-grow col-span-5">
+		<Chatbox {session} {jwt} {user} />
+	</div>
+</div>
+
+<WeeklySurvey {user} />
diff --git a/frontend/src/routes/sessions/[id]/+page.ts b/frontend/src/routes/sessions/[id]/+page.ts
new file mode 100644
index 0000000000000000000000000000000000000000..251ba545835504f12d817b4ff4eee7d6bfb406df
--- /dev/null
+++ b/frontend/src/routes/sessions/[id]/+page.ts
@@ -0,0 +1,26 @@
+import { getSessionAPI } from '$lib/api/sessions';
+import Session from '$lib/types/session';
+import { error, type Load } from '@sveltejs/kit';
+
+export const load: Load = async ({ params, fetch, data }) => {
+	const jwt = data?.jwt;
+
+	const id = params.id;
+	if (!id) {
+		error(404, 'Not found');
+	}
+
+	const nid = parseInt(id);
+	if (isNaN(nid)) {
+		error(404, 'Not found');
+	}
+
+	const session = Session.parse(await getSessionAPI(fetch, nid));
+	if (!session) {
+		error(404, 'Not found');
+	}
+
+	await session.loadMessages(fetch);
+
+	return { session, jwt };
+};
diff --git a/frontend/src/lib/components/sessions/chatbox.svelte b/frontend/src/routes/sessions/[id]/Chatbox.svelte
similarity index 83%
rename from frontend/src/lib/components/sessions/chatbox.svelte
rename to frontend/src/routes/sessions/[id]/Chatbox.svelte
index d4f6ad2a6f30ef9610137c321faa0fddb3030ec5..56c7a9a3ed797cf080dbf0bd2b845d514b7384fe 100644
--- a/frontend/src/lib/components/sessions/chatbox.svelte
+++ b/frontend/src/routes/sessions/[id]/Chatbox.svelte
@@ -1,30 +1,31 @@
 <script lang="ts">
 	import type Session from '$lib/types/session';
 	import { onDestroy, onMount } from 'svelte';
-	import MessageC from './message.svelte';
+	import MessageC from './Message.svelte';
 	import { get } from 'svelte/store';
-	import Writebox from './writebox.svelte';
+	import Writebox from './Writebox.svelte';
 	import { t } from '$lib/services/i18n';
 	import { toastSuccess } from '$lib/utils/toasts';
 	import { Icon, PencilSquare } from 'svelte-hero-icons';
 	import Message from '$lib/types/message';
 	import Feedback from '$lib/types/feedback';
+	import type User from '$lib/types/user';
 
-	export let token: string;
+	const { session, jwt, user }: { session: Session; jwt: string; user: User } = $props();
 
-	export let session: Session;
-	let messages = get(session.messages);
+	const { messages } = session;
 
-	session.messages.subscribe((newMessages) => {
+	let replyTo: Message | undefined = $state();
+
+	messages.subscribe((newMessages) => {
 		let news = newMessages
 			.filter(
 				(m) =>
-					!messages.find(
+					!$messages.find(
 						(m2) => m instanceof Message && m2 instanceof Message && m2.message_id === m.message_id
 					)
 			)
 			.at(-1);
-		messages = newMessages;
 		if (!news || !(news instanceof Message)) return;
 
 		if (document.hidden) {
@@ -36,7 +37,7 @@
 		}
 	});
 
-	let wsConnected = true;
+	let wsConnected = $state(true);
 	let timeout: number;
 	session.wsConnected.subscribe((newConnected) => {
 		clearTimeout(timeout);
@@ -50,7 +51,7 @@
 		}
 	});
 
-	$: isTyping = false;
+	let isTyping = $state(false);
 	let timeoutTyping: number;
 
 	session.lastTyping.subscribe((lastTyping) => {
@@ -102,9 +103,9 @@
 		}, timeBeforeSatify);
 	}
 
-	let satisfyQ1: number = 2;
-	let satisfyQ2: number = 2;
-	let satisfyQ3: string = '';
+	let satisfyQ1: number = $state(2);
+	let satisfyQ2: number = $state(2);
+	let satisfyQ3: string = $state('');
 
 	async function submitSatisfy() {
 		const res = await session.sendSatisfy(satisfyQ1, satisfyQ2, satisfyQ3);
@@ -115,8 +116,7 @@
 	}
 
 	onMount(async () => {
-		await session.loadMessages();
-		session.wsConnect(token);
+		session.wsConnect(jwt);
 		presenceIndicator();
 		satisfyModal();
 		Notification.requestPermission(); // Should do something with denial ?
@@ -127,14 +127,14 @@
 	});
 </script>
 
-<div class="flex flex-col w-full h-full border-x-2 scroll-smooth">
+<div class="flex flex-col w-full max-w-5xl mx-auto h-full scroll-smooth">
 	<div class="flex-grow h-48 overflow-auto flex-col-reverse px-4 flex mb-2">
 		<div class:hidden={!isTyping}>
 			<span class="loading loading-dots loading-md"></span>
 		</div>
-		{#each messages.sort((a, b) => b.created_at.getTime() - a.created_at.getTime()) as message (message.uuid)}
+		{#each $messages.sort((a, b) => b.created_at.getTime() - a.created_at.getTime()) as message (message.uuid)}
 			{#if message instanceof Message}
-				<MessageC {message} />
+				<MessageC {user} {message} bind:replyTo />
 			{:else if message instanceof Feedback}
 				<a class="text-center italic text-gray-500 my-2" href="#{message.message.uuid}">
 					{$t('session.feedbackInline')} "{message.message.content.length > 20
@@ -151,14 +151,20 @@
 			Real-time sync lost. You may need to refresh the page to see new messages.
 		</div>
 	{/if}
-	<div class="flex flex-row h-30">
-		<Writebox {session} />
+	<div class="flex flex-row">
+		<Writebox {user} {session} bind:replyTo />
 	</div>
 </div>
 
 <dialog bind:this={satisfyModalElement} class="modal">
 	<div class="modal-box">
-		<form method="post" on:submit|preventDefault={submitSatisfy}>
+		<form
+			method="post"
+			onsubmit={(e) => {
+				e.preventDefault();
+				submitSatisfy();
+			}}
+		>
 			<h3 class="text-lg font-bold">{$t('session.modal.satisfy.title')}</h3>
 			<p class="mt-4 mb-2">{$t('session.modal.satisfy.q1')}</p>
 			<input
@@ -204,7 +210,7 @@
 
 <div class="absolute bottom-4 right-4">
 	<button
-		on:click={() => {
+		onclick={() => {
 			satisfyModalElement.showModal();
 		}}
 		class="btn btn-primary btn-circle"
diff --git a/frontend/src/lib/components/sessions/message.svelte b/frontend/src/routes/sessions/[id]/Message.svelte
similarity index 77%
rename from frontend/src/lib/components/sessions/message.svelte
rename to frontend/src/routes/sessions/[id]/Message.svelte
index 8f9f25026e5d788fc2610783b039a96e36f0085e..a5532fb1325c34f3f9edc517a9c8f350505dee42 100644
--- a/frontend/src/lib/components/sessions/message.svelte
+++ b/frontend/src/routes/sessions/[id]/Message.svelte
@@ -1,55 +1,37 @@
 <script lang="ts">
-	import type Message from '$lib/types/message';
 	import { displayTime } from '$lib/utils/date';
-	import { Pencil, Check, Icon, ArrowUturnLeft } from 'svelte-hero-icons';
-	import { user } from '$lib/types/user';
-	import Gravatar from 'svelte-gravatar';
+	import { ArrowUturnLeft, Check, Icon, Pencil } from 'svelte-hero-icons';
 	import { t } from '$lib/services/i18n';
 	import { onMount } from 'svelte';
 	import SpellCheck from '$lib/components/icons/spellCheck.svelte';
-	import ChatBubble from '../icons/chatBubble.svelte';
+	import ChatBubble from '$lib/components/icons/chatBubble.svelte';
 	import type Feedback from '$lib/types/feedback';
 	import linkifyHtml from 'linkify-html';
 	import { sanitize } from '$lib/utils/sanitize';
-	import CloseIcon from '../icons/closeIcon.svelte';
-	import { initiateReply } from '$lib/utils/replyUtils';
+	import CloseIcon from '$lib/components/icons/closeIcon.svelte';
+	import Message from '$lib/types/message';
+	import type User from '$lib/types/user';
+	import { get } from 'svelte/store';
 
-	export let message: Message;
+	let {
+		user,
+		message,
+		replyTo = $bindable()
+	}: { user: User; message: Message; replyTo: Message | undefined } = $props();
 
-	let replyTo: string | undefined;
+	let displayedTime = $state(displayTime(message.created_at));
 
-	$: replyTo = message['_replyTo'];
+	setInterval(() => {
+		displayedTime = displayTime(message.created_at);
+	}, 1000);
 
-	let replyToMessage: Message | null = null;
+	let isEdit = $state(false);
 
-	$: if (replyTo) {
-		findMessageById(replyTo).then((msg) => {
-			replyToMessage = msg;
-		});
-	}
-
-	async function findMessageById(id: string): Promise<Message | null> {
-		try {
-			const resolvedMessage = await message.getMessageById(Number(id));
-			return resolvedMessage;
-		} catch (error) {
-			console.error(`Error resolving message ID ${id}:`, error);
-			return null;
-		}
-	}
+	let replyToMessage: Message | undefined = $state(message.replyToMessage);
 
-	let timer: number;
-	$: displayedTime = displayTime(message.created_at);
-	$: {
-		clearInterval(timer);
-		timer = setInterval(() => {
-			displayedTime = displayTime(message.created_at);
-		}, 1000);
-	}
-	let isEdit = false;
-	let contentDiv: HTMLDivElement;
+	let contentDiv: HTMLDivElement | null = $state(null);
 	let historyModal: HTMLDialogElement;
-	$: messageVersions = message.versions;
+	let messageVersions = $state(message.versions);
 
 	function startEdit() {
 		isEdit = true;
@@ -60,6 +42,8 @@
 	}
 
 	async function endEdit(validate = true) {
+		if (!contentDiv) return;
+
 		if (!validate) {
 			contentDiv.innerText = message.content;
 			isEdit = false;
@@ -79,10 +63,8 @@
 	}
 
 	function truncateMessage(content: string, maxLength: number = 20): string {
-		if (content.length > maxLength) {
-			return content.slice(0, maxLength) + '...';
-		}
-		return content;
+		if (content.length <= maxLength) return content;
+		return content.slice(0, maxLength) + '...';
 	}
 	let hightlight: HTMLDivElement;
 
@@ -91,6 +73,8 @@
 	});
 
 	function getSelectionCharacterOffsetWithin() {
+		if (!contentDiv) return { start: 0, end: 0 };
+
 		var start = 0;
 		var end = 0;
 		var doc = contentDiv.ownerDocument;
@@ -184,10 +168,15 @@
 		return parts;
 	}
 
-	$: fbs = message.feedbacks;
-	$: parts = getParts(message.content, $fbs);
+	let fbs = $state([] as Feedback[]);
+	let parts = $state([] as { text: string; feedback: Feedback | null }[]);
+	fbs = get(message.feedbacks);
+	message.feedbacks.subscribe((value) => {
+		fbs = value;
+		parts = getParts(message.content, fbs);
+	});
 
-	const isSender = message.user.id == $user?.id;
+	const isSender = message.user.id == user.id;
 
 	async function deleteFeedback(feedback: Feedback | null) {
 		if (!feedback) return;
@@ -204,11 +193,10 @@
 	class:chat-end={isSender}
 >
 	<div class="rounded-full mx-2 chat-image size-12" title={message.user.nickname}>
-		<Gravatar
-			email={message.user.email}
-			size={64}
-			title={message.user.nickname}
-			class="rounded-full"
+		<img
+			src={`https://gravatar.com/avatar/${user.emailHash}?d=identicon`}
+			alt={user.nickname}
+			class="rounded-full border border-neutral-400 text-sm"
 		/>
 	</div>
 
@@ -228,35 +216,28 @@
 			</a>
 		{/if}
 
-		<button
-			class="absolute -right-6 top-1/2 transform -translate-y-1/2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 cursor-pointer"
-			on:click={() => initiateReply(message)}
-		>
-			<Icon src={ArrowUturnLeft} class="w-4 h-4 text-gray-800" />
-		</button>
-
 		{#if isEdit}
 			<div
 				contenteditable="true"
 				bind:this={contentDiv}
-				class="bg-blue-50 whitespace-pre-wrap min-h-8 p-2"
+				class="bg-blue-50 whitespace-pre-wrap min-h-8 p-2 text-lg"
 			>
 				{message.content}
 			</div>
 			<button
 				class="float-end border rounded-full px-4 py-2 mt-2 bg-white text-blue-700"
-				on:click={() => endEdit()}
+				onclick={() => endEdit()}
 			>
 				{$t('button.save')}
 			</button>
 			<button
 				class="float-end border rounded-full px-4 py-2 mt-2 mr-2"
-				on:click={() => endEdit(false)}
+				onclick={() => endEdit(false)}
 			>
 				{$t('button.cancel')}
 			</button>
 		{:else}
-			<div class="whitespace-pre-wrap" bind:this={contentDiv}>
+			<div class="whitespace-pre-wrap text-lg" bind:this={contentDiv}>
 				{#each parts as part}
 					{#if isEdit || !part.feedback}
 						{@html linkifyHtml(sanitize(part.text), { className: 'underline', target: '_blank' })}
@@ -275,7 +256,7 @@
 										aria-label="close"
 										class:ml-1={part.feedback.content}
 										class="hover:border-inherit border border-transparent rounded"
-										on:click={() => deleteFeedback(part.feedback)}
+										onclick={() => deleteFeedback(part.feedback)}
 									>
 										<CloseIcon />
 									</button>
@@ -293,17 +274,23 @@
 		{#if isSender}
 			<button
 				class="absolute bottom-0 left-[-1.5rem] invisible group-hover:visible h-full p-0"
-				on:click={() => (isEdit ? endEdit() : startEdit())}
+				onclick={() => (isEdit ? endEdit() : startEdit())}
 			>
 				<Icon src={Pencil} class="w-5 h-full text-gray-500 hover:text-gray-800" />
 			</button>
 		{/if}
+		<button
+			class="absolute bottom-0 left-[-3.5rem] invisible group-hover:visible h-full p-0"
+			onclick={() => (replyTo = message)}
+		>
+			<Icon src={ArrowUturnLeft} class="w-5 h-full text-gray-500 hover:text-gray-800" />
+		</button>
 	</div>
 	<div class="chat-footer opacity-50">
 		<Icon src={Check} class="w-4 inline" />
 		{displayedTime}
 		{#if message.edited}
-			<button class="italic cursor-help" on:click={() => historyModal.showModal()}>
+			<button class="italic cursor-help" onclick={() => historyModal.showModal()}>
 				{$t('chatbox.edited')}
 			</button>
 		{/if}
@@ -315,13 +302,13 @@
 	bind:this={hightlight}
 >
 	<button
-		on:click={() => onSelect(false)}
+		onclick={() => onSelect(false)}
 		class="bg-opacity-0 bg-blue-200 hover:bg-opacity-100 p-2 pl-4 rounded-l-xl"
 	>
 		<SpellCheck />
 	</button><!---
 	--><button
-		on:click={() => onSelect(true)}
+		onclick={() => onSelect(true)}
 		class="bg-opacity-0 bg-blue-200 hover:bg-opacity-100 p-2 pr-4 rounded-r-xl"
 	>
 		<ChatBubble />
diff --git a/frontend/src/lib/components/users/weeklySurvey.svelte b/frontend/src/routes/sessions/[id]/WeeklySurvey.svelte
similarity index 74%
rename from frontend/src/lib/components/users/weeklySurvey.svelte
rename to frontend/src/routes/sessions/[id]/WeeklySurvey.svelte
index 8143e783b884b73140fb9f8f69cddec53a50a731..2b269c2133a4863be20a368e30a37de812e92249 100644
--- a/frontend/src/lib/components/users/weeklySurvey.svelte
+++ b/frontend/src/routes/sessions/[id]/WeeklySurvey.svelte
@@ -2,31 +2,31 @@
 	import { createWeeklySurveyAPI } from '$lib/api/users';
 	import config from '$lib/config';
 	import { t } from '$lib/services/i18n';
-	import { user } from '$lib/types/user';
+	import type User from '$lib/types/user';
 	import { formatToUTCDate } from '$lib/utils/date';
 	import { toastAlert, toastSuccess } from '$lib/utils/toasts';
 
-	let open =
-		!$user?.is_tutor &&
-		!$user?.is_admin &&
-		(!$user?.last_survey ||
-			$user.last_survey.getTime() + config.WEEKLY_SURVEY_INTERVAL < Date.now());
+	let { user }: { user: User } = $props();
 
-	async function send() {
-		if (!$user) return;
+	let open = $state(
+		!user.is_tutor &&
+			!user.is_admin &&
+			(!user.last_survey || user.last_survey.getTime() + config.WEEKLY_SURVEY_INTERVAL < Date.now())
+	);
 
+	async function send() {
 		const data = Array.from({ length: 4 }, (_, i) => {
 			const value = (document.getElementById('questions-' + i) as HTMLSelectElement).value;
 			return value === '-1' ? null : parseFloat(value);
 		});
 
-		const res = await createWeeklySurveyAPI($user.id, data[0]!, data[1]!, data[2]!, data[3]!);
+		const res = await createWeeklySurveyAPI(fetch, user.id, data[0]!, data[1]!, data[2]!, data[3]!);
 
 		if (!res) {
 			toastAlert($t('session.modal.weekly.errors.submit'));
 		}
 
-		await $user.patch({ last_survey: formatToUTCDate(new Date()) });
+		await user.patch({ last_survey: formatToUTCDate(new Date()) });
 
 		open = false;
 
@@ -34,14 +34,7 @@
 	}
 </script>
 
-<dialog
-	class="modal bg-black bg-opacity-50"
-	{open}
-	on:close={() => (open = false)}
-	on:keydown={(e) => e.key === 'Escape' && (open = false)}
-	tabindex="0"
-	aria-modal="true"
->
+<dialog class="modal bg-black bg-opacity-50" {open} aria-modal="true">
 	<div class="modal-box max-w-[800px]">
 		<h2 class="text-xl font-bold mb-4">{$t('session.modal.weekly.title')}</h2>
 		<p>{@html $t('session.modal.weekly.description')}</p>
@@ -50,7 +43,7 @@
 				<div class="label">
 					<span class="label-text"
 						>{@html $t('session.modal.weekly.questions.' + i, {
-							lang: $t('utils.language.' + $user?.target_language).toLowerCase()
+							lang: $t('utils.language.' + user.target_language).toLowerCase()
 						})}</span
 					>
 				</div>
@@ -73,6 +66,6 @@
 				</select>
 			</label>
 		{/each}
-		<button class="btn btn-primary w-full mt-10" on:click={send}>{$t('button.submit')}</button>
+		<button class="btn btn-primary w-full mt-10" onclick={send}>{$t('button.submit')}</button>
 	</div>
 </dialog>
diff --git a/frontend/src/lib/components/sessions/writebox.svelte b/frontend/src/routes/sessions/[id]/Writebox.svelte
similarity index 62%
rename from frontend/src/lib/components/sessions/writebox.svelte
rename to frontend/src/routes/sessions/[id]/Writebox.svelte
index 9c6f2f864d95db4208f64385e343ace10b53a0e5..ae77e62d0e02ad4c277f54f054863f8c6553757e 100644
--- a/frontend/src/lib/components/sessions/writebox.svelte
+++ b/frontend/src/routes/sessions/[id]/Writebox.svelte
@@ -1,39 +1,38 @@
 <script lang="ts">
 	import config from '$lib/config';
 	import { t } from '$lib/services/i18n';
-	import type Session from '$lib/types/session';
 	import { toastAlert } from '$lib/utils/toasts';
 	import { Icon, PaperAirplane } from 'svelte-hero-icons';
-	import { user } from '$lib/types/user';
 	import { onMount } from 'svelte';
-	import { get } from 'svelte/store';
+	import autosize from 'svelte-autosize';
+	import type User from '$lib/types/user';
 
-	import { replyToMessage, clearReplyToMessage } from '$lib/utils/replyUtils';
+	import type Session from '$lib/types/session';
+	import type Message from '$lib/types/message';
 
 	onMount(async () => {
 		await import('emoji-picker-element');
 	});
 
-	export let session: Session;
+	let {
+		user,
+		session,
+		replyTo = $bindable()
+	}: { user: User; session: Session; replyTo: Message | undefined } = $props();
 
-	let currentReplyToMessage = null;
 	let metadata: { message: string; date: number }[] = [];
 	let lastMessage = '';
-	let message = '';
-	let showPicker = false;
-	let showSpecials = false;
+	let message = $state('');
+	let showPicker = $state(false);
+	let showSpecials = $state(false);
 	let textearea: HTMLTextAreaElement;
 
-	$: currentReplyToMessage = $replyToMessage;
-
 	function cancelReply() {
-		clearReplyToMessage();
+		replyTo = undefined;
 	}
 
-	let us = get(user);
 	let disabled =
-		us == null ||
-		session.users.find((u) => us.id === u.id) === undefined ||
+		session.users.find((u: User) => user.id === u.id) === undefined ||
 		new Date().getTime() > session.end_time.getTime() + 3600000 ||
 		new Date().getTime() < session.start_time.getTime() - 3600000;
 
@@ -41,28 +40,20 @@
 		message = message.trim();
 		if (message.length == 0) return;
 
-		if ($user === null) {
-			toastAlert($t('chatbox.sendError'));
-			return;
-		}
-
 		try {
-			const m = await session.sendMessage(
-				structuredClone($user),
-				message,
-				[...metadata],
-				$replyToMessage?.id || null
-			);
+			const m = await session.sendMessage(user, message, [...metadata], replyTo?.id || null);
 
 			if (m === null) {
 				toastAlert($t('chatbox.sendError'));
 				return;
 			}
 
-			// Reset after sending
 			message = '';
 			metadata = [];
-			clearReplyToMessage();
+			setTimeout(() => {
+				autosize.update(textearea);
+			}, 10);
+			cancelReply();
 		} catch (error) {
 			console.error('Failed to send message:', error);
 			toastAlert($t('chatbox.sendError'));
@@ -86,15 +77,15 @@
 	}
 </script>
 
-<div class="flex flex-col w-full py-2 relative">
-	{#if currentReplyToMessage}
+<div class="flex flex-col w-full py-2 relative mb-2">
+	{#if replyTo}
 		<div
 			class="flex items-center justify-between bg-gray-100 p-2 rounded-md mb-1 text-sm text-gray-600"
 		>
 			<p class="text-xs text-gray-400">
-				Replying to: <span class="text-xs text-gray-400">{currentReplyToMessage.content}</span>
+				Replying to: <span class="text-xs text-gray-400">{replyTo.content}</span>
 			</p>
-			<button class="text-xs text-blue-500 underline ml-4 cursor-pointer" on:click={cancelReply}>
+			<button class="text-xs text-blue-500 underline ml-4 cursor-pointer" onclick={cancelReply}>
 				Cancel
 			</button>
 		</div>
@@ -105,7 +96,7 @@
 			{#each config.SPECIAL_CHARS as char (char)}
 				<button
 					class="border-none"
-					on:click={() => {
+					onclick={() => {
 						message += char;
 						textearea.focus();
 					}}
@@ -117,38 +108,30 @@
 			{/each}
 		</ul>
 	{/if}
-
-	<div class="w-full flex relative">
-		<textarea
-			bind:this={textearea}
-			class="flex-grow border border-gray-300 rounded-md p-2 text-base resize-none"
-			placeholder={disabled ? $t('chatbox.disabled') : $t('chatbox.placeholder')}
-			{disabled}
-			bind:value={message}
-			on:keypress={keyPress}
-		/>
-
+	<div class="w-full flex items-center relative">
 		<div
-			class="absolute top-1/2 right-20 transform -translate-y-1/2 text-lg select-none cursor-pointer"
-			on:click={() => (showPicker = !showPicker)}
+			class="text-2xl select-none cursor-pointer mx-4"
+			onclick={() => (showPicker = !showPicker)}
 			data-tooltip-target="tooltip-emoji"
 			data-tooltip-placement="right"
+			data-riple-light="true"
 			aria-hidden={false}
 			role="button"
 			tabindex="0"
 		>
 			😀
 		</div>
-
 		<div class="relative">
 			<div
 				id="tooltip-emoji"
+				data-tooltip="tooltip-emoji"
+				role="tooltip"
 				class:hidden={!showPicker}
-				class="absolute z-10 bottom-16 right-0 lg:left-0 lg:right-auto hidden"
+				class="absolute z-10 tooltip bottom-16 right-0 lg:left-0 lg:right-auto"
 			>
 				<emoji-picker
 					class="light"
-					on:emoji-click={(event) => {
+					onemoji-click={(event) => {
 						message += event.detail.unicode;
 						textearea.focus();
 					}}
@@ -156,18 +139,26 @@
 				</emoji-picker>
 			</div>
 		</div>
-
+		<textarea
+			bind:this={textearea}
+			class="flex-grow p-2 resize-none overflow-hidden py-4 pr-12 border rounded-[32px]"
+			placeholder={disabled ? $t('chatbox.disabled') : $t('chatbox.placeholder')}
+			{disabled}
+			bind:value={message}
+			use:autosize
+			rows={1}
+			onkeypress={keyPress}
+		></textarea>
 		<div
-			class="absolute top-1/2 right-28 kbd transform -translate-y-1/2 text-sm select-none cursor-pointer"
-			on:click={() => (showSpecials = !showSpecials)}
+			class="absolute right-28 kbd text-sm select-none cursor-pointer"
+			onclick={() => (showSpecials = !showSpecials)}
 			aria-hidden={false}
 			role="button"
 			tabindex="0"
 		>
 			É
 		</div>
-
-		<button class="btn btn-primary rounded-none size-16" on:click={sendMessage}>
+		<button class="btn btn-primary rounded-full size-14 mx-4" onclick={sendMessage}>
 			<Icon src={PaperAirplane} />
 		</button>
 	</div>
diff --git a/frontend/src/routes/tests/[id]/+layout.server.ts b/frontend/src/routes/tests/[id]/+layout.server.ts
deleted file mode 100644
index 0c0a1405302c40587c0e1cf82529670ef4c82254..0000000000000000000000000000000000000000
--- a/frontend/src/routes/tests/[id]/+layout.server.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { type ServerLoad, redirect } from '@sveltejs/kit';
-
-const publicly_allowed = ['/login', '/register', '/tests', '/surveys'];
-
-const isPublic = (path: string) => {
-	for (const allowed of publicly_allowed) {
-		if (path.startsWith(allowed)) {
-			return true;
-		}
-	}
-	return false;
-};
-
-export const load: ServerLoad = async ({ locals, url }) => {
-	if (locals.user == null || locals.user == undefined) {
-		if (!isPublic(url.pathname)) {
-			redirect(307, `/login`);
-		}
-	}
-
-	return {
-		user: locals.user,
-		session: locals.session,
-		locale: locals.locale
-	};
-};
diff --git a/frontend/src/routes/tests/[id]/+layout.ts b/frontend/src/routes/tests/[id]/+layout.ts
deleted file mode 100644
index e87df190ee4256ad1c12a6925e66302bf57eeee3..0000000000000000000000000000000000000000
--- a/frontend/src/routes/tests/[id]/+layout.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-export const ssr = true;
-
-import type { Load } from '@sveltejs/kit';
-import { loadTranslations } from '$lib/services/i18n';
-
-export const load: Load = async ({ url, data }) => {
-	const { user, session, locale } = data;
-	const { pathname } = url;
-
-	await loadTranslations(locale, pathname);
-
-	return {
-		user: user,
-		token: session
-	};
-};
diff --git a/frontend/src/routes/tests/[id]/+page.svelte b/frontend/src/routes/tests/[id]/+page.svelte
index 4c0f09943f9a4aeeec00ff3f2622275965d63a75..298c40a0544f83a3fac345bf6f143237c45cc687 100644
--- a/frontend/src/routes/tests/[id]/+page.svelte
+++ b/frontend/src/routes/tests/[id]/+page.svelte
@@ -1,23 +1,20 @@
 <script lang="ts">
 	import { sendSurveyResponseAPI, sendSurveyResponseInfoAPI } from '$lib/api/survey';
 	import { getSurveyScoreAPI } from '$lib/api/survey';
-
-	import Survey from '$lib/types/survey.js';
 	import { t } from '$lib/services/i18n';
 	import { toastWarning } from '$lib/utils/toasts.js';
 	import { get } from 'svelte/store';
-	import User from '$lib/types/user.js';
 	import type SurveyGroup from '$lib/types/surveyGroup';
 	import Gapfill from '$lib/components/surveys/gapfill.svelte';
+	import type { PageData } from './$types';
 	import Consent from '$lib/components/surveys/consent.svelte';
 	import Dropdown from '$lib/components/surveys/dropdown.svelte';
 	import config from '$lib/config';
-	import { formatToUTCDate } from '$lib/utils/date';
-
-	export let data;
+	import type User from '$lib/types/user';
+	import type Survey from '$lib/types/survey';
 
-	const survey: Survey = data.survey!;
-	const user = data.user ? User.parse(JSON.parse(data.user)) : null;
+	let { data }: { data: PageData } = $props();
+	let { user, survey }: { user: User | null; survey: Survey } = data;
 
 	let sid =
 		Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
@@ -27,24 +24,24 @@
 		return group.questions.sort(() => Math.random() - 0.5);
 	}
 
-	$: step = user ? 1 : 0;
-	$: uuid = user?.email || '';
-	$: uid = user?.id || null;
-	$: code = '';
-	$: subStep = 0;
+	let step = $state(user ? 2 : 0);
+	let uuid = $state(user?.email || '');
+	let uid = $state(user?.id || null);
+	let code = $state('');
+	let subStep = $state(0);
 
-	let currentGroupId = 0;
-	let currentGroup = survey.groups[currentGroupId];
-	let questionsRandomized = getSortedQuestions(currentGroup);
-	let currentQuestionId = 0;
-	let currentQuestion = questionsRandomized[currentQuestionId];
-	let type = currentQuestion.question.split(':')[0];
-	let value = currentQuestion.question.split(':').slice(1).join(':');
-	let gaps = type === 'gap' ? gapParts(currentQuestion.question) : null;
+	let currentGroupId = $state(0);
+	let currentGroup = $derived(survey.groups[currentGroupId]);
+	let questionsRandomized = $derived(getSortedQuestions(currentGroup));
+	let currentQuestionId = $state(0);
+	let currentQuestion = $derived(questionsRandomized[currentQuestionId]);
+	let type = $derived(currentQuestion.question.split(':')[0]);
+	let value = $derived(currentQuestion.question.split(':').slice(1).join(':'));
+	let gaps = $derived(type === 'gap' ? gapParts(currentQuestion.question) : null);
 	let soundPlayer: HTMLAudioElement;
-	let displayQuestionOptions: string[] = [...(currentQuestion.options ?? [])];
-	shuffle(displayQuestionOptions);
-	let finalScore: number | null = null;
+	let displayQuestionOptions: string[] = $derived([...(currentQuestion.options ?? [])]);
+	$effect(() => shuffle(displayQuestionOptions));
+	let finalScore: number | null = $state(null);
 	let selectedOption: string;
 	let endSurveyAnswers: { [key: string]: any } = {};
 
@@ -63,27 +60,20 @@
 
 	function setGroupId(id: number) {
 		currentGroupId = id;
-		currentGroup = survey.groups[currentGroupId];
 		if (currentGroup.id < 1100) {
-			questionsRandomized = getSortedQuestions(currentGroup);
 			setQuestionId(0);
 		}
 	}
 
 	function setQuestionId(id: number) {
 		currentQuestionId = id;
-		currentQuestion = questionsRandomized[currentQuestionId];
-		type = currentQuestion.question.split(':')[0];
-		value = currentQuestion.question.split(':').slice(1).join(':');
-		gaps = type === 'gap' ? gapParts(currentQuestion.question) : null;
-		displayQuestionOptions = [...(currentQuestion.options ?? [])];
-		shuffle(displayQuestionOptions);
 		if (soundPlayer) soundPlayer.load();
 	}
 
 	async function selectOption(option: string) {
 		if (
 			!(await sendSurveyResponseAPI(
+				fetch,
 				code,
 				sid,
 				uid,
@@ -114,8 +104,10 @@
 
 		if (
 			!(await sendSurveyResponseAPI(
-				uuid,
+				fetch,
+				code,
 				sid,
+				uid,
 				survey.id,
 				currentGroupId,
 				questionsRandomized[currentQuestionId]['_id'],
@@ -139,7 +131,7 @@
 			setGroupId(currentGroupId + 1);
 			//special group id for end of survey questions
 			if (currentGroup.id >= 1100) {
-				const scoreData = await getSurveyScoreAPI(survey.id, sid);
+				const scoreData = await getSurveyScoreAPI(fetch, survey.id, sid);
 				if (scoreData) {
 					finalScore = scoreData.score;
 				}
@@ -147,7 +139,7 @@
 				return;
 			}
 		} else {
-			const scoreData = await getSurveyScoreAPI(survey.id, sid);
+			const scoreData = await getSurveyScoreAPI(fetch, survey.id, sid);
 			if (scoreData) {
 				finalScore = scoreData.score;
 			}
@@ -168,19 +160,6 @@
 		step += 1;
 	}
 
-	function checkUUID() {
-		if (!uuid) {
-			toastWarning(get(t)('surveys.invalidEmail'));
-			return;
-		}
-		if (!uuid.includes('@')) {
-			toastWarning(get(t)('surveys.invalidEmail'));
-			return;
-		}
-
-		step = 1;
-	}
-
 	function gapParts(question: string): { text: string; gap: string | null }[] {
 		if (!question.startsWith('gap:')) return [];
 
@@ -202,6 +181,7 @@
 		subStep += 1;
 		if (subStep == 4) {
 			await sendSurveyResponseInfoAPI(
+				fetch,
 				survey.id,
 				sid,
 				endSurveyAnswers.birthYear,
@@ -225,13 +205,12 @@
 			type="text"
 			placeholder="Code"
 			class="input block mx-auto w-full max-w-xs border border-gray-300 rounded-md py-2 px-3 text-center"
-			on:keydown={(e) => e.key === 'Enter' && checkCode()}
+			onkeydown={(e) => e.key === 'Enter' && checkCode()}
 			bind:value={code}
 		/>
-		<!-- Button -->
 		<button
 			class="button mt-4 block bg-yellow-500 text-white rounded-md py-2 px-6 hover:bg-yellow-600 transition"
-			on:click={checkCode}
+			onclick={checkCode}
 		>
 			{$t('button.next')}
 		</button>
@@ -247,13 +226,13 @@
 			rights={$t('register.consent.rights')}
 		/>
 		<div class="form-control">
-			<button class="button mt-4" on:click={() => step++}>
+			<button class="button mt-4" onclick={() => step++}>
 				{$t('register.consent.ok')}
 			</button>
 		</div>
 	</div>
 {:else if step == 2}
-	{#if type == 'gap'}
+	{#if type == 'gap' && gaps}
 		<div class="mx-auto mt-16 center flex flex-col">
 			<div>
 				{#each gaps as part}
@@ -264,7 +243,7 @@
 					{/if}
 				{/each}
 			</div>
-			<button class="button mt-8" on:click={sendGap}>{$t('button.next')}</button>
+			<button class="button mt-8" onclick={sendGap}>{$t('button.next')}</button>
 		</div>
 	{:else}
 		<div class="mx-auto mt-16 text-center">
@@ -282,7 +261,7 @@
 
 		<div class="mx-auto mt-16">
 			<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4">
-				{#each displayQuestionOptions as option (option)}
+				{#each displayQuestionOptions as option, i (option)}
 					{@const type = option.split(':')[0]}
 					{#if type == 'dropdown'}
 						{@const value = option.split(':')[1].split(', ')}
@@ -290,8 +269,8 @@
 							class="select select-bordered !ml-0"
 							id="dropdown"
 							name="dropdown"
-							bind:value={option}
-							on:change={() => selectOption(option)}
+							bind:value={displayQuestionOptions[i]}
+							onchange={() => selectOption(option)}
 							required
 						>
 							{#each value as op}
@@ -306,7 +285,7 @@
 									type="radio"
 									name="dropdown"
 									value={op}
-									on:change={() => selectOption(op)}
+									onchange={() => selectOption(op)}
 									required
 									class="radio-button"
 								/>
@@ -317,9 +296,9 @@
 						{@const value = option.split(':').slice(1).join(':')}
 						<div
 							class="h-48 w-48 overflow-hidden rounded-lg border border-black"
-							on:click={() => selectOption(option)}
+							onclick={() => selectOption(option)}
 							role="button"
-							on:keydown={() => selectOption(option)}
+							onkeydown={() => selectOption(option)}
 							tabindex="0"
 						>
 							{#if type === 'text'}
@@ -335,7 +314,14 @@
 									class="object-cover h-full w-full transition-transform duration-200 ease-in-out transform hover:scale-105"
 								/>
 							{:else if type == 'audio'}
-								<audio controls class="w-full" on:click|preventDefault|stopPropagation>
+								<audio
+									controls
+									class="w-full"
+									onclick={(e) => {
+										e.preventDefault();
+										e.stopPropagation();
+									}}
+								>
 									<source src={value} type="audio/mpeg" />
 									Your browser does not support the audio element.
 								</audio>
@@ -377,7 +363,7 @@
 								type="radio"
 								name="gender"
 								{value}
-								on:change={() => selectAnswer('gender', value)}
+								onchange={() => selectAnswer('gender', value)}
 								required
 								class="radio-button"
 							/>
@@ -418,7 +404,6 @@
 			</div>
 		{/if}
 	{:else}
-		<!--In case special id received not defined, can still keep going-->
 		{(step += 1)}
 	{/if}
 {:else if step == 4}
diff --git a/frontend/src/routes/tests/[id]/+page.ts b/frontend/src/routes/tests/[id]/+page.ts
index 7ada027448295fe88193014b3e7387c544c24c88..62c35f258fa6bd1e92334e62c421ce1c47a341e9 100644
--- a/frontend/src/routes/tests/[id]/+page.ts
+++ b/frontend/src/routes/tests/[id]/+page.ts
@@ -1,38 +1,24 @@
 import { getSurveyAPI } from '$lib/api/survey';
 import Survey from '$lib/types/survey';
-import type { Load } from '@sveltejs/kit';
+import { error, type Load } from '@sveltejs/kit';
 
 export const ssr = false;
 
-export const load: Load = async ({ params, parent }) => {
-	const data = await parent();
+export const load: Load = async ({ params, parent, fetch }) => {
+	const { user } = await parent();
+
+	if (!params.id) return error(400, 'Invalid survey ID');
+
 	const survey_id = parseInt(params.id);
-	const { user, token } = data;
-
-	if (isNaN(survey_id)) {
-		return {
-			status: 400,
-			body: {
-				error: 'Invalid survey id'
-			}
-		};
-	}
-
-	const survey = Survey.parse(await getSurveyAPI(survey_id));
-
-	if (!survey) {
-		return {
-			status: 404,
-			body: {
-				error: 'Survey not found'
-			}
-		};
-	}
+
+	if (isNaN(survey_id)) return error(400, 'Invalid survey ID');
+
+	const survey = Survey.parse(await getSurveyAPI(fetch, survey_id));
+
+	if (!survey) return error(404, 'Survey not found');
 
 	return {
-		survey_id,
 		survey,
-		user,
-		token
+		user
 	};
 };
diff --git a/frontend/src/routes/tests/typing/+page.server.ts b/frontend/src/routes/tests/typing/+page.server.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8bdcc4ffa4d7daa8a62ce92a8bfcb493f5010a65
--- /dev/null
+++ b/frontend/src/routes/tests/typing/+page.server.ts
@@ -0,0 +1,7 @@
+import { redirect, type ServerLoad } from '@sveltejs/kit';
+
+export const load: ServerLoad = async ({ params, locals }) => {
+	if (locals.user == null || locals.user == undefined) {
+		redirect(303, '/login?redirect=/tests/typing/' + params.id);
+	}
+};
diff --git a/frontend/src/routes/tests/typing/+page.svelte b/frontend/src/routes/tests/typing/+page.svelte
index dea8911226b03a1d8d99a48cd61e53945fec93f9..ad66eb237c3ffa9eacca42b7f8410255af5be9c0 100644
--- a/frontend/src/routes/tests/typing/+page.svelte
+++ b/frontend/src/routes/tests/typing/+page.svelte
@@ -2,11 +2,14 @@
 	import Typingtest from '$lib/components/tests/typingtest.svelte';
 	import { t } from '$lib/services/i18n';
 
-	let finished = false;
+	let finished = $state(false);
+
+	let { data } = $props();
+	let user = data.user!;
 </script>
 
 {#if finished}
 	<p>{$t('surveys.complete')}</p>
 {:else}
-	<Typingtest onFinish={() => (finished = true)} />
+	<Typingtest {user} onFinish={() => (finished = true)} />
 {/if}
diff --git a/frontend/src/routes/timeslots/+page.server.ts b/frontend/src/routes/timeslots/+page.server.ts
deleted file mode 100644
index 9e9464e76bdedf2c759c84b269c84ffe49eda503..0000000000000000000000000000000000000000
--- a/frontend/src/routes/timeslots/+page.server.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { type ServerLoad, redirect } from '@sveltejs/kit';
-
-export const load: ServerLoad = async ({ locals }) => {
-	if (locals.user == null || locals.user == undefined) {
-		redirect(307, `/login`);
-	}
-};
diff --git a/frontend/src/routes/timeslots/+page.svelte b/frontend/src/routes/timeslots/+page.svelte
deleted file mode 100644
index f175825e28c4cd451388376c97ba62f8098b480d..0000000000000000000000000000000000000000
--- a/frontend/src/routes/timeslots/+page.svelte
+++ /dev/null
@@ -1,27 +0,0 @@
-<script lang="ts">
-	import { onMount } from 'svelte';
-	import { t } from '$lib/services/i18n';
-	import Timeslots from '$lib/components/users/timeslots.svelte';
-	import { getUsersAPI } from '$lib/api/users';
-	import User, { users } from '$lib/types/user';
-	import AvailableTutors from '$lib/components/users/availableTutors.svelte';
-
-	onMount(async () => {
-		User.parseAll(await getUsersAPI());
-	});
-
-	let timeslots = 0n;
-
-	$: filteredUsers = $users.filter((user) => {
-		if (user.availability === 0n) return false;
-		if (timeslots === 0n) return true;
-
-		return user.availability & timeslots;
-	});
-</script>
-
-<div class="w-4/5 m-auto mt-4">
-	<h2 class="my-4 text-xl">{$t('timeslots.availabilities')}</h2>
-	<Timeslots bind:timeslots />
-	<AvailableTutors users={filteredUsers} {timeslots} />
-</div>
diff --git a/frontend/src/routes/tutor/+layout.server.ts b/frontend/src/routes/tutor/+layout.server.ts
index a03ab77e6b293c8c5432ec77e535dda144160890..742f936e9ed4f60fe8fe0aa7e1955c5d8d9cd00f 100644
--- a/frontend/src/routes/tutor/+layout.server.ts
+++ b/frontend/src/routes/tutor/+layout.server.ts
@@ -5,7 +5,7 @@ export const load: ServerLoad = async ({ locals, url }) => {
 		if (url.pathname.startsWith('/tutor/register')) {
 			return {};
 		}
-		redirect(307, '/login');
+		redirect(303, '/login');
 	}
 
 	const user = JSON.parse(locals.user);
diff --git a/frontend/src/routes/tutor/register/+page.svelte b/frontend/src/routes/tutor/register/+page.svelte
index 9b893472cb3d949f52e9aebe48e8dbb5ffcf2587..82b3f07754538ad47776a9ec530f365e05a46d29 100644
--- a/frontend/src/routes/tutor/register/+page.svelte
+++ b/frontend/src/routes/tutor/register/+page.svelte
@@ -1,37 +1,36 @@
 <script lang="ts">
-	import { loginAPI, registerAPI } from '$lib/api/auth';
 	import config from '$lib/config';
 	import { locale, t } from '$lib/services/i18n';
-	import { user } from '$lib/types/user';
 	import { toastAlert, toastWarning } from '$lib/utils/toasts';
 	import { onMount } from 'svelte';
-	import { get } from 'svelte/store';
 	import Timeslots from '$lib/components/users/timeslots.svelte';
-	import User, { users } from '$lib/types/user';
+	import User from '$lib/types/user';
 	import { getUsersAPI, patchUserAPI, getUserContactsAPI } from '$lib/api/users';
 	import { Icon, Envelope, Key, UserCircle, Calendar, QuestionMarkCircle } from 'svelte-hero-icons';
 	import Typingtest from '$lib/components/tests/typingtest.svelte';
 	import { formatToUTCDate } from '$lib/utils/date';
+	import type { PageData } from './$types';
 
-	let current_step = 0;
+	let { data }: { data: PageData } = $props();
+	let user = data.user;
 
-	$: message = '';
+	let current_step = $state(0);
 
-	onMount(async () => {
-		const u = get(user);
+	let message = $state('');
 
-		if (u == null) {
+	onMount(async () => {
+		if (user == null) {
 			current_step = 1;
 			return;
 		}
-		User.parseAll(await getUsersAPI());
+		User.parseAll(await getUsersAPI(fetch));
 
-		if (!u.home_language || !u.target_language || !u.birthdate || !u.gender) {
+		if (!user.home_language || !user.target_language || !user.birthdate || !user.gender) {
 			current_step = 3;
 			return;
 		}
 
-		const contacts = User.parseAll(await getUserContactsAPI(u.id));
+		const contacts = User.parseAll(await getUserContactsAPI(fetch, user.id));
 
 		if (contacts.length == 0) {
 			current_step = 4;
@@ -95,7 +94,7 @@
 	}
 
 	async function onData() {
-		const user_id = get(user)?.id;
+		const user_id = user.id;
 
 		if (!user_id) {
 			toastAlert('Failed to get current user ID');
@@ -107,7 +106,7 @@
 			return;
 		}
 
-		const res = await patchUserAPI(user_id, {
+		const res = await patchUserAPI(fetch, user_id, {
 			ui_language,
 			home_language,
 			birthdate,
@@ -128,7 +127,7 @@
 			return;
 		}
 
-		const res = $user?.setAvailability(timeslots, calcom_link);
+		const res = user.setAvailability(timeslots, calcom_link);
 
 		if (!res) return;
 
diff --git a/frontend/src/routes/tutor/timeslots/+page.svelte b/frontend/src/routes/tutor/timeslots/+page.svelte
deleted file mode 100644
index c38fea0ba1f7114fc9e25ee4dda8b217bc7ad607..0000000000000000000000000000000000000000
--- a/frontend/src/routes/tutor/timeslots/+page.svelte
+++ /dev/null
@@ -1,122 +0,0 @@
-<script lang="ts">
-	import { onMount } from 'svelte';
-	import { t } from '$lib/services/i18n';
-	import Timeslots from '$lib/components/users/timeslots.svelte';
-	import { user } from '$lib/types/user';
-	import { toastWarning } from '$lib/utils/toasts';
-	import { Icon, Calendar, QuestionMarkCircle } from 'svelte-hero-icons';
-
-	$: lastTimeslots = 0n;
-	$: timeslots = 0n;
-	$: calcom_link = '';
-	$: last_calcom_link = '';
-	let ready = false;
-	let sent = false;
-
-	onMount(async () => {
-		if ($user != null) {
-			timeslots = $user.availability;
-			lastTimeslots = timeslots;
-			calcom_link = $user.calcom_link || '';
-			last_calcom_link = calcom_link;
-			ready = true;
-		}
-	});
-
-	async function send() {
-		if (!calcom_link || calcom_link.length == 0) {
-			toastWarning($t('timeslots.calcomWarning'));
-			return;
-		}
-
-		const res = $user?.setAvailability(timeslots, calcom_link);
-
-		if (!res) return;
-
-		lastTimeslots = timeslots;
-		last_calcom_link = calcom_link;
-		sent = true;
-		setTimeout(() => (sent = false), 3000);
-	}
-</script>
-
-<svelte:head>
-	<script>
-		(function (C, A, L) {
-			let p = function (a, ar) {
-				a.q.push(ar);
-			};
-			let d = C.document;
-			C.Cal =
-				C.Cal ||
-				function () {
-					let cal = C.Cal;
-					let ar = arguments;
-					if (!cal.loaded) {
-						cal.ns = {};
-						cal.q = cal.q || [];
-						d.head.appendChild(d.createElement('script')).src = A;
-						cal.loaded = true;
-					}
-					if (ar[0] === L) {
-						const api = function () {
-							p(api, arguments);
-						};
-						const namespace = ar[1];
-						api.q = api.q || [];
-						if (typeof namespace === 'string') {
-							cal.ns[namespace] = cal.ns[namespace] || api;
-							p(cal.ns[namespace], ar);
-							p(cal, ['initNamespace', namespace]);
-						} else p(cal, ar);
-						return;
-					}
-					p(cal, ar);
-				};
-		})(window, 'https://app.cal.com/embed/embed.js', 'init');
-		Cal('init');
-	</script>
-</svelte:head>
-
-<div class="max-w-screen-md mx-auto p-2">
-	<h2 class="my-4 text-xl">{$t('timeslots.setAvailabilities')}</h2>
-	{#if ready}
-		<div class="form-control mt-4">
-			<label class="label" for="calcom">
-				<span class="label-text">
-					{$t('timeslots.calcom')}
-					<a
-						href="https://forge.uclouvain.be/sbibauw/languagelab/-/blob/93897d67f63ec81ebbe13b10035e4cd5a3a09071/docs/cal.com.md"
-						target="_blank"
-					>
-						<Icon
-							src={QuestionMarkCircle}
-							class="w-5 h-5 cursor-pointer inline"
-							title="Documentation"
-							solid
-						/>
-					</a>
-				</span>
-			</label>
-			<div class="input flex items-center">
-				<Icon src={Calendar} class="w-5 h-5 mr-2 opacity-70" solid />
-				<input
-					type="text"
-					id="calcom"
-					class="grow"
-					placeholder="username/tutoring"
-					bind:value={calcom_link}
-				/>
-			</div>
-		</div>
-
-		<div class="form-control mt-4">
-			<button class="button" data-cal-link={calcom_link}>{$t('button.tryit')}</button>
-			<button
-				class="button mt-4"
-				disabled={sent || (lastTimeslots === timeslots && calcom_link === last_calcom_link)}
-				on:click={send}>{$t(sent ? 'button.updated' : 'button.update')}</button
-			>
-		</div>
-	{/if}
-</div>
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
index 180277378b4e369a23ddffb6333720078fb24b97..a76f2ab5f34f065df0bc5571047c14c0788ac7b8 100644
--- a/frontend/tsconfig.json
+++ b/frontend/tsconfig.json
@@ -10,11 +10,6 @@
 		"skipLibCheck": true,
 		"sourceMap": true,
 		"strict": true,
-		"baseUrl": "./",
-		"paths": {
-			"$lib/*": ["src/lib/*"],
-			"$routes/*": ["src/routes/*"]
-		},
 		"moduleResolution": "node",
 		"target": "ESNext",
 		"module": "ESNext",
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index 4fe702bfd25658ce68b5a7faad57c7c1a3e89818..fd70ec442a25fdcb9c262229952f779f547f7f20 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -12,14 +12,5 @@ export default defineConfig({
 	optimizeDeps: {
 		exclude: ['emoji-picker-element'],
 		include: ['svelte-gravatar', 'svelte-waypoint']
-	},
-	server: {
-		proxy: {
-			'/api': {
-				target: 'http://localhost:8000',
-				changeOrigin: true,
-				secure: false
-			}
-		}
 	}
 });