diff --git a/backend/app/crud.py b/backend/app/crud.py
index b24620c55631646e0c8a527352ab15dbc71eb758..1183de4f78ffc0c9d42fdd7fecd5d9aa2342e8c3 100644
--- a/backend/app/crud.py
+++ b/backend/app/crud.py
@@ -66,6 +66,12 @@ def create_contact(db: Session, user, contact):
     db.refresh(user)
     return user
 
+def create_user_survey_weekly(db: Session, user_id: int, survey: schemas.SurveyCreate):
+    db_user_survey_weekly = models.UserSurveyWeekly(user_id=user_id, **survey.dict())
+    db.add(db_user_survey_weekly)
+    db.commit()
+    db.refresh(db_user_survey_weekly)
+    return db_user_survey_weekly
 
 def get_contact_sessions(db: Session, user_id: int, contact_id: int):
     return (
diff --git a/backend/app/main.py b/backend/app/main.py
index 20ef39f3e9e3310fdb46ae53379e8d650d2eb744..857b253df529c3b3b2e639e0264caf6700c23c8f 100644
--- a/backend/app/main.py
+++ b/backend/app/main.py
@@ -379,6 +379,27 @@ def get_contacts(
     return db_user.contacts + db_user.contact_of
 
 
+@usersRouter.post("/{user_id}/surveys/weekly", status_code=status.HTTP_201_CREATED)
+def create_weekly_survey(
+    user_id: int,
+    survey: schemas.UserSurveyWeeklyCreate,
+    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
+    ):
+        raise HTTPException(
+            status_code=401,
+            detail="You do not have permission to create a survey for this user",
+        )
+    db_user = crud.get_user(db, user_id)
+    if db_user is None:
+        raise HTTPException(status_code=404, detail="User not found")
+    
+    return crud.create_user_survey_weekly(db, user_id, survey).id
+
 @sessionsRouter.post("", response_model=schemas.Session)
 def create_session(
     db: Session = Depends(get_db),
diff --git a/backend/app/models.py b/backend/app/models.py
index c9849dc450212cb2939d26046c88090dd918f87a..7a03108ed07f1a84d885ca0454c471d0f9d4b03c 100644
--- a/backend/app/models.py
+++ b/backend/app/models.py
@@ -47,6 +47,7 @@ class User(Base):
     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(
         "Session", secondary="user_sessions", back_populates="users"
@@ -69,6 +70,18 @@ class User(Base):
     )
 
 
+class UserSurveyWeekly(Base):
+    __tablename__ = "users_survey_weekly"
+
+    id = Column(Integer, primary_key=True, index=True)
+    created_at = Column(DateTime, default=datetime.datetime.now)
+    user_id = Column(Integer, ForeignKey("users.id"))
+    q1 = Column(Float)
+    q2 = Column(Float)
+    q3 = Column(Float)
+    q4 = Column(Float)
+
+
 class Session(Base):
     __tablename__ = "sessions"
 
diff --git a/backend/app/schemas.py b/backend/app/schemas.py
index 68d60de310050c32744d219817200b83d79f17bc..5e75ab7248fdffa0515990cec1a4418883aa759b 100644
--- a/backend/app/schemas.py
+++ b/backend/app/schemas.py
@@ -18,6 +18,7 @@ class User(BaseModel):
     gender: str | None = None
     calcom_link: str | None
     study_id: int | None = None
+    last_survey: datetime.datetime | None = None
 
     class Config:
         from_attributes = True
@@ -37,6 +38,7 @@ class UserCreate(BaseModel):
     gender: str | None = None
     calcom_link: str | None = None
     study_id: int | None = None
+    last_survey: datetime.datetime | None = None
 
 
 class UserUpdate(BaseModel):
@@ -53,6 +55,7 @@ class UserUpdate(BaseModel):
     gender: str | None = None
     calcom_link: str | None = None
     study_id: int | None = None
+    last_survey: datetime.datetime | None = None
 
     class Config:
         from_attributes = True
@@ -65,6 +68,13 @@ class ContactCreate(BaseModel):
         from_attributes = True
 
 
+class UserSurveyWeeklyCreate(BaseModel):
+    q1: float
+    q2: float
+    q3: float
+    q4: float
+
+
 class Session(BaseModel):
     id: int
     created_at: datetime.datetime
diff --git a/frontend/src/lang/fr.json b/frontend/src/lang/fr.json
index 682189b68d21e7b366e8eee07faff023717e3601..b6cf78875b78d94045d84592db38fc8b1b88d6f4 100644
--- a/frontend/src/lang/fr.json
+++ b/frontend/src/lang/fr.json
@@ -211,6 +211,36 @@
 				"q1": "À quel point cette application est-elle utile ?",
 				"q2": "À quel point cette application est-elle facile à utiliser ?",
 				"q3": "Remarques éventuelles"
+			},
+			"weekly": {
+				"title": "Questionnaire hebdomadaire",
+				"description": "Au cours des 7 derniers jours...",
+				"questions": [
+					"Combien d'heures de <span class='font-bold'>cours</span> de {TARGET_LANGUAGE} avez vous suivies ?",
+					"Combien d'heures avez-vous <span class='font-bold'>regardé des vidéos</span> en {TARGET_LANGUAGE} (films, séries, Youtube...) ou <span class='font-bold'>écouté des contenus</span> en {TARGET_LANGUAGE} (podcasts, radio, cours universitaires...) ?",
+					"Combien d'heures avez-vous <span class='font-bold'>lu des textes</span> en {TARGET_LANGUAGE} (livre, journal, BD, sites web...) ?",
+					"Combien d'heures avez-vous <span class='font-bold'>parlé</span> en {TARGET_LANGUAGE} (discussions avec amis, famille, collègues...) ?"
+				],
+				"answers": {
+					"placeholder": "",
+					"0": "Aucune",
+					"05": "30 minutes ou moins",
+					"1": "1 heure",
+					"2": "2 heures",
+					"3": "3 heures",
+					"4": "4 heures",
+					"5": "5 heures",
+					"6": "6 heures",
+					"7": "7 heures",
+					"8": "8 heures",
+					"9": "9 heures",
+					"10": "10 heures ou plus"
+				},
+				"errors": {
+					"null": "Veuillez répondre à toutes les questions",
+					"submit": "Erreur lors de l'envoi du questionnaire"
+				},
+				"success": "Questionnaire envoyé, merci !"
 			}
 		}
 	},
diff --git a/frontend/src/lib/api/users.ts b/frontend/src/lib/api/users.ts
index 61de74e9b22b54f74773db244f37f419c82862e3..b5d3ced3e99aa115de2c78f361502e56786d0b04 100644
--- a/frontend/src/lib/api/users.ts
+++ b/frontend/src/lib/api/users.ts
@@ -145,3 +145,23 @@ export async function createTestTypingAPI(
 
 	return response.data;
 }
+
+export async function createWeeklySurveyAPI(
+	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
+	});
+	if (response.status !== 201) {
+		toastAlert('Failed to create weekly survey');
+		return null;
+	}
+	return response.data;
+}
diff --git a/frontend/src/lib/components/users/weeklySurvey.svelte b/frontend/src/lib/components/users/weeklySurvey.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..a3f0fcbfc15f9c01a9fa6971e87265ddb6221e14
--- /dev/null
+++ b/frontend/src/lib/components/users/weeklySurvey.svelte
@@ -0,0 +1,80 @@
+<script lang="ts">
+	import { createWeeklySurveyAPI } from '$lib/api/users';
+	import config from '$lib/config';
+	import { t } from '$lib/services/i18n';
+	import { user } from '$lib/types/user';
+	import { toastAlert, toastSuccess, toastWarning } from '$lib/utils/toasts';
+
+	let open =
+		!$user?.last_survey || $user.last_survey.getTime() + config.WEEKLY_SURVEY_INTERVAL < Date.now();
+
+	async function send() {
+		if (!$user) return;
+
+		const data = Array.from({ length: 4 }, (_, i) => {
+			const value = (document.getElementById('questions-' + i) as HTMLSelectElement).value;
+			return value === '-1' ? null : parseFloat(value);
+		});
+
+		if (data.includes(null)) {
+			toastWarning($t('session.modal.weekly.errors.null'));
+			return;
+		}
+
+		const res = await createWeeklySurveyAPI($user.id, data[0]!, data[1]!, data[2]!, data[3]!);
+
+		if (!res) {
+			toastAlert($t('session.modal.weekly.errors.submit'));
+		}
+
+		await $user.patch({ last_survey: new Date() });
+
+		open = false;
+
+		toastSuccess($t('session.modal.weekly.success'));
+	}
+</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"
+>
+	<div class="modal-box max-w-none">
+		<h2 class="text-xl font-bold mb-4">{$t('session.modal.weekly.title')}</h2>
+		<p>{@html $t('session.modal.weekly.description')}</p>
+		{#each new Array(4) as _, i}
+			<label class="form-control w-full">
+				<div class="label">
+					<span class="label-text"
+						>{@html $t('session.modal.weekly.questions.' + i).replaceAll(
+							'{TARGET_LANGUAGE}',
+							$t('utils.language.' + $user?.target_language).toLowerCase()
+						)}</span
+					>
+				</div>
+				<select id={'questions-' + i} class="select select-bordered">
+					<option value="-1" hidden selected
+						>{$t('session.modal.weekly.answers.placeholder')}</option
+					>
+					<option value="0">{$t('session.modal.weekly.answers.0')}</option>
+					<option value="0.5">{$t('session.modal.weekly.answers.05')}</option>
+					<option value="1">{$t('session.modal.weekly.answers.1')}</option>
+					<option value="2">{$t('session.modal.weekly.answers.2')}</option>
+					<option value="3">{$t('session.modal.weekly.answers.3')}</option>
+					<option value="4">{$t('session.modal.weekly.answers.4')}</option>
+					<option value="5">{$t('session.modal.weekly.answers.5')}</option>
+					<option value="6">{$t('session.modal.weekly.answers.6')}</option>
+					<option value="7">{$t('session.modal.weekly.answers.7')}</option>
+					<option value="8">{$t('session.modal.weekly.answers.8')}</option>
+					<option value="9">{$t('session.modal.weekly.answers.9')}</option>
+					<option value="10">{$t('session.modal.weekly.answers.10')}</option>
+				</select>
+			</label>
+		{/each}
+		<button class="btn btn-primary w-full mt-10" on:click={send}>{$t('button.submit')}</button>
+	</div>
+</dialog>
diff --git a/frontend/src/lib/config.ts b/frontend/src/lib/config.ts
index 86c8f76119f629bc96a3b00ab5ad2f191e69e385..10b3cf95a5b512c8e378e20cc4c96d3534b14573 100644
--- a/frontend/src/lib/config.ts
+++ b/frontend/src/lib/config.ts
@@ -3,6 +3,8 @@ export default {
 	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',
+	// 1 week - 2 hours
+	WEEKLY_SURVEY_INTERVAL: (7 * 24 - 2) * 60 * 60 * 1000,
 	LEARNING_LANGUAGES: {
 		fra: 'French - fran\u00e7ais'
 	},
diff --git a/frontend/src/lib/types/user.ts b/frontend/src/lib/types/user.ts
index 692f92bfac097e9e9a74e450cca77a1e7121cb65..ec8e2de3a81d9234a98bfe570c97931fb087af51 100644
--- a/frontend/src/lib/types/user.ts
+++ b/frontend/src/lib/types/user.ts
@@ -1,4 +1,5 @@
 import { createUserAPI, getUsersAPI, patchUserAPI } from '$lib/api/users';
+import { parseToLocalDate } from '$lib/utils/date';
 import { toastAlert } from '$lib/utils/toasts';
 import { get, writable } from 'svelte/store';
 
@@ -30,6 +31,8 @@ export default class User {
 	private _birthdate: number | null;
 	private _gender: string | null;
 	private _calcom_link: string | null;
+	private _study_id: number | null;
+	private _last_survey: Date | null;
 
 	private constructor(
 		id: number,
@@ -43,7 +46,9 @@ export default class User {
 		target_language: string | null,
 		birthdate: number | null,
 		gender: string | null,
-		calcom_link: string | null
+		calcom_link: string | null,
+		study_id: number | null,
+		last_survey: Date | null
 	) {
 		this._id = id;
 		this._email = email;
@@ -57,6 +62,8 @@ export default class User {
 		this._birthdate = birthdate;
 		this._gender = gender;
 		this._calcom_link = calcom_link;
+		this._study_id = study_id;
+		this._last_survey = last_survey;
 	}
 
 	get id(): number {
@@ -115,6 +122,14 @@ export default class User {
 		return this._calcom_link;
 	}
 
+	get study_id(): number | null {
+		return this._study_id;
+	}
+
+	get last_survey(): Date | null {
+		return this._last_survey;
+	}
+
 	equals<T>(obj: T): boolean {
 		if (obj === null || obj === undefined) return false;
 		if (!(obj instanceof User)) return false;
@@ -147,7 +162,9 @@ export default class User {
 			target_language: this.target_language,
 			birthdate: this.birthdate,
 			gender: this.gender,
-			calcom_link: this.calcom_link
+			calcom_link: this.calcom_link,
+			study_id: this.study_id,
+			last_survey: this.last_survey
 		});
 	}
 
@@ -165,6 +182,8 @@ export default class User {
 			if (data.birthdate) this._birthdate = data.birthdate;
 			if (data.gender) this._gender = data.gender;
 			if (data.calcum_link) this._calcom_link = data.calcom_link;
+			if (data.study_id) this._study_id = data.study_id;
+			if (data.last_survey) this._last_survey = data.last_survey;
 		}
 		return res;
 	}
@@ -195,6 +214,8 @@ export default class User {
 			null,
 			null,
 			null,
+			null,
+			null,
 			null
 		);
 		users.add(user);
@@ -235,7 +256,9 @@ export default class User {
 			json.target_language,
 			json.birthdate,
 			json.gender,
-			json.calcom_link
+			json.calcom_link,
+			json.study_id,
+			json.last_survey === null ? null : new Date(json.last_survey)
 		);
 
 		users.update((us) => {
diff --git a/frontend/src/routes/session/+page.svelte b/frontend/src/routes/session/+page.svelte
index cdc89480fbce27ada5f3afd4156218ab923e6651..9791e4807987da8efc14689b66312dcd7599edbf 100644
--- a/frontend/src/routes/session/+page.svelte
+++ b/frontend/src/routes/session/+page.svelte
@@ -7,6 +7,8 @@
 	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;
@@ -61,3 +63,7 @@
 		<div class=""></div>
 	</div>
 {/if}
+
+{#if $user}
+	<WeeklySurvey />
+{/if}