diff --git a/backend/alembic/versions/0bf670c4a564_study_users.py b/backend/alembic/versions/0bf670c4a564_study_users.py
new file mode 100644
index 0000000000000000000000000000000000000000..f3cd5906e8ce3db9c953c22b2b03d547f4083829
--- /dev/null
+++ b/backend/alembic/versions/0bf670c4a564_study_users.py
@@ -0,0 +1,53 @@
+"""study users
+
+Revision ID: 0bf670c4a564
+Revises: 37f4cc82f93e
+Create Date: 2025-01-12 19:33:40.885741
+
+"""
+
+from typing import Sequence, Union
+
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision: str = "0bf670c4a564"
+down_revision: Union[str, None] = "37f4cc82f93e"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+    op.create_table(
+        "study_users",
+        sa.Column("study_id", sa.Integer(), nullable=False),
+        sa.Column("user_id", sa.Integer(), nullable=False),
+        sa.ForeignKeyConstraint(["study_id"], ["studies.id"]),
+        sa.ForeignKeyConstraint(["user_id"], ["users.id"]),
+        sa.PrimaryKeyConstraint("study_id", "user_id"),
+    )
+
+    op.create_table(
+        "study_surveys",
+        sa.Column("study_id", sa.Integer(), nullable=False),
+        sa.Column("survey_id", sa.Integer(), nullable=False),
+        sa.ForeignKeyConstraint(["study_id"], ["studies.id"]),
+        sa.ForeignKeyConstraint(["survey_id"], ["survey_surveys.id"]),
+        sa.PrimaryKeyConstraint("study_id", "survey_id"),
+    )
+
+    op.add_column("studies", sa.Column("typing_test", sa.Boolean(), nullable=True))
+
+    op.drop_column("users", "study_id")
+
+
+def downgrade() -> None:
+    op.add_column("users", sa.Column("study_id", sa.Integer(), nullable=True))
+    op.create_foreign_key(None, "users", "studies", ["study_id"], ["id"])
+
+    op.drop_column("studies", "typing_test")
+
+    op.drop_table("study_surveys")
+    op.drop_table("study_users")
diff --git a/backend/app/crud.py b/backend/app/crud.py
index 2f25bae81f9aa78f6a6e3deba145f16e20aca4fb..41be44b063f73793b49614238cbc472fe20a0131 100644
--- a/backend/app/crud.py
+++ b/backend/app/crud.py
@@ -263,14 +263,6 @@ def delete_message_feedback(db: Session, feedback_id: int):
     db.commit()
 
 
-def create_study(db: Session, study: schemas.StudyCreate):
-    db_study = models.Study(**study.dict())
-    db.add(db_study)
-    db.commit()
-    db.refresh(db_study)
-    return db_study
-
-
 def create_test_typing(db: Session, test: schemas.TestTypingCreate, user: schemas.User):
     db_test = models.TestTyping(user_id=user.id)
     db.add(db_test)
@@ -447,3 +439,67 @@ def create_survey_response_info(
     db.commit()
     db.refresh(db_survey_response_info)
     return db_survey_response_info
+
+
+def create_study(db: Session, study: schemas.StudyCreate):
+    db_study = models.Study(**study.dict())
+    db.add(db_study)
+    db.commit()
+    db.refresh(db_study)
+    return db_study
+
+
+def get_study(db: Session, study_id: int):
+    return db.query(models.Study).filter(models.Study.id == study_id).first()
+
+
+def get_studies(db: Session, skip: int = 0):
+    return db.query(models.Study).offset(skip).all()
+
+
+def update_study(db: Session, study: schemas.StudyCreate, study_id: int):
+    db.query(models.Study).filter(models.Study.id == study_id).update(
+        {**study.model_dump(exclude_unset=True)}
+    )
+    db.commit()
+
+
+def delete_study(db: Session, study_id: int):
+    db.query(models.Study).filter(models.Study.id == study_id).delete()
+    db.commit()
+
+
+def add_user_to_study(db: Session, study_id: int, user: schemas.User):
+    db_study = db.query(models.Study).filter(models.Study.id == study_id).first()
+    db_study.users.append(user)
+    db.commit()
+    db.refresh(db_study)
+    return db_study
+
+
+def remove_user_from_study(db: Session, study_id: int, user: schemas.User):
+    study = db.query(models.Study).filter(models.Study.id == study_id).first()
+    user = db.query(models.User).filter(models.User.id == user.id).first()
+    study.users.remove(user)
+    db.commit()
+    return study
+
+
+def add_survey_to_study(db: Session, study_id: int, survey: schemas.Survey):
+    db_study = db.query(models.Study).filter(models.Study.id == study_id).first()
+    db_study.surveys.append(survey)
+    db.commit()
+    db.refresh(db_study)
+    return db_study
+
+
+def remove_survey_from_study(db: Session, study_id: int, survey: schemas.Survey):
+    study = db.query(models.Study).filter(models.Study.id == study_id).first()
+    survey = (
+        db.query(models.SurveySurvey)
+        .filter(models.SurveySurvey.id == survey.id)
+        .first()
+    )
+    study.surveys.remove(survey)
+    db.commit()
+    return study
diff --git a/backend/app/main.py b/backend/app/main.py
index afae43c18065b80937a7090323fa2d99883c6963..7acb7331954b18e356241afde5f97de9b07162ee 100644
--- a/backend/app/main.py
+++ b/backend/app/main.py
@@ -217,6 +217,24 @@ def update_user(
         raise HTTPException(status_code=404, detail="User not found")
 
 
+@usersRouter.get("/by-email/{email}", response_model=schemas.User)
+def read_user_by_email(
+    email: str,
+    db: Session = Depends(get_db),
+    current_user: schemas.User = Depends(get_jwt_user),
+):
+    if not check_user_level(current_user, models.UserType.ADMIN):
+        raise HTTPException(
+            status_code=401, detail="You do not have permission to view this user"
+        )
+
+    db_user = crud.get_user_by_email(db, email)
+
+    if db_user is None:
+        raise HTTPException(status_code=404, detail="User not found")
+    return db_user
+
+
 @usersRouter.get("/{user_id}/sessions", response_model=list[schemas.Session])
 def read_user_sessions(
     user_id: int,
@@ -959,7 +977,7 @@ def propagate_presence(
     return
 
 
-@studyRouter.post("/", status_code=status.HTTP_201_CREATED)
+@studyRouter.post("", status_code=status.HTTP_201_CREATED)
 def study_create(
     study: schemas.StudyCreate,
     db: Session = Depends(get_db),
@@ -972,6 +990,177 @@ def study_create(
     return crud.create_study(db, study).id
 
 
+@studyRouter.get("", response_model=list[schemas.Study])
+def studies_read(
+    db: Session = Depends(get_db),
+):
+    return crud.get_studies(db)
+
+
+@studyRouter.get("/{study_id}", response_model=schemas.Study)
+def study_read(
+    study_id: int,
+    db: Session = Depends(get_db),
+):
+    study = crud.get_study(db, study_id)
+    if study is None:
+        raise HTTPException(status_code=404, detail="Study not found")
+    return study
+
+
+@studyRouter.patch("/{study_id}", status_code=status.HTTP_204_NO_CONTENT)
+def study_update(
+    study_id: int,
+    studyUpdate: schemas.StudyCreate,
+    db: Session = Depends(get_db),
+    current_user: schemas.User = Depends(get_jwt_user),
+):
+    if not check_user_level(current_user, models.UserType.ADMIN):
+        raise HTTPException(
+            status_code=401, detail="You do not have permission to update a study"
+        )
+
+    study = crud.get_study(db, study_id)
+    if study is None:
+        raise HTTPException(status_code=404, detail="Study not found")
+
+    crud.update_study(db, studyUpdate, study_id)
+
+
+@studyRouter.delete("/{study_id}", status_code=status.HTTP_204_NO_CONTENT)
+def study_delete(
+    study_id: int,
+    db: Session = Depends(get_db),
+    current_user: schemas.User = Depends(get_jwt_user),
+):
+    if not check_user_level(current_user, models.UserType.ADMIN):
+        raise HTTPException(
+            status_code=401, detail="You do not have permission to delete a study"
+        )
+
+    study = crud.get_study(db, study_id)
+    if study is None:
+        raise HTTPException(status_code=404, detail="Study not found")
+
+    crud.delete_study(db, study_id)
+
+
+@studyRouter.post("/{study_id}/users/{user_id}", status_code=status.HTTP_201_CREATED)
+def study_add_user(
+    study_id: int,
+    user_id: int,
+    db: Session = Depends(get_db),
+    current_user: schemas.User = Depends(get_jwt_user),
+):
+    user = crud.get_user(db, user_id)
+
+    if user != current_user and not check_user_level(
+        current_user, models.UserType.ADMIN
+    ):
+        raise HTTPException(
+            status_code=401,
+            detail="You do not have permission to add a user to a study",
+        )
+
+    study = crud.get_study(db, study_id)
+    if study is None:
+        raise HTTPException(status_code=404, detail="Study not found")
+
+    if user is None:
+        raise HTTPException(status_code=404, detail="User not found")
+
+    if user in study.users:
+        raise HTTPException(status_code=400, detail="User already exists in this study")
+
+    crud.add_user_to_study(db, study_id, user)
+
+
+@studyRouter.delete(
+    "/{study_id}/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT
+)
+def study_delete_user(
+    study_id: int,
+    user_id: int,
+    db: Session = Depends(get_db),
+    current_user: schemas.User = Depends(get_jwt_user),
+):
+    if not check_user_level(current_user, models.UserType.ADMIN):
+        raise HTTPException(
+            status_code=401,
+            detail="You do not have permission to add a user to a study",
+        )
+
+    study = crud.get_study(db, study_id)
+    if study is None:
+        raise HTTPException(status_code=404, detail="Study not found")
+
+    user = crud.get_user(db, user_id)
+    if user is None:
+        raise HTTPException(status_code=404, detail="User not found")
+
+    if user not in study.users:
+        raise HTTPException(status_code=400, detail="User does not exist in this study")
+
+    crud.remove_user_from_study(db, study_id, user)
+
+
+@studyRouter.post(
+    "/{study_id}/surveys/{survey_id}", status_code=status.HTTP_201_CREATED
+)
+def study_add_survey(
+    study_id: int,
+    survey_id: int,
+    db: Session = Depends(get_db),
+    current_user: schemas.User = Depends(get_jwt_user),
+):
+    if not check_user_level(current_user, models.UserType.ADMIN):
+        raise HTTPException(
+            status_code=401,
+            detail="You do not have permission to add a survey to a study",
+        )
+
+    study = crud.get_study(db, study_id)
+    if study is None:
+        raise HTTPException(status_code=404, detail="Study not found")
+    survey = crud.get_survey(db, survey_id)
+    if survey is None:
+        raise HTTPException(status_code=404, detail="Survey not found")
+    if survey in study.surveys:
+        raise HTTPException(
+            status_code=400, detail="Survey already exists in this study"
+        )
+
+    crud.add_survey_to_study(db, study_id, survey)
+
+
+@studyRouter.delete(
+    "/{study_id}/surveys/{survey_id}", status_code=status.HTTP_204_NO_CONTENT
+)
+def study_delete_survey(
+    study_id: int,
+    survey_id: int,
+    db: Session = Depends(get_db),
+    current_user: schemas.User = Depends(get_jwt_user),
+):
+    if not check_user_level(current_user, models.UserType.ADMIN):
+        raise HTTPException(
+            status_code=401,
+            detail="You do not have permission to add a survey to a study",
+        )
+    study = crud.get_study(db, study_id)
+    if study is None:
+        raise HTTPException(status_code=404, detail="Study not found")
+    survey = crud.get_survey(db, survey_id)
+    if survey is None:
+        raise HTTPException(status_code=404, detail="Survey not found")
+    if survey not in study.surveys:
+        raise HTTPException(
+            status_code=400, detail="Survey does not exist in this study"
+        )
+
+    crud.remove_survey_from_study(db, study_id, survey)
+
+
 @websocketRouter.websocket("/sessions/{session_id}")
 async def websocket_session(
     session_id: int, token: str, websocket: WebSocket, db: Session = Depends(get_db)
diff --git a/backend/app/models.py b/backend/app/models.py
index ed982e8fe68d6040230d72585ee262cc3b2d1789..b156ea33e33e5a983440aeab2f26f8a865fbab29 100644
--- a/backend/app/models.py
+++ b/backend/app/models.py
@@ -47,7 +47,6 @@ class User(Base):
     birthdate = Column(DateTime, default=None)
     gender = Column(String, default=None)
     calcom_link = Column(String, default="")
-    study_id = Column(Integer, ForeignKey("studies.id"), default=None)
     last_survey = Column(DateTime, default=None)
 
     sessions = relationship(
@@ -70,6 +69,8 @@ class User(Base):
         back_populates="contacts",
     )
 
+    studies = relationship("Study", secondary="study_users", back_populates="users")
+
 
 class UserSurveyWeekly(Base):
     __tablename__ = "users_survey_weekly"
@@ -240,6 +241,7 @@ class SurveySurvey(Base):
     groups = relationship(
         "SurveyGroup", secondary="survey_survey_groups", backref="survey"
     )
+    studies = relationship("Study", secondary="study_surveys", back_populates="surveys")
 
 
 class SurveySurveyGroup(Base):
@@ -285,3 +287,23 @@ class Study(Base):
     start_date = Column(DateTime)
     end_date = Column(DateTime)
     chat_duration = Column(Integer)
+    typing_test = Column(Boolean)
+
+    users = relationship("User", secondary="study_users", back_populates="studies")
+    surveys = relationship(
+        "SurveySurvey", secondary="study_surveys", back_populates="studies"
+    )
+
+
+class StudyUser(Base):
+    __tablename__ = "study_users"
+
+    study_id = Column(Integer, ForeignKey("studies.id"), primary_key=True)
+    user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
+
+
+class StudySurvey(Base):
+    __tablename__ = "study_surveys"
+
+    study_id = Column(Integer, ForeignKey("studies.id"), primary_key=True)
+    survey_id = Column(Integer, ForeignKey("survey_surveys.id"), primary_key=True)
diff --git a/backend/app/schemas.py b/backend/app/schemas.py
index a2a9b64dffaf17ab8093443bbb919bab8c82db37..a5b75b334374db6498d645842a546531c911f016 100644
--- a/backend/app/schemas.py
+++ b/backend/app/schemas.py
@@ -28,7 +28,6 @@ class User(BaseModel):
     birthdate: NaiveDatetime | None
     gender: str | None = None
     calcom_link: str | None
-    study_id: int | None = None
     last_survey: NaiveDatetime | None = None
 
     class Config:
@@ -62,7 +61,6 @@ class UserCreate(BaseModel):
     birthdate: NaiveDatetime | None = None
     gender: str | None = None
     calcom_link: str | None = None
-    study_id: int | None = None
     last_survey: NaiveDatetime | None = None
 
 
@@ -79,7 +77,6 @@ class UserUpdate(BaseModel):
     birthdate: NaiveDatetime | None = None
     gender: str | None = None
     calcom_link: str | None = None
-    study_id: int | None = None
     last_survey: NaiveDatetime | None = None
 
     class Config:
@@ -354,6 +351,8 @@ class Study(BaseModel):
     start_date: NaiveDatetime
     end_date: NaiveDatetime
     chat_duration: int
+    users: list[User]
+    surveys: list[Survey]
 
 
 class StudyCreate(BaseModel):
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 3a0ba508e603ea29e41710d3f0da07bdb9eebaac..f4f9f7d90024901bdeb254fa5026fa793cdcd150 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -607,9 +607,9 @@
 			}
 		},
 		"node_modules/@eslint/js": {
-			"version": "9.16.0",
-			"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.16.0.tgz",
-			"integrity": "sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==",
+			"version": "9.17.0",
+			"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz",
+			"integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==",
 			"dev": true,
 			"license": "MIT",
 			"engines": {
@@ -665,50 +665,51 @@
 			"license": "MIT"
 		},
 		"node_modules/@formatjs/ecma402-abstract": {
-			"version": "2.2.4",
-			"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.2.4.tgz",
-			"integrity": "sha512-lFyiQDVvSbQOpU+WFd//ILolGj4UgA/qXrKeZxdV14uKiAUiPAtX6XAn7WBCRi7Mx6I7EybM9E5yYn4BIpZWYg==",
+			"version": "2.3.1",
+			"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.1.tgz",
+			"integrity": "sha512-Ip9uV+/MpLXWRk03U/GzeJMuPeOXpJBSB5V1tjA6kJhvqssye5J5LoYLc7Z5IAHb7nR62sRoguzrFiVCP/hnzw==",
 			"license": "MIT",
 			"dependencies": {
-				"@formatjs/fast-memoize": "2.2.3",
-				"@formatjs/intl-localematcher": "0.5.8",
+				"@formatjs/fast-memoize": "2.2.5",
+				"@formatjs/intl-localematcher": "0.5.9",
+				"decimal.js": "10",
 				"tslib": "2"
 			}
 		},
 		"node_modules/@formatjs/fast-memoize": {
-			"version": "2.2.3",
-			"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.3.tgz",
-			"integrity": "sha512-3jeJ+HyOfu8osl3GNSL4vVHUuWFXR03Iz9jjgI7RwjG6ysu/Ymdr0JRCPHfF5yGbTE6JCrd63EpvX1/WybYRbA==",
+			"version": "2.2.5",
+			"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.5.tgz",
+			"integrity": "sha512-6PoewUMrrcqxSoBXAOJDiW1m+AmkrAj0RiXnOMD59GRaswjXhm3MDhgepXPBgonc09oSirAJTsAggzAGQf6A6g==",
 			"license": "MIT",
 			"dependencies": {
 				"tslib": "2"
 			}
 		},
 		"node_modules/@formatjs/icu-messageformat-parser": {
-			"version": "2.9.4",
-			"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.9.4.tgz",
-			"integrity": "sha512-Tbvp5a9IWuxUcpWNIW6GlMQYEc4rwNHR259uUFoKWNN1jM9obf9Ul0e+7r7MvFOBNcN+13K7NuKCKqQiAn1QEg==",
+			"version": "2.9.7",
+			"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.9.7.tgz",
+			"integrity": "sha512-cuEHyRM5VqLQobANOjtjlgU7+qmk9Q3fDQuBiRRJ3+Wp3ZoZhpUPtUfuimZXsir6SaI2TaAJ+SLo9vLnV5QcbA==",
 			"license": "MIT",
 			"dependencies": {
-				"@formatjs/ecma402-abstract": "2.2.4",
-				"@formatjs/icu-skeleton-parser": "1.8.8",
+				"@formatjs/ecma402-abstract": "2.3.1",
+				"@formatjs/icu-skeleton-parser": "1.8.11",
 				"tslib": "2"
 			}
 		},
 		"node_modules/@formatjs/icu-skeleton-parser": {
-			"version": "1.8.8",
-			"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.8.tgz",
-			"integrity": "sha512-vHwK3piXwamFcx5YQdCdJxUQ1WdTl6ANclt5xba5zLGDv5Bsur7qz8AD7BevaKxITwpgDeU0u8My3AIibW9ywA==",
+			"version": "1.8.11",
+			"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.11.tgz",
+			"integrity": "sha512-8LlHHE/yL/zVJZHAX3pbKaCjZKmBIO6aJY1mkVh4RMSEu/2WRZ4Ysvv3kKXJ9M8RJLBHdnk1/dUQFdod1Dt7Dw==",
 			"license": "MIT",
 			"dependencies": {
-				"@formatjs/ecma402-abstract": "2.2.4",
+				"@formatjs/ecma402-abstract": "2.3.1",
 				"tslib": "2"
 			}
 		},
 		"node_modules/@formatjs/intl-localematcher": {
-			"version": "0.5.8",
-			"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.8.tgz",
-			"integrity": "sha512-I+WDNWWJFZie+jkfkiK5Mp4hEDyRSEvmyfYadflOno/mmKJKcB17fEpEH0oJu/OWhhCJ8kJBDz2YMd/6cDl7Mg==",
+			"version": "0.5.9",
+			"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.9.tgz",
+			"integrity": "sha512-8zkGu/sv5euxbjfZ/xmklqLyDGQSxsLqg8XOq88JW3cmJtzhCP8EtSJXlaKZnVO4beEaoiT9wj4eIoCQ9smwxA==",
 			"license": "MIT",
 			"dependencies": {
 				"tslib": "2"
@@ -799,9 +800,9 @@
 			}
 		},
 		"node_modules/@jridgewell/gen-mapping": {
-			"version": "0.3.5",
-			"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
-			"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
+			"version": "0.3.8",
+			"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
+			"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
 			"license": "MIT",
 			"dependencies": {
 				"@jridgewell/set-array": "^1.2.1",
@@ -903,9 +904,9 @@
 			"license": "MIT"
 		},
 		"node_modules/@rollup/plugin-commonjs": {
-			"version": "28.0.1",
-			"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.1.tgz",
-			"integrity": "sha512-+tNWdlWKbpB3WgBN7ijjYkq9X5uhjmcvyjEght4NmH5fAU++zfQzAJ6wumLS+dNcvwEZhKx2Z+skY8m7v0wGSA==",
+			"version": "28.0.2",
+			"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.2.tgz",
+			"integrity": "sha512-BEFI2EDqzl+vA1rl97IDRZ61AIwGH093d9nz8+dThxJNH8oSoB7MjWvPCX3dkaK1/RCJ/1v/R1XB15FuSs0fQw==",
 			"dev": true,
 			"license": "MIT",
 			"dependencies": {
@@ -951,9 +952,9 @@
 			}
 		},
 		"node_modules/@rollup/plugin-node-resolve": {
-			"version": "15.3.0",
-			"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.0.tgz",
-			"integrity": "sha512-9eO5McEICxMzJpDW9OnMYSv4Sta3hmt7VtBFz5zR9273suNOydOyq/FrGeGy+KsTRFm8w0SLVhzig2ILFT63Ag==",
+			"version": "15.3.1",
+			"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz",
+			"integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==",
 			"dev": true,
 			"license": "MIT",
 			"dependencies": {
@@ -976,9 +977,9 @@
 			}
 		},
 		"node_modules/@rollup/pluginutils": {
-			"version": "5.1.3",
-			"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.3.tgz",
-			"integrity": "sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==",
+			"version": "5.1.4",
+			"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz",
+			"integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==",
 			"dev": true,
 			"license": "MIT",
 			"dependencies": {
@@ -1285,9 +1286,9 @@
 			}
 		},
 		"node_modules/@sveltejs/adapter-node": {
-			"version": "5.2.9",
-			"resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.2.9.tgz",
-			"integrity": "sha512-51euNrx0AcaTu8//wDfVh7xmqQSVgU52rfinE/MwvGkJa4nHPJMHmzv6+OIpmxg7gZaF6+5NVlxnieCzxLD59g==",
+			"version": "5.2.10",
+			"resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.2.10.tgz",
+			"integrity": "sha512-U0SCdULhHbSYCDgvvrHRtKUykl9GZkM/b3NyeIUtaxM39upQFd5059pWmXgTNaNTU1HMdj4xx0xvNAvQcIzmXQ==",
 			"dev": true,
 			"license": "MIT",
 			"dependencies": {
@@ -1311,9 +1312,9 @@
 			}
 		},
 		"node_modules/@sveltejs/kit": {
-			"version": "2.9.0",
-			"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.9.0.tgz",
-			"integrity": "sha512-W3E7ed3ChB6kPqRs2H7tcHp+Z7oiTFC6m+lLyAQQuyXeqw6LdNuuwEUla+5VM0OGgqQD+cYD6+7Xq80vVm17Vg==",
+			"version": "2.11.1",
+			"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.11.1.tgz",
+			"integrity": "sha512-dAiHDEd+AOm20eYdMPV1a2eKBOc0s/7XsSs7PCoNv2kKS7BAoVRC9uzR+FQmxLtp8xuEo9z8CtrMQoszkThltQ==",
 			"dev": true,
 			"hasInstallScript": true,
 			"license": "MIT",
@@ -1344,9 +1345,9 @@
 			}
 		},
 		"node_modules/@sveltejs/vite-plugin-svelte": {
-			"version": "4.0.2",
-			"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-4.0.2.tgz",
-			"integrity": "sha512-Y9r/fWy539XlAC7+5wfNJ4zH6TygUYoQ0Eegzp0zDDqhJ54+92gOyOX1l4MO1cJSx0O+Gp13YePT5XEa3+kX0w==",
+			"version": "4.0.3",
+			"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-4.0.3.tgz",
+			"integrity": "sha512-J7nC5gT5qpmvyD2pmzPUntLUgoinyEaNy9sTpGGE6N7pblggO0A1NyneJJvR2ELlzK6ti28aF2SLXG1yJdnJeA==",
 			"dev": true,
 			"license": "MIT",
 			"dependencies": {
@@ -1490,17 +1491,17 @@
 			}
 		},
 		"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",
-			"integrity": "sha512-HU1KAdW3Tt8zQkdvNoIijfWDMvdSweFYm4hWh+KwhPstv+sCmWb89hCIP8msFm9N1R/ooh9honpSuvqKWlYy3w==",
+			"version": "8.18.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.0.tgz",
+			"integrity": "sha512-NR2yS7qUqCL7AIxdJUQf2MKKNDVNaig/dEB0GBLU7D+ZdHgK1NoH/3wsgO3OnPVipn51tG3MAwaODEGil70WEw==",
 			"dev": true,
 			"license": "MIT",
 			"dependencies": {
 				"@eslint-community/regexpp": "^4.10.0",
-				"@typescript-eslint/scope-manager": "8.17.0",
-				"@typescript-eslint/type-utils": "8.17.0",
-				"@typescript-eslint/utils": "8.17.0",
-				"@typescript-eslint/visitor-keys": "8.17.0",
+				"@typescript-eslint/scope-manager": "8.18.0",
+				"@typescript-eslint/type-utils": "8.18.0",
+				"@typescript-eslint/utils": "8.18.0",
+				"@typescript-eslint/visitor-keys": "8.18.0",
 				"graphemer": "^1.4.0",
 				"ignore": "^5.3.1",
 				"natural-compare": "^1.4.0",
@@ -1515,25 +1516,21 @@
 			},
 			"peerDependencies": {
 				"@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
-				"eslint": "^8.57.0 || ^9.0.0"
-			},
-			"peerDependenciesMeta": {
-				"typescript": {
-					"optional": true
-				}
+				"eslint": "^8.57.0 || ^9.0.0",
+				"typescript": ">=4.8.4 <5.8.0"
 			}
 		},
 		"node_modules/@typescript-eslint/parser": {
-			"version": "8.17.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.17.0.tgz",
-			"integrity": "sha512-Drp39TXuUlD49F7ilHHCG7TTg8IkA+hxCuULdmzWYICxGXvDXmDmWEjJYZQYgf6l/TFfYNE167m7isnc3xlIEg==",
+			"version": "8.18.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.18.0.tgz",
+			"integrity": "sha512-hgUZ3kTEpVzKaK3uNibExUYm6SKKOmTU2BOxBSvOYwtJEPdVQ70kZJpPjstlnhCHcuc2WGfSbpKlb/69ttyN5Q==",
 			"dev": true,
-			"license": "BSD-2-Clause",
+			"license": "MITClause",
 			"dependencies": {
-				"@typescript-eslint/scope-manager": "8.17.0",
-				"@typescript-eslint/types": "8.17.0",
-				"@typescript-eslint/typescript-estree": "8.17.0",
-				"@typescript-eslint/visitor-keys": "8.17.0",
+				"@typescript-eslint/scope-manager": "8.18.0",
+				"@typescript-eslint/types": "8.18.0",
+				"@typescript-eslint/typescript-estree": "8.18.0",
+				"@typescript-eslint/visitor-keys": "8.18.0",
 				"debug": "^4.3.4"
 			},
 			"engines": {
@@ -1544,23 +1541,19 @@
 				"url": "https://opencollective.com/typescript-eslint"
 			},
 			"peerDependencies": {
-				"eslint": "^8.57.0 || ^9.0.0"
-			},
-			"peerDependenciesMeta": {
-				"typescript": {
-					"optional": true
-				}
+				"eslint": "^8.57.0 || ^9.0.0",
+				"typescript": ">=4.8.4 <5.8.0"
 			}
 		},
 		"node_modules/@typescript-eslint/scope-manager": {
-			"version": "8.17.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.17.0.tgz",
-			"integrity": "sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==",
+			"version": "8.18.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.18.0.tgz",
+			"integrity": "sha512-PNGcHop0jkK2WVYGotk/hxj+UFLhXtGPiGtiaWgVBVP1jhMoMCHlTyJA+hEj4rszoSdLTK3fN4oOatrL0Cp+Xw==",
 			"dev": true,
 			"license": "MIT",
 			"dependencies": {
-				"@typescript-eslint/types": "8.17.0",
-				"@typescript-eslint/visitor-keys": "8.17.0"
+				"@typescript-eslint/types": "8.18.0",
+				"@typescript-eslint/visitor-keys": "8.18.0"
 			},
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1571,14 +1564,14 @@
 			}
 		},
 		"node_modules/@typescript-eslint/type-utils": {
-			"version": "8.17.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.17.0.tgz",
-			"integrity": "sha512-q38llWJYPd63rRnJ6wY/ZQqIzPrBCkPdpIsaCfkR3Q4t3p6sb422zougfad4TFW9+ElIFLVDzWGiGAfbb/v2qw==",
+			"version": "8.18.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.18.0.tgz",
+			"integrity": "sha512-er224jRepVAVLnMF2Q7MZJCq5CsdH2oqjP4dT7K6ij09Kyd+R21r7UVJrF0buMVdZS5QRhDzpvzAxHxabQadow==",
 			"dev": true,
 			"license": "MIT",
 			"dependencies": {
-				"@typescript-eslint/typescript-estree": "8.17.0",
-				"@typescript-eslint/utils": "8.17.0",
+				"@typescript-eslint/typescript-estree": "8.18.0",
+				"@typescript-eslint/utils": "8.18.0",
 				"debug": "^4.3.4",
 				"ts-api-utils": "^1.3.0"
 			},
@@ -1590,18 +1583,14 @@
 				"url": "https://opencollective.com/typescript-eslint"
 			},
 			"peerDependencies": {
-				"eslint": "^8.57.0 || ^9.0.0"
-			},
-			"peerDependenciesMeta": {
-				"typescript": {
-					"optional": true
-				}
+				"eslint": "^8.57.0 || ^9.0.0",
+				"typescript": ">=4.8.4 <5.8.0"
 			}
 		},
 		"node_modules/@typescript-eslint/types": {
-			"version": "8.17.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.17.0.tgz",
-			"integrity": "sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==",
+			"version": "8.18.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.18.0.tgz",
+			"integrity": "sha512-FNYxgyTCAnFwTrzpBGq+zrnoTO4x0c1CKYY5MuUTzpScqmY5fmsh2o3+57lqdI3NZucBDCzDgdEbIaNfAjAHQA==",
 			"dev": true,
 			"license": "MIT",
 			"engines": {
@@ -1613,14 +1602,14 @@
 			}
 		},
 		"node_modules/@typescript-eslint/typescript-estree": {
-			"version": "8.17.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz",
-			"integrity": "sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==",
+			"version": "8.18.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.0.tgz",
+			"integrity": "sha512-rqQgFRu6yPkauz+ms3nQpohwejS8bvgbPyIDq13cgEDbkXt4LH4OkDMT0/fN1RUtzG8e8AKJyDBoocuQh8qNeg==",
 			"dev": true,
-			"license": "BSD-2-Clause",
+			"license": "MIT",
 			"dependencies": {
-				"@typescript-eslint/types": "8.17.0",
-				"@typescript-eslint/visitor-keys": "8.17.0",
+				"@typescript-eslint/types": "8.18.0",
+				"@typescript-eslint/visitor-keys": "8.18.0",
 				"debug": "^4.3.4",
 				"fast-glob": "^3.3.2",
 				"is-glob": "^4.0.3",
@@ -1635,23 +1624,21 @@
 				"type": "opencollective",
 				"url": "https://opencollective.com/typescript-eslint"
 			},
-			"peerDependenciesMeta": {
-				"typescript": {
-					"optional": true
-				}
+			"peerDependencies": {
+				"typescript": ">=4.8.4 <5.8.0"
 			}
 		},
 		"node_modules/@typescript-eslint/utils": {
-			"version": "8.17.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.17.0.tgz",
-			"integrity": "sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w==",
+			"version": "8.18.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.18.0.tgz",
+			"integrity": "sha512-p6GLdY383i7h5b0Qrfbix3Vc3+J2k6QWw6UMUeY5JGfm3C5LbZ4QIZzJNoNOfgyRe0uuYKjvVOsO/jD4SJO+xg==",
 			"dev": true,
 			"license": "MIT",
 			"dependencies": {
 				"@eslint-community/eslint-utils": "^4.4.0",
-				"@typescript-eslint/scope-manager": "8.17.0",
-				"@typescript-eslint/types": "8.17.0",
-				"@typescript-eslint/typescript-estree": "8.17.0"
+				"@typescript-eslint/scope-manager": "8.18.0",
+				"@typescript-eslint/types": "8.18.0",
+				"@typescript-eslint/typescript-estree": "8.18.0"
 			},
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1661,22 +1648,18 @@
 				"url": "https://opencollective.com/typescript-eslint"
 			},
 			"peerDependencies": {
-				"eslint": "^8.57.0 || ^9.0.0"
-			},
-			"peerDependenciesMeta": {
-				"typescript": {
-					"optional": true
-				}
+				"eslint": "^8.57.0 || ^9.0.0",
+				"typescript": ">=4.8.4 <5.8.0"
 			}
 		},
 		"node_modules/@typescript-eslint/visitor-keys": {
-			"version": "8.17.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz",
-			"integrity": "sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==",
+			"version": "8.18.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.0.tgz",
+			"integrity": "sha512-pCh/qEA8Lb1wVIqNvBke8UaRjJ6wrAWkJO5yyIbs8Yx6TNGYyfNjOo61tLv+WwLvoLPp4BQ8B7AHKijl8NGUfw==",
 			"dev": true,
 			"license": "MIT",
 			"dependencies": {
-				"@typescript-eslint/types": "8.17.0",
+				"@typescript-eslint/types": "8.18.0",
 				"eslint-visitor-keys": "^4.2.0"
 			},
 			"engines": {
@@ -1987,9 +1970,9 @@
 			}
 		},
 		"node_modules/browserslist": {
-			"version": "4.24.2",
-			"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz",
-			"integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==",
+			"version": "4.24.3",
+			"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz",
+			"integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==",
 			"dev": true,
 			"funding": [
 				{
@@ -2007,9 +1990,9 @@
 			],
 			"license": "MIT",
 			"dependencies": {
-				"caniuse-lite": "^1.0.30001669",
-				"electron-to-chromium": "^1.5.41",
-				"node-releases": "^2.0.18",
+				"caniuse-lite": "^1.0.30001688",
+				"electron-to-chromium": "^1.5.73",
+				"node-releases": "^2.0.19",
 				"update-browserslist-db": "^1.1.1"
 			},
 			"bin": {
@@ -2040,9 +2023,9 @@
 			}
 		},
 		"node_modules/caniuse-lite": {
-			"version": "1.0.30001687",
-			"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001687.tgz",
-			"integrity": "sha512-0S/FDhf4ZiqrTUiQ39dKeUjYRjkv7lOZU1Dgif2rIqrTzX/1wV2hfKu9TOm1IHkdSijfLswxTFzl/cvir+SLSQ==",
+			"version": "1.0.30001689",
+			"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001689.tgz",
+			"integrity": "sha512-CmeR2VBycfa+5/jOfnp/NpWPGd06nf1XYiefUvhXFfZE4GkRc9jv+eGPS4nT558WS/8lYCzV8SlANCIPvbWP1g==",
 			"dev": true,
 			"funding": [
 				{
@@ -2254,9 +2237,9 @@
 			}
 		},
 		"node_modules/daisyui": {
-			"version": "4.12.14",
-			"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.12.14.tgz",
-			"integrity": "sha512-hA27cdBasdwd4/iEjn+aidoCrRroDuo3G5W9NDKaVCJI437Mm/3eSL/2u7MkZ0pt8a+TrYF3aT2pFVemTS3how==",
+			"version": "4.12.22",
+			"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.12.22.tgz",
+			"integrity": "sha512-HDLWbmTnXxhE1MrMgSWjVgdRt+bVYHvfNbW3GTsyIokRSqTHonUTrxV3RhpPDjGIWaHt+ELtDCTYCtUFgL2/Nw==",
 			"dev": true,
 			"license": "MIT",
 			"dependencies": {
@@ -2304,6 +2287,12 @@
 			"dev": true,
 			"license": "MIT"
 		},
+		"node_modules/decimal.js": {
+			"version": "10.4.3",
+			"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
+			"integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==",
+			"license": "MIT"
+		},
 		"node_modules/deep-is": {
 			"version": "0.1.4",
 			"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -2414,16 +2403,16 @@
 			"license": "MIT"
 		},
 		"node_modules/electron-to-chromium": {
-			"version": "1.5.71",
-			"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.71.tgz",
-			"integrity": "sha512-dB68l59BI75W1BUGVTAEJy45CEVuEGy9qPVVQ8pnHyHMn36PLPPoE1mjLH+lo9rKulO3HC2OhbACI/8tCqJBcA==",
+			"version": "1.5.73",
+			"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.73.tgz",
+			"integrity": "sha512-8wGNxG9tAG5KhGd3eeA0o6ixhiNdgr0DcHWm85XPCphwZgD1lIEoi6t3VERayWao7SF7AAZTw6oARGJeVjH8Kg==",
 			"dev": true,
 			"license": "ISC"
 		},
 		"node_modules/emoji-picker-element": {
-			"version": "1.25.0",
-			"resolved": "https://registry.npmjs.org/emoji-picker-element/-/emoji-picker-element-1.25.0.tgz",
-			"integrity": "sha512-UcUMxqIuneLCsEJ5KpqTD1xaHZyUpg6Oa7uCVe5AMXXpsW3C2TNegbNLXj2/rlbyr6qVMf7lXTFyzvFEarOIUg==",
+			"version": "1.26.0",
+			"resolved": "https://registry.npmjs.org/emoji-picker-element/-/emoji-picker-element-1.26.0.tgz",
+			"integrity": "sha512-IcffFc+LNymYScmMuxOJooZulOCOACGc1Xvj+s7XeKqpc+0EoZfWrV9o4rBjEiuM7XjsgcEjD+m5DHg0aIfnnA==",
 			"license": "Apache-2.0"
 		},
 		"node_modules/emoji-regex": {
@@ -2577,9 +2566,9 @@
 			}
 		},
 		"node_modules/eslint": {
-			"version": "9.16.0",
-			"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.16.0.tgz",
-			"integrity": "sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==",
+			"version": "9.17.0",
+			"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz",
+			"integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==",
 			"dev": true,
 			"license": "MIT",
 			"dependencies": {
@@ -2588,7 +2577,7 @@
 				"@eslint/config-array": "^0.19.0",
 				"@eslint/core": "^0.9.0",
 				"@eslint/eslintrc": "^3.2.0",
-				"@eslint/js": "9.16.0",
+				"@eslint/js": "9.17.0",
 				"@eslint/plugin-kit": "^0.2.3",
 				"@humanfs/node": "^0.16.6",
 				"@humanwhocodes/module-importer": "^1.0.1",
@@ -2597,7 +2586,7 @@
 				"@types/json-schema": "^7.0.15",
 				"ajv": "^6.12.4",
 				"chalk": "^4.0.0",
-				"cross-spawn": "^7.0.5",
+				"cross-spawn": "^7.0.6",
 				"debug": "^4.3.2",
 				"escape-string-regexp": "^4.0.0",
 				"eslint-scope": "^8.2.0",
@@ -3338,14 +3327,14 @@
 			}
 		},
 		"node_modules/intl-messageformat": {
-			"version": "10.7.7",
-			"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.7.tgz",
-			"integrity": "sha512-F134jIoeYMro/3I0h08D0Yt4N9o9pjddU/4IIxMMURqbAtI2wu70X8hvG1V48W49zXHXv3RKSF/po+0fDfsGjA==",
+			"version": "10.7.10",
+			"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.10.tgz",
+			"integrity": "sha512-hp7iejCBiJdW3zmOe18FdlJu8U/JsADSDiBPQhfdSeI8B9POtvPRvPh3nMlvhYayGMKLv6maldhR7y3Pf1vkpw==",
 			"license": "BSD-3-Clause",
 			"dependencies": {
-				"@formatjs/ecma402-abstract": "2.2.4",
-				"@formatjs/fast-memoize": "2.2.3",
-				"@formatjs/icu-messageformat-parser": "2.9.4",
+				"@formatjs/ecma402-abstract": "2.3.1",
+				"@formatjs/fast-memoize": "2.2.5",
+				"@formatjs/icu-messageformat-parser": "2.9.7",
 				"tslib": "2"
 			}
 		},
@@ -3363,9 +3352,9 @@
 			}
 		},
 		"node_modules/is-core-module": {
-			"version": "2.15.1",
-			"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
-			"integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
+			"version": "2.16.0",
+			"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.0.tgz",
+			"integrity": "sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g==",
 			"dev": true,
 			"license": "MIT",
 			"dependencies": {
@@ -3691,9 +3680,9 @@
 			}
 		},
 		"node_modules/magic-string": {
-			"version": "0.30.14",
-			"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.14.tgz",
-			"integrity": "sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==",
+			"version": "0.30.17",
+			"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
+			"integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
 			"license": "MIT",
 			"dependencies": {
 				"@jridgewell/sourcemap-codec": "^1.5.0"
@@ -3948,9 +3937,9 @@
 			"license": "ISC"
 		},
 		"node_modules/node-releases": {
-			"version": "2.0.18",
-			"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
-			"integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
+			"version": "2.0.19",
+			"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
+			"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
 			"dev": true,
 			"license": "MIT"
 		},
@@ -4469,13 +4458,13 @@
 			}
 		},
 		"node_modules/resolve": {
-			"version": "1.22.8",
-			"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
-			"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
+			"version": "1.22.9",
+			"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.9.tgz",
+			"integrity": "sha512-QxrmX1DzraFIi9PxdG5VkRfRwIgjwyud+z/iBwfRRrVmHc+P9Q7u2lSSpQ6bjr2gy5lrqIiU9vb6iAeGf2400A==",
 			"dev": true,
 			"license": "MIT",
 			"dependencies": {
-				"is-core-module": "^2.13.0",
+				"is-core-module": "^2.16.0",
 				"path-parse": "^1.0.7",
 				"supports-preserve-symlinks-flag": "^1.0.0"
 			},
@@ -4871,9 +4860,9 @@
 			}
 		},
 		"node_modules/svelte": {
-			"version": "5.8.1",
-			"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.8.1.tgz",
-			"integrity": "sha512-tqJY46Xoe+KiKvD4/guNlqpE+jco4IBcuaM6Ei9SEMETtsbLMfbak9XjTacqd6aGMmWXh7uFInfFTd4yES5r0A==",
+			"version": "5.14.0",
+			"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.14.0.tgz",
+			"integrity": "sha512-xHrS9dd2Ci9GJd2sReNFqJztoe515wB4OzsPw4A8L2M6lddLFkREkWDJnM5DAND30Zyvjwc1icQVzH0F+Sdx5A==",
 			"license": "MIT",
 			"dependencies": {
 				"@ampproject/remapping": "^2.3.0",
diff --git a/frontend/src/lang/en.json b/frontend/src/lang/en.json
index ff996950da42564b2d1fdbb7ac0993ce7609e306..b1c77682359fd8361c20850283b4b09beceb883c 100644
--- a/frontend/src/lang/en.json
+++ b/frontend/src/lang/en.json
@@ -160,7 +160,8 @@
 		"login": "Log in",
 		"save": "Save",
 		"update": "Update",
-		"updated": "Updated!"
+		"updated": "Updated!",
+		"continue": "Continue"
 	},
 	"surveys": {
 		"complete": "Thank you for participating!",
@@ -285,6 +286,7 @@
 		"startButton": "To start",
 		"startFastButton": "Start using the app directly",
 		"tab": {
+			"study": "Study",
 			"availabilities": "Availability",
 			"consent": "Consent",
 			"continue": "Continue",
diff --git a/frontend/src/lang/fr.json b/frontend/src/lang/fr.json
index de6627901980686a039465826cb3c763e6535297..8c8701e4624f000b43f0d9de04e0b1164598cb58 100644
--- a/frontend/src/lang/fr.json
+++ b/frontend/src/lang/fr.json
@@ -11,7 +11,8 @@
 		"availability": "Disponibilités",
 		"admin": {
 			"users": "Utilisateurs",
-			"sessions": "Sessions"
+			"sessions": "Sessions",
+			"studies": "Études"
 		}
 	},
 	"chatbox": {
@@ -130,6 +131,7 @@
 		"humans": "Je ne suis pas un robot",
 		"signup": "S'inscrire",
 		"tab": {
+			"study": "Étude",
 			"consent": "Consentement",
 			"signup": "Inscription",
 			"information": "Informations",
@@ -220,7 +222,10 @@
 		"continueButton": "Continuer vers les tests",
 		"startFastButton": "Commencer directement à utiliser l'application",
 		"start": "Aut sint sint et nihil aut. Quia nihil eos rerum neque exercitationem molestiae. Aut ab accusantium consequatur rerum architecto voluptas. Repudiandae minima nemo vitae tempore. Molestiae rerum aliquid ut fugit eligendi. Alias dolorum quia voluptatum veniam harum aut qui et. Voluptatibus adipisci illo velit assumenda. Asperiores accusamus deserunt eveniet adipisci reprehenderit. Ducimus placeat sit reprehenderit ea eos quam. Ut facilis quia suscipit officiis odit unde temporibus error. Neque sapiente ut similique. Eius ut sapiente maxime dolor est voluptatem eum. Veniam aut sit quo. Quibusdam sed numquam et rem. Tempore eum id nobis sunt deleniti et non ducimus. Maiores perferendis non consequuntur neque nostrum. Odio itaque fugiat non. Laboriosam sint voluptatem aut placeat et perferendis. Sed quam voluptatem necessitatibus quia dolorum. Eius est nihil natus modi natus quisquam ut impedit. Tempore enim autem laboriosam sequi ipsum quo. <bold class=\"font-bold\">Aut voluptatum debitis et aliquam vel rerum facere.</bold>",
-		"startButton": "Commencer"
+		"startButton": "Commencer",
+		"study": "Étude",
+		"study.note": "Nom de l'étude à laquelle vous participez",
+		"study.placeholder": "Nom de l'étude"
 	},
 	"timeslots": {
 		"cesttime": "Heure CET (Bruxelles)",
@@ -305,6 +310,32 @@
 		},
 		"availability": "Disponibilités"
 	},
+	"studies": {
+		"study": "Étude",
+		"deleteConfirm": "Êtes-vous sûr de vouloir supprimer cette étude ? Cette action est irréversible.",
+		"startDate": "Date de début",
+		"endDate": "Date de fin",
+		"chatDuration": "Durée des sessions (en secondes)",
+		"updated": "Étude mise à jour avec succès",
+		"noChanges": "Aucune modification",
+		"updateError": "Erreur lors de la mise à jour de l'étude",
+		"create": "Créer une nouvelle étude",
+		"createMissing": "Veuillez remplir tous les champs",
+		"created": "Étude créée avec succès",
+		"createError": "Erreur lors de la création de l'étude",
+		"addUserButton": "Ajouter un participant",
+		"newUser": "Nouveau participant",
+		"addUserError": "Erreur lors de l'ajout du participant",
+		"removeUserConfirm": "Êtes-vous sûr de vouloir retirer ce participant de l'étude ?",
+		"removeUserSuccess": "Participant retiré de l'étude",
+		"removeUserError": "Erreur lors de la suppression du participant",
+		"invalidEmail": "Adresse e-mail invalide",
+		"userNotFound": "Utilisateur non trouvé",
+		"addUserError": "Erreur lors de l'ajout de l'utilisateur",
+		"addUserSuccess": "Utilisateur ajouté à l'étude",
+		"deleteConfirm": "Êtes-vous sûr de vouloir supprimer cette étude ? Cette action est irréversible.",
+		"createTitle": "Créer une nouvelle étude"
+	},
 	"button": {
 		"create": "Créer",
 		"submit": "Envoyer",
@@ -318,7 +349,10 @@
 		"close": "Fermer",
 		"tryit": "Essayer",
 		"update": "Mettre à jour",
-		"updated": "Mis à jour !"
+		"updated": "Mis à jour !",
+		"delete": "Supprimer",
+		"remove": "Retirer",
+		"continue": "Continuer"
 	},
 	"utils": {
 		"month": {
@@ -393,7 +427,11 @@
 			"programed": "Programmée",
 			"inProgress": "En cours",
 			"finished": "Terminée",
-			"topics": "Topics"
+			"topics": "Topics",
+			"title": "Titre",
+			"users": "Utilisateurs",
+			"description": "Description",
+			"email": "E-mail"
 		}
 	},
 	"inputs": {
diff --git a/frontend/src/lib/api/studies.ts b/frontend/src/lib/api/studies.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6616c897a752c2b4cafc35d5a272bdeff9303e08
--- /dev/null
+++ b/frontend/src/lib/api/studies.ts
@@ -0,0 +1,79 @@
+import { formatToUTCDate } from '$lib/utils/date';
+import type { fetchType } from '$lib/utils/types';
+
+export async function getStudiesAPI(fetch: fetchType): Promise<any[]> {
+	const response = await fetch('/api/studies');
+	if (!response.ok) return [];
+	return await response.json();
+}
+
+export async function getStudyAPI(fetch: fetchType, id: number): Promise<any | undefined> {
+	const response = await fetch(`/api/studies/${id}`);
+	if (!response.ok) return;
+	return await response.json();
+}
+
+export async function deleteStudyAPI(fetch: fetchType, id: number): Promise<boolean> {
+	const response = await fetch(`/api/studies/${id}`, {
+		method: 'DELETE'
+	});
+	return response.ok;
+}
+
+export async function createStudyAPI(
+	fetch: fetchType,
+	title: string,
+	description: string,
+	startDate: Date,
+	endDate: Date,
+	chatDuration: number
+): Promise<number | null> {
+	const response = await fetch('/api/studies', {
+		method: 'POST',
+		headers: { 'Content-Type': 'application/json' },
+		body: JSON.stringify({
+			title,
+			description,
+			start_date: formatToUTCDate(startDate),
+			end_date: formatToUTCDate(endDate),
+			chat_duration: chatDuration
+		})
+	});
+	if (!response.ok) return null;
+	return parseInt(await response.text());
+}
+
+export async function patchStudyAPI(
+	fetch: fetchType,
+	study_id: number,
+	data: any
+): Promise<boolean> {
+	const response = await fetch(`/api/studies/${study_id}`, {
+		method: 'PATCH',
+		headers: { 'Content-Type': 'application/json' },
+		body: JSON.stringify(data)
+	});
+	return response.ok;
+}
+
+export async function addUserToStudyAPI(
+	fetch: fetchType,
+	study_id: number,
+	user_id: number
+): Promise<boolean> {
+	const response = await fetch(`/api/studies/${study_id}/users/${user_id}`, {
+		method: 'POST'
+	});
+	return response.ok;
+}
+
+export async function removeUserToStudyAPI(
+	fetch: fetchType,
+	study_id: number,
+	user_id: number
+): Promise<boolean> {
+	const response = await fetch(`/api/studies/${study_id}/users/${user_id}`, {
+		method: 'DELETE'
+	});
+	return response.ok;
+}
diff --git a/frontend/src/lib/api/users.ts b/frontend/src/lib/api/users.ts
index 19f124d7787d07e724092ebe50159898a7f00aad..300bdb0beb03c98701ed6952a8bdb9ea974166c7 100644
--- a/frontend/src/lib/api/users.ts
+++ b/frontend/src/lib/api/users.ts
@@ -14,6 +14,12 @@ export async function getUserAPI(fetch: fetchType, user_id: number): Promise<any
 	return await response.json();
 }
 
+export async function getUserByEmailAPI(fetch: fetchType, email: string) {
+	const response = await fetch(`/api/users/by-email/${email}`);
+	if (!response.ok) return null;
+	return await response.json();
+}
+
 export async function createUserContactAPI(
 	fetch: fetchType,
 	user_id: number,
@@ -21,7 +27,6 @@ export async function createUserContactAPI(
 ): Promise<any | null> {
 	const response = await fetch(`/api/users/${user_id}/contacts/${contact_id}`);
 	if (!response.ok) return null;
-
 	return await response.json();
 }
 
diff --git a/frontend/src/lib/components/utils/dateInput.svelte b/frontend/src/lib/components/utils/dateInput.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..8a3cd51631fb9c5b082c3fe9b72a4dca24f3afcd
--- /dev/null
+++ b/frontend/src/lib/components/utils/dateInput.svelte
@@ -0,0 +1,23 @@
+<!--
+source: https://svelte.dev/playground/dc963bbead384b69aad17824149d6d27?version=3.25.1
+-->
+
+<script lang="ts">
+	import dayjs from 'dayjs';
+
+	export let format = 'YYYY-MM-DD';
+	export let date: Date | null;
+	let class_: string | undefined;
+	export { class_ as class };
+	export let id: string | undefined;
+
+	let internal: string | null = null;
+
+	const input = (x) => (internal = x ? dayjs(x).format(format) : null);
+	const output = (x) => (date = x ? dayjs(x, format).toDate() : null);
+
+	$: input(date);
+	$: output(internal);
+</script>
+
+<input type="date" bind:value={internal} {id} class={class_} />
diff --git a/frontend/src/lib/types/study.ts b/frontend/src/lib/types/study.ts
new file mode 100644
index 0000000000000000000000000000000000000000..dd4353d3eed56d2028e2d38af8c98d7d79a03866
--- /dev/null
+++ b/frontend/src/lib/types/study.ts
@@ -0,0 +1,158 @@
+import {
+	addUserToStudyAPI,
+	createStudyAPI,
+	deleteStudyAPI,
+	patchStudyAPI,
+	removeUserToStudyAPI
+} from '$lib/api/studies';
+import { parseToLocalDate } from '$lib/utils/date';
+import { toastAlert } from '$lib/utils/toasts';
+import type { fetchType } from '$lib/utils/types';
+import User from './user';
+
+export default class Study {
+	private _id: number;
+	private _title: string;
+	private _description: string;
+	private _startDate: Date;
+	private _endDate: Date;
+	private _chatDuration: number;
+	private _users: User[];
+
+	private constructor(
+		id: number,
+		title: string,
+		description: string,
+		startDate: Date,
+		endDate: Date,
+		chatDuration: number,
+		users: User[]
+	) {
+		this._id = id;
+		this._title = title;
+		this._description = description;
+		this._startDate = startDate;
+		this._endDate = endDate;
+		this._chatDuration = chatDuration;
+		this._users = users;
+	}
+
+	get id(): number {
+		return this._id;
+	}
+
+	get title(): string {
+		return this._title;
+	}
+
+	get description(): string {
+		return this._description;
+	}
+
+	get startDate(): Date {
+		return this._startDate;
+	}
+
+	get endDate(): Date {
+		return this._endDate;
+	}
+
+	get chatDuration(): number {
+		return this._chatDuration;
+	}
+
+	get users(): User[] {
+		return this._users;
+	}
+
+	get numberOfUsers(): number {
+		return this._users.length;
+	}
+
+	static async create(
+		title: string,
+		description: string,
+		startDate: Date,
+		endDate: Date,
+		chatDuration: number,
+		f: fetchType = fetch
+	): Promise<Study | null> {
+		const id = await createStudyAPI(f, title, description, startDate, endDate, chatDuration);
+
+		if (id) {
+			return new Study(id, title, description, startDate, endDate, chatDuration, []);
+		}
+		return null;
+	}
+
+	async delete(f: fetchType = fetch): Promise<void> {
+		await deleteStudyAPI(f, this._id);
+	}
+
+	async patch(data: any, f: fetchType = fetch): Promise<boolean> {
+		const res = await patchStudyAPI(f, this._id, data);
+		if (res) {
+			if (data.title) this._title = data.title;
+			if (data.description) this._description = data.description;
+			if (data.start_date) this._startDate = parseToLocalDate(data.start_date);
+			if (data.end_date) this._endDate = parseToLocalDate(data.end_date);
+			if (data.chat_duration) this._chatDuration = data.chat_duration;
+			return true;
+		}
+		return false;
+	}
+
+	async removeUser(user: User, f: fetchType = fetch): Promise<boolean> {
+		const res = await removeUserToStudyAPI(f, this._id, user.id);
+		if (res) {
+			this._users = this._users.filter((u) => u.id !== user.id);
+		}
+
+		return res;
+	}
+
+	async addUser(user: User, f: fetchType = fetch): Promise<boolean> {
+		const res = await addUserToStudyAPI(f, this._id, user.id);
+		if (res) {
+			this._users.push(user);
+		}
+
+		return res;
+	}
+
+	static parse(json: any): Study | null {
+		if (json === null || json === undefined) {
+			toastAlert('Failed to parse study: json is null');
+			return null;
+		}
+
+		const study = new Study(
+			json.id,
+			json.title,
+			json.description,
+			parseToLocalDate(json.start_date),
+			parseToLocalDate(json.end_date),
+			json.chat_duration,
+			[]
+		);
+
+		study._users = User.parseAll(json.users);
+
+		return study;
+	}
+
+	static parseAll(json: any): Study[] {
+		if (json === null || json === undefined) {
+			toastAlert('Failed to parse studies: json is null');
+			return [];
+		}
+		const studies: Study[] = [];
+		for (const studyJson of json) {
+			const study = Study.parse(studyJson);
+			if (study) {
+				studies.push(study);
+			}
+		}
+		return studies;
+	}
+}
diff --git a/frontend/src/lib/utils/date.ts b/frontend/src/lib/utils/date.ts
index c276e2d2c6aa0bd8de3073db92eadffc851b9d0d..9d30f83eb97b40cb1f63de01323ff6e72da81206 100644
--- a/frontend/src/lib/utils/date.ts
+++ b/frontend/src/lib/utils/date.ts
@@ -68,25 +68,10 @@ export function displayDate(date: Date): string {
 
 	const now = new Date();
 
-	const hours = date.getHours().toString();
-	const minutes = date.getMinutes().toString().padStart(2, '0');
-
-	if (now.getDate() === date.getDate()) {
-		return hours + ':' + minutes;
-	} else if (now.getFullYear() === date.getFullYear()) {
-		return date.getDate() + ' ' + getFullMonth(date.getMonth()) + ' ' + hours + ':' + minutes;
+	if (now.getFullYear() === date.getFullYear()) {
+		return date.getDate() + ' ' + getFullMonth(date.getMonth());
 	} else {
-		return (
-			date.getDate() +
-			' ' +
-			getFullMonth(date.getMonth()) +
-			' ' +
-			date.getFullYear() +
-			' ' +
-			hours +
-			':' +
-			minutes
-		);
+		return date.getDate() + ' ' + getFullMonth(date.getMonth()) + ' ' + date.getFullYear();
 	}
 }
 
diff --git a/frontend/src/routes/Header.svelte b/frontend/src/routes/Header.svelte
index 2860508ee6c8087caf91af12e86e2dd2ca6811aa..876faf2f741150f97c9aaa4e5e0bb02f82717092 100644
--- a/frontend/src/routes/Header.svelte
+++ b/frontend/src/routes/Header.svelte
@@ -79,7 +79,7 @@
 							</summary>
 							<ul class="menu menu-sm dropdown-content absolute right-0 z-10">
 								<li>
-									<a data-sveltekit-reload href="/admin">
+									<a data-sveltekit-reload href="/admin/users">
 										{$t('header.admin.users')}
 									</a>
 								</li>
@@ -88,6 +88,11 @@
 										{$t('header.admin.sessions')}
 									</a>
 								</li>
+								<li>
+									<a data-sveltekit-reload href="/admin/studies">
+										{$t('header.admin.studies')}
+									</a>
+								</li>
 							</ul>
 						</details>
 					</li>
diff --git a/frontend/src/routes/admin/studies/+page.svelte b/frontend/src/routes/admin/studies/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..56920d9dfb3c47abf0c39f1b43d2c58585cf3e35
--- /dev/null
+++ b/frontend/src/routes/admin/studies/+page.svelte
@@ -0,0 +1,314 @@
+<script lang="ts">
+	import { t } from '$lib/services/i18n';
+	import Study from '$lib/types/study';
+	import { displayDate, formatToUTCDate } from '$lib/utils/date';
+	import autosize from 'svelte-autosize';
+	import DateInput from '$lib/components/utils/dateInput.svelte';
+	import { toastAlert, toastSuccess, toastWarning } from '$lib/utils/toasts';
+	import User from '$lib/types/user';
+	import { Icon, MagnifyingGlass } from 'svelte-hero-icons';
+	import { getUserByEmailAPI } from '$lib/api/users';
+	import type { PageData } from './$types';
+
+	const { data }: { data: PageData } = $props();
+
+	let studies: Study[] = $state(data.studies);
+	let selectedStudy: Study | null = $state(null);
+	let title: string | null = $state(null);
+	let description: string | null = $state(null);
+	let startDate: Date | null = $state(null);
+	let endDate: Date | null = $state(null);
+	let chatDuration: number | null = $state(null);
+	let typingTest: boolean = $state(true);
+
+	let studyCreate: boolean = $state(false);
+
+	function selectStudy(study: Study | null) {
+		selectedStudy = study;
+
+		title = study?.title ?? null;
+		description = study?.description ?? null;
+		startDate = study?.startDate ?? null;
+		endDate = study?.endDate ?? null;
+		chatDuration = study?.chatDuration ?? null;
+	}
+
+	async function studyUpdate() {
+		if (
+			selectedStudy === null ||
+			title === null ||
+			description === null ||
+			startDate === null ||
+			endDate === null ||
+			chatDuration === null ||
+			title === '' ||
+			description === '' ||
+			(title === selectedStudy.title &&
+				description === selectedStudy.description &&
+				startDate.getDay() === selectedStudy.startDate.getDay() &&
+				startDate.getMonth() === selectedStudy.startDate.getMonth() &&
+				startDate.getFullYear() === selectedStudy.startDate.getFullYear() &&
+				endDate.getDay() === selectedStudy.endDate.getDay() &&
+				endDate.getMonth() === selectedStudy.endDate.getMonth() &&
+				endDate.getFullYear() === selectedStudy.endDate.getFullYear() &&
+				chatDuration === selectedStudy.chatDuration)
+		) {
+			selectStudy(null);
+			toastSuccess($t('studies.noChanges'));
+			return;
+		}
+
+		const result = await selectedStudy.patch({
+			title,
+			description,
+			start_date: formatToUTCDate(startDate),
+			end_date: formatToUTCDate(endDate),
+			chatDuration
+		});
+
+		if (result) {
+			selectStudy(null);
+			toastSuccess($t('studies.updated'));
+		} else {
+			toastAlert($t('studies.updateError'));
+		}
+	}
+
+	async function createStudy() {
+		if (
+			title === null ||
+			description === null ||
+			startDate === null ||
+			endDate === null ||
+			chatDuration === null ||
+			title === '' ||
+			description === ''
+		) {
+			toastAlert($t('studies.createMissing'));
+			return;
+		}
+
+		const study = await Study.create(title, description, startDate, endDate, chatDuration);
+
+		if (study) {
+			toastSuccess($t('studies.created'));
+			studyCreate = false;
+			studies.push(study);
+		} else {
+			toastAlert($t('studies.createError'));
+		}
+	}
+
+	async function deleteStudy() {
+		if (!selectedStudy) return;
+
+		studies.splice(studies.indexOf(selectedStudy), 1);
+		selectedStudy?.delete();
+		selectStudy(null);
+	}
+
+	async function removeUser(user: User) {
+		if (selectedStudy === null) return;
+		if (!confirm($t('studies.removeUserConfirm'))) return;
+
+		const res = await selectedStudy.removeUser(user);
+
+		if (res) {
+			toastSuccess($t('studies.removeUserSuccess'));
+			selectStudy(null);
+		} else {
+			toastAlert($t('studies.removeUserError'));
+		}
+	}
+
+	let newUsername: string = $state('');
+	let newUserModal = $state(false);
+
+	async function addUser() {
+		if (selectedStudy === null) return;
+		newUserModal = true;
+	}
+
+	async function searchUser() {
+		if (selectedStudy === null) return;
+		if (!newUsername || !newUsername.includes('@')) {
+			toastWarning($t('studies.invalidEmail'));
+			return;
+		}
+
+		const userData = await getUserByEmailAPI(fetch, newUsername);
+
+		if (!userData) {
+			toastWarning($t('studies.userNotFound'));
+			return;
+		}
+
+		const user = User.parse(userData);
+
+		if (!user) {
+			toastAlert($t('studies.userNotFound'));
+			return;
+		}
+
+		const res = await selectedStudy.addUser(user);
+
+		if (!res) {
+			toastAlert($t('studies.addUserError'));
+			return;
+		}
+
+		newUsername = '';
+		newUserModal = false;
+		toastSuccess($t('studies.addUserSuccess'));
+		selectStudy(null);
+	}
+</script>
+
+<h1 class="text-xl font-bold m-5 text-center">{$t('header.admin.studies')}</h1>
+
+<table class="table max-w-5xl mx-auto text-center">
+	<thead>
+		<tr>
+			<th>#</th>
+			<th>{$t('utils.words.date')}</th>
+			<th>{$t('utils.words.title')}</th>
+			<th># {$t('utils.words.users')}</th>
+		</tr>
+	</thead>
+	<tbody>
+		{#each studies as study (study.id)}
+			<tr class="hover:bg-gray-100 hover:cursor-pointer" onclick={() => selectStudy(study)}>
+				<td>{study.id}</td>
+				<td>{displayDate(study.startDate)} - {displayDate(study.endDate)}</td>
+				<td>{study.title}</td>
+				<td>{study.numberOfUsers}</td>
+				<td></td>
+			</tr>
+		{/each}
+	</tbody>
+</table>
+<div class="mt-8 w-[64rem] mx-auto">
+	<button class="button" onclick={() => (studyCreate = true)}>{$t('studies.create')}</button>
+</div>
+
+<dialog class="modal bg-black bg-opacity-50" open={selectedStudy != null}>
+	<div class="modal-box max-w-4xl">
+		<h2 class="text-xl font-bold m-5 text-center">{$t('studies.study')} #{selectedStudy?.id}</h2>
+		<form class="mb-4">
+			<label class="label" for="title">{$t('utils.words.title')}</label>
+			<input class="input w-full" type="text" id="title" bind:value={title} />
+			<label class="label" for="description">{$t('utils.words.description')}</label>
+			<textarea use:autosize class="input w-full max-h-52" id="title" bind:value={description}>
+			</textarea>
+			<label class="label" for="startDate">{$t('studies.startDate')}</label>
+			<DateInput class="input w-full" id="startDate" bind:date={startDate} />
+			<label class="label" for="endDate">{$t('studies.endDate')}</label>
+			<DateInput class="input w-full" id="endDate" bind:date={endDate} />
+			<label class="label" for="chatDuration">{$t('studies.chatDuration')}</label>
+			<input
+				class="input w-full"
+				type="number"
+				id="chatDuration"
+				bind:value={chatDuration}
+				min="0"
+			/>
+			<label class="label" for="users">{$t('utils.words.users')}</label>
+			<table class="table">
+				<thead>
+					<tr>
+						<td>#</td>
+						<td>{$t('users.category')}</td>
+						<td>{$t('users.nickname')}</td>
+						<td>{$t('users.email')}</td>
+						<td></td>
+					</tr>
+				</thead>
+				<tbody>
+					{#each selectedStudy?.users ?? [] as user (user.id)}
+						<tr>
+							<td>{user.id}</td>
+							<td>{$t('users.type.' + user.type)}</td>
+							<td>{user.nickname}</td>
+							<td>{user.email}</td>
+							<td>
+								<button class="btn btn-sm btn-error text-white" onclick={() => removeUser(user)}>
+									{$t('button.remove')}
+								</button>
+							</td>
+						</tr>
+					{/each}
+				</tbody>
+			</table>
+			<button class="btn btn-primary block mx-auto" onclick={addUser}>
+				{$t('studies.addUserButton')}
+			</button>
+		</form>
+		<div class="mt-4">
+			<button class="button" onclick={studyUpdate}>{$t('button.update')}</button>
+			<button class="btn btn-outline float-end ml-2" onclick={() => selectStudy(null)}>
+				{$t('button.cancel')}
+			</button>
+			<button
+				class="btn btn-error btn-outline float-end"
+				onclick={() => confirm($t('studies.deleteConfirm')) && deleteStudy()}
+			>
+				{$t('button.delete')}
+			</button>
+		</div>
+	</div>
+</dialog>
+
+<dialog class="modal bg-black bg-opacity-50" open={studyCreate}>
+	<div class="modal-box max-w-4xl">
+		<h2 class="text-xl font-bold m-5 text-center">{$t('studies.createTitle')}</h2>
+		<form>
+			<label class="label" for="title">{$t('utils.words.title')} *</label>
+			<input class="input w-full" type="text" id="title" bind:value={title} />
+			<label class="label" for="description">{$t('utils.words.description')} *</label>
+			<textarea use:autosize class="input w-full max-h-52" id="title" bind:value={description}>
+			</textarea>
+			<label class="label" for="startDate">{$t('studies.startDate')} *</label>
+			<DateInput class="input w-full" id="startDate" bind:date={startDate} />
+			<label class="label" for="endDate">{$t('studies.endDate')} *</label>
+			<DateInput class="input w-full" id="endDate" bind:date={endDate} />
+			<label class="label" for="chatDuration">{$t('studies.chatDuration')} *</label>
+			<input
+				class="input w-full"
+				type="number"
+				id="chatDuration"
+				bind:value={chatDuration}
+				min="0"
+			/>
+			<label class="label" for="typingTest">{$t('studies.typingTest')} *</label>
+			<input type="checkbox" class="input" id="typingTest" bind:checked={typingTest} />
+		</form>
+		<div class="mt-4">
+			<button class="button" onclick={createStudy}>{$t('button.create')}</button>
+			<button
+				class="btn btn-outline float-end ml-2"
+				onclick={() => (studyCreate = false && selectStudy(null))}
+			>
+				{$t('button.cancel')}
+			</button>
+		</div>
+	</div>
+</dialog>
+
+<dialog class="modal bg-black bg-opacity-50" open={newUserModal}>
+	<div class="modal-box">
+		<h2 class="text-xl font-bold mb-4">{$t('studies.newUser')}</h2>
+		<div class="w-full flex">
+			<input
+				type="text"
+				placeholder={$t('utils.words.email')}
+				bind:value={newUsername}
+				class="input flex-grow mr-2"
+				onkeypress={(e) => e.key === 'Enter' && searchUser()}
+			/>
+			<button class="button w-16" onclick={searchUser}>
+				<Icon src={MagnifyingGlass} />
+			</button>
+		</div>
+		<button class="btn float-end mt-4" onclick={() => (newUserModal = false)}>Close</button>
+	</div>
+</dialog>
diff --git a/frontend/src/routes/admin/studies/+page.ts b/frontend/src/routes/admin/studies/+page.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f4043711bee65e230b7b3fee7fa244006d43629d
--- /dev/null
+++ b/frontend/src/routes/admin/studies/+page.ts
@@ -0,0 +1,11 @@
+import { getStudiesAPI } from '$lib/api/studies';
+import Study from '$lib/types/study';
+import { type Load } from '@sveltejs/kit';
+
+export const load: Load = async ({ fetch }) => {
+	const studies = Study.parseAll(await getStudiesAPI(fetch));
+
+	return {
+		studies
+	};
+};
diff --git a/frontend/src/routes/admin/+page.svelte b/frontend/src/routes/admin/users/+page.svelte
similarity index 100%
rename from frontend/src/routes/admin/+page.svelte
rename to frontend/src/routes/admin/users/+page.svelte
diff --git a/frontend/src/routes/admin/+page.ts b/frontend/src/routes/admin/users/+page.ts
similarity index 100%
rename from frontend/src/routes/admin/+page.ts
rename to frontend/src/routes/admin/users/+page.ts
diff --git a/frontend/src/routes/register/+page.server.ts b/frontend/src/routes/register/[[studyId]]/+page.server.ts
similarity index 79%
rename from frontend/src/routes/register/+page.server.ts
rename to frontend/src/routes/register/[[studyId]]/+page.server.ts
index 12e004dbfd1149fc827697a68fbdc2a12e403a60..b4e4ee040b4d3d0d2fc11bb81e9c37c54b3e3e3a 100644
--- a/frontend/src/routes/register/+page.server.ts
+++ b/frontend/src/routes/register/[[studyId]]/+page.server.ts
@@ -1,11 +1,14 @@
+import { addUserToStudyAPI } from '$lib/api/studies';
 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 }) => {
+	register: async ({ request, fetch, params }) => {
 		const formData = await request.formData();
+		const studyId = params.studyId;
+		if (!studyId) return { message: 'Invalid request' };
 
 		const email = formData.get('email');
 		const nickname = formData.get('nickname');
@@ -47,7 +50,7 @@ export const actions: Actions = {
 		if (response.status === 422) return { message: 'Invalid request' };
 		if (!response.ok) return { message: 'Unknown error occurred' };
 
-		return redirect(303, '/register');
+		return redirect(303, `/register/${studyId}`);
 	},
 	data: async ({ request, fetch, locals }) => {
 		if (!locals.user) {
@@ -60,8 +63,9 @@ export const actions: Actions = {
 		const targetLanguage = formData.get('targetLanguage');
 		const birthyear = formData.get('birthyear');
 		const gender = formData.get('gender');
+		const study = formData.get('study');
 
-		if (!homeLanguage || !targetLanguage || !birthyear || !gender) {
+		if (!homeLanguage || !targetLanguage || !birthyear || !gender || !study) {
 			return { message: 'Invalid request' };
 		}
 
@@ -72,7 +76,14 @@ export const actions: Actions = {
 			return { message: 'Invalid request' };
 		}
 
-		const response = await patchUserAPI(fetch, locals.user.id, {
+		let studyId;
+		try {
+			studyId = parseInt(study.toString());
+		} catch (e) {
+			return { message: 'Invalid request' };
+		}
+
+		let response = await patchUserAPI(fetch, locals.user.id, {
 			home_language: homeLanguage,
 			target_language: targetLanguage,
 			gender,
@@ -80,6 +91,9 @@ export const actions: Actions = {
 		});
 		if (!response) return { message: 'Unknown error occurred' };
 
+		response = await addUserToStudyAPI(fetch, studyId, locals.user.id);
+		if (!response) return { message: 'Failed to add user to study' };
+
 		redirect(303, '/register');
 	}
 };
diff --git a/frontend/src/routes/register/+page.svelte b/frontend/src/routes/register/[[studyId]]/+page.svelte
similarity index 82%
rename from frontend/src/routes/register/+page.svelte
rename to frontend/src/routes/register/[[studyId]]/+page.svelte
index 431a5ce766442936c49d1eacec23812674cb61dd..199555a35bf121ea7e3570def930fd87a15b78c6 100644
--- a/frontend/src/routes/register/+page.svelte
+++ b/frontend/src/routes/register/[[studyId]]/+page.svelte
@@ -3,63 +3,66 @@
 	import { t } from '$lib/services/i18n';
 	import { Icon, Envelope, Key, UserCircle } from 'svelte-hero-icons';
 	import Typingtest from '$lib/components/tests/typingtest.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';
+	import type Study from '$lib/types/study';
+	import { displayDate } from '$lib/utils/date';
 
 	let { data, form }: { data: PageData; form: FormData } = $props();
+	let study: Study | undefined = $state(data.study);
+	let studies: Study[] | undefined = $state(data.studies);
 	let user = $state(data.user);
 	let message = $state('');
 
+	let selectedStudy: Study | undefined = $state();
+
 	let current_step = $state(
 		(() => {
 			if (user == null) {
-				if (form?.message) return 2;
+				if (form?.message) return 3;
+				if (study) return 2;
 				return 1;
 			} else if (!user.home_language || !user.target_language || !user.birthdate || !user.gender) {
-				return 3;
+				return 4;
 			} else {
-				return 5;
+				return 6;
 			}
 		})()
 	);
-
-	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;
-	})();
 </script>
 
 <div class="header mx-auto my-5">
 	<ul class="steps text-xs">
 		<li class="step" class:step-primary={current_step >= 1}>
-			{#if current_step >= 1 && current_step <= 2}
-				<button onclick={() => (current_step = 1)}>
+			<a href="/register" data-sveltekit-reload class:btn-disabled={current_step > 3}
+				>{$t('register.tab.study')}</a
+			>
+		</li>
+		<li class="step" class:step-primary={current_step >= 2}>
+			{#if current_step >= 2 && current_step <= 3}
+				<button onclick={() => (current_step = 2)}>
 					{$t('register.tab.consent')}
 				</button>
 			{:else}
 				{$t('register.tab.consent')}
 			{/if}
 		</li>
-		<li class="step" class:step-primary={current_step >= 2}>
+		<li class="step" class:step-primary={current_step >= 3}>
 			{$t('register.tab.signup')}
 		</li>
-		<li class="step" class:step-primary={current_step >= 3}>
+		<li class="step" class:step-primary={current_step >= 4}>
 			{$t('register.tab.information')}
 		</li>
-		<li class="step" class:step-primary={current_step >= 4}>
+		<li class="step" class:step-primary={current_step >= 5}>
 			{$t('register.tab.timeslots')}
 		</li>
-		<li class="step" class:step-primary={current_step >= 5} data-content="?">
+		<li class="step" class:step-primary={current_step >= 6} data-content="?">
 			{$t('register.tab.continue')}
 		</li>
-		<li class="step" class:step-primary={current_step >= 6} data-content="">
+		<li class="step" class:step-primary={current_step >= 7} data-content="">
 			{$t('register.tab.test')}
 		</li>
-		<li class="step" class:step-primary={current_step >= 7} data-content="★">
+		<li class="step" class:step-primary={current_step >= 8} data-content="★">
 			{$t('register.tab.start')}
 		</li>
 	</ul>
@@ -76,6 +79,46 @@
 		</div>
 	{/if}
 	{#if current_step == 1}
+		<div class="form-control">
+			<label for="study" class="label">
+				<span class="label-text">{$t('register.study')}</span>
+				<span class="label-text-alt">{$t('register.study.note')}</span>
+			</label>
+			<select
+				class="select select-bordered"
+				id="study"
+				name="study"
+				required
+				disabled={!!study}
+				bind:value={selectedStudy}
+			>
+				{#if study}
+					<option selected value={study}>
+						{study.title} ({displayDate(study.startDate)} - {displayDate(study.endDate)})
+					</option>
+				{:else if studies}
+					<option disabled selected value="">{$t('register.study.placeholder')}</option>
+					{#each studies as s}
+						<option value={s}>
+							{s.title} ({displayDate(s.startDate)} - {displayDate(s.endDate)})
+						</option>
+					{/each}
+				{:else}
+					<option disabled></option>
+				{/if}
+			</select>
+		</div>
+		<div class="form-control">
+			<a
+				class="button mt-8"
+				class:btn-disabled={!selectedStudy}
+				href="/register/{selectedStudy?.id}"
+				data-sveltekit-reload
+			>
+				{$t('button.continue')}
+			</a>
+		</div>
+	{:else if current_step == 2}
 		<Consent
 			introText={$t('register.consent.intro')}
 			participation={$t('register.consent.participation')}
@@ -89,7 +132,7 @@
 				{$t('register.consent.ok')}
 			</button>
 		</div>
-	{:else if current_step == 2}
+	{:else if current_step == 3}
 		<div class="space-y-2">
 			<form method="POST" action="?/register">
 				<label for="email" class="form-control">
@@ -157,7 +200,7 @@
 				</div>
 			</form>
 		</div>
-	{:else if current_step == 3}
+	{:else if current_step == 4}
 		<form class="space-y-2" method="POST" action="?/data">
 			<div class="p-5 text-sm text-prose">
 				{@html $t('register.welcome')}
@@ -227,14 +270,15 @@
 					</label>
 				</div>
 			</div>
+			<input type="hidden" id="study" name="study" value={study?.id} />
 			<div class="form-control">
 				<button class="button mt-4">{$t('button.submit')}</button>
 			</div>
 		</form>
-	{:else if current_step == 4}
+	{:else if current_step == 5}
 		<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}
+	{:else if current_step == 6}
 		<div class="text-center">
 			<p class="text-center">
 				{@html $t('register.continue')}
@@ -246,11 +290,11 @@
 				{$t('register.startFastButton')}
 			</button>
 		</div>
-	{:else if current_step == 6}
+	{:else if current_step == 7}
 		{#if user}
 			<Typingtest onFinish={() => current_step++} {user} />
 		{/if}
-	{:else if current_step == 7}
+	{:else if current_step == 8}
 		<div class="text-center">
 			<p class="text-center">
 				{@html $t('register.start')}
diff --git a/frontend/src/routes/register/[[studyId]]/+page.ts b/frontend/src/routes/register/[[studyId]]/+page.ts
new file mode 100644
index 0000000000000000000000000000000000000000..60dfddf01dd9b80439997c2b968964a94b3afe37
--- /dev/null
+++ b/frontend/src/routes/register/[[studyId]]/+page.ts
@@ -0,0 +1,27 @@
+import { getStudiesAPI, getStudyAPI } from '$lib/api/studies';
+import Study from '$lib/types/study';
+import type { Load } from '@sveltejs/kit';
+
+export const load: Load = async ({ parent, fetch, params }) => {
+	const { user } = await parent();
+
+	const sStudyId: string | undefined = params.studyId;
+	if (sStudyId) {
+		const studyId = parseInt(sStudyId);
+		if (studyId) {
+			const study = Study.parse(await getStudyAPI(fetch, studyId));
+			if (study) {
+				return {
+					study
+				};
+			}
+		}
+	}
+
+	const studies = Study.parseAll(await getStudiesAPI(fetch));
+
+	return {
+		studyError: true,
+		studies
+	};
+};