diff --git a/frontend/src/lang/fr.json b/frontend/src/lang/fr.json
index 92ddba90763cd13f837f83c43f9de39beb18f589..a029f519eb6a156d40f1557eb649d336bb6991c2 100644
--- a/frontend/src/lang/fr.json
+++ b/frontend/src/lang/fr.json
@@ -14,7 +14,8 @@
 			"sessions": "Sessions",
 			"studies": "Études",
 			"tasks": "Tâches"
-		}
+		},
+		"profile": "Mon profil"
 	},
 	"chatbox": {
 		"placeholder": "Écrivez votre message ici...",
@@ -429,6 +430,10 @@
 		"startTask": "Commencer cette tâche",
 		"achieveTask": "Tâche achevée"
 	},
+	"profile": {
+		"title": "Mon profile",
+		"save": "Sauvegarder les modifications"
+	},
 	"button": {
 		"create": "Créer",
 		"submit": "Envoyer",
diff --git a/frontend/src/lib/types/user.ts b/frontend/src/lib/types/user.ts
index 906d98667918f45fd8a268b76446186c84d30baf..0acb93b5a9e65569a4c4e977b52b2a5cc074b9cc 100644
--- a/frontend/src/lib/types/user.ts
+++ b/frontend/src/lib/types/user.ts
@@ -28,7 +28,7 @@ export default class User {
 	private _ui_language: string | null;
 	private _home_language: string | null;
 	private _target_language: string | null;
-	private _birthdate: Date | null;
+	private _birthdate: string | null;
 	private _gender: string | null;
 	private _bio: string | null;
 	private _calcom_link: string | null;
@@ -51,7 +51,7 @@ export default class User {
 		ui_language: string | null,
 		home_language: string | null,
 		target_language: string | null,
-		birthdate: Date | null,
+		birthdate: string | null,
 		gender: string | null,
 		calcom_link: string | null,
 		studies_id: number[] | null,
@@ -130,10 +130,17 @@ export default class User {
 		return this._target_language;
 	}
 
-	get birthdate(): Date | null {
+	get birthdate(): string | null {
 		return this._birthdate;
 	}
 
+	get birthdateAsDay(): string | null {
+		if (this._birthdate) {
+			return this._birthdate.slice(0, 10); // Format as YYYY-MM-DD
+		}
+		return null;
+	}
+
 	get gender(): string | null {
 		return this._gender;
 	}
diff --git a/frontend/src/routes/Header.svelte b/frontend/src/routes/Header.svelte
index dbb172773b765caaeaec429c1998c2ce5b2e6bfa..79f04b7c1005aa12df5040f350acfadd393f1304 100644
--- a/frontend/src/routes/Header.svelte
+++ b/frontend/src/routes/Header.svelte
@@ -50,7 +50,7 @@
 		</h1>
 	</div>
 	<div class="navbar-end hidden sm:flex">
-		<ul class="menu menu-horizontal p-0 flex items-center">
+		<ul class="menu menu-horizontal p-0 flex items-center z-10">
 			{#if user}
 				<li>
 					<details>
@@ -63,6 +63,19 @@
 							{user.nickname}
 						</summary>
 						<ul class="menu menu-sm dropdown-content absolute right-0">
+							{#if user?.type < 2}
+								<li>
+									<a
+										class="btn btn-sm my-auto"
+										data-sveltekit-reload
+										href="/tutor/profile?redirect={encodeURIComponent(
+											$page.url.pathname + $page.url.search
+										)}"
+									>
+										{$t('header.profile')}
+									</a>
+								</li>
+							{/if}
 							<li>
 								<a data-sveltekit-reload href="/logout" class="whitespace-nowrap">
 									{$t('header.signout')}
diff --git a/frontend/src/routes/tutor/+layout.server.ts b/frontend/src/routes/tutor/+layout.server.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ddf2023916330cb19354b8333ee001f0a6f1c306
--- /dev/null
+++ b/frontend/src/routes/tutor/+layout.server.ts
@@ -0,0 +1,11 @@
+import { type ServerLoad, error, redirect } from '@sveltejs/kit';
+
+export const load: ServerLoad = async ({ locals }) => {
+	if (locals.user == null || locals.user == undefined) {
+		redirect(303, '/login');
+	}
+
+	if (locals.user == null || locals.user == undefined || locals.user.type > 1) {
+		error(403, 'Forbidden');
+	}
+};
diff --git a/frontend/src/routes/tutor/profile/+page.server.ts b/frontend/src/routes/tutor/profile/+page.server.ts
new file mode 100644
index 0000000000000000000000000000000000000000..821322e9427656ab239af47a78d5644d0ef72a8a
--- /dev/null
+++ b/frontend/src/routes/tutor/profile/+page.server.ts
@@ -0,0 +1,57 @@
+import { fail, redirect, type Actions } from '@sveltejs/kit';
+import { patchUserAPI } from '$lib/api/users';
+import { safeRedirectAuto } from '$lib/utils/security';
+
+export const actions: Actions = {
+	default: async ({ request, fetch, locals, url }) => {
+		if (!locals.user) {
+			return fail(401, { message: 'Unauthorized' });
+		}
+		const formData = await request.formData();
+		const nickname = formData.get('nickname') as string;
+		const email = formData.get('email') as string;
+		const gender = formData.get('gender') as string;
+		const birthdate = formData.get('birthdate') as string;
+		const bio = formData.get('bio') as string;
+		const availabilitiesRaw = formData.getAll('availability[]') as string[];
+
+		let availabilities: { day: string; start: string; end: string }[] = [];
+		for (const availability of availabilitiesRaw) {
+			const [day, start, end] = availability.split('-', 3);
+
+			if (!day || !start || !end) continue;
+
+			availabilities.push({
+				day: day.trim(),
+				start: start.trim(),
+				end: end.trim()
+			});
+		}
+
+		console.log('Updating profile with data:', {
+			nickname,
+			email,
+			gender,
+			birthdate,
+			bio,
+			availabilities
+		});
+
+		const data: any = {
+			nickname,
+			email,
+			gender,
+			birthdate,
+			bio,
+			availabilities
+		};
+
+		const ok = await patchUserAPI(fetch, locals.user.id, data);
+
+		if (!ok) {
+			return fail(400, { message: 'Failed to update profile.' });
+		}
+
+		return safeRedirectAuto(url);
+	}
+};
diff --git a/frontend/src/routes/tutor/profile/+page.svelte b/frontend/src/routes/tutor/profile/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..f586cba06724f91893319c5c5a7f7c74850ab468
--- /dev/null
+++ b/frontend/src/routes/tutor/profile/+page.svelte
@@ -0,0 +1,193 @@
+<script lang="ts">
+	import { t } from '$lib/services/i18n';
+	import type { PageData } from './$types';
+	import autosize from 'svelte-autosize';
+
+	let { data, form }: { data: PageData; form: FormData } = $props();
+	let user = $state(data.user);
+
+	let isLoading = false;
+	let message = $state(form?.message || '');
+
+	const MAX_BIO_LENGTH = 100;
+
+	let bio = $state(user?.bio || '');
+
+	let remainingCharacters = $derived(MAX_BIO_LENGTH - bio.length);
+
+	type Availability = {
+		day: string;
+		start: string;
+		end: string;
+	};
+
+	const days = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
+
+	let availabilities: Availability[] = $state(
+		user?.availabilities ? [...user?.availabilities] : []
+	);
+	let selectedWeekday = $state('');
+	let selectedTimeStart = $state('');
+	let selectedTimeEnd = $state('');
+
+	function addAvailability() {
+		if (!selectedWeekday || !selectedTimeStart || !selectedTimeEnd) {
+			message = 'All fields must be selected.';
+			return;
+		}
+		const startHour = parseInt(selectedTimeStart.split(':')[0]);
+		const endHour = parseInt(selectedTimeEnd.split(':')[0]);
+		if (startHour >= endHour) {
+			message = 'End time must be later than start time.';
+			return;
+		}
+		availabilities = [
+			...availabilities,
+			{ day: selectedWeekday, start: selectedTimeStart, end: selectedTimeEnd }
+		];
+		selectedWeekday = '';
+		selectedTimeStart = '';
+		selectedTimeEnd = '';
+		message = '';
+	}
+
+	function removeAvailability(index: number) {
+		availabilities = availabilities.filter((_, i) => i !== index);
+	}
+</script>
+
+<div class="w-full max-w-4xl mx-auto p-6 bg-white rounded-lg shadow mt-8">
+	<h1 class="text-2xl font-bold mb-6">{$t('profile.title')}</h1>
+	{#if message}
+		<div class="alert alert-error mb-4">{message}</div>
+	{/if}
+	<form class="space-y-6" method="POST">
+		<div>
+			<label class="block text-sm font-medium mb-1" for="nickname">
+				{$t('register.nickname')}
+			</label>
+			<input
+				id="nickname"
+				name="nickname"
+				type="text"
+				class="input input-bordered w-full"
+				value={user?.nickname}
+				required
+			/>
+		</div>
+		<div>
+			<label class="block text-sm font-medium mb-1" for="email">
+				{$t('register.email')}
+			</label>
+			<input
+				id="email"
+				name="email"
+				type="email"
+				class="input input-bordered w-full"
+				value={user?.email}
+				required
+			/>
+		</div>
+		<div>
+			<label class="block text-sm font-medium mb-1" for="gender">
+				{$t('register.gender')}
+			</label>
+			<select
+				id="gender"
+				name="gender"
+				class="select select-bordered w-full"
+				value={user?.gender}
+				required
+			>
+				<option value="" disabled>{$t('register.gender')}</option>
+				<option value="male">{$t('register.genders.male')}</option>
+				<option value="female">{$t('register.genders.female')}</option>
+				<option value="other">{$t('register.genders.other')}</option>
+				<option value="na">{$t('register.genders.na')}</option>
+			</select>
+		</div>
+		<div>
+			<label class="block text-sm font-medium mb-1" for="birthdate">
+				{$t('register.birthyear')}
+			</label>
+			<input
+				id="birthdate"
+				name="birthdate"
+				type="date"
+				class="input input-bordered w-full"
+				value={user?.birthdateAsDay}
+			/>
+			required />
+		</div>
+		<div>
+			<label class="block text-sm font-medium mb-1" for="bio">
+				{$t('register.bio')}
+				<span class="text-xs text-gray-400 ml-2">{remainingCharacters} / {MAX_BIO_LENGTH}</span>
+			</label>
+			<textarea
+				use:autosize
+				id="bio"
+				name="bio"
+				class="textarea textarea-bordered w-full"
+				bind:value={bio}
+				maxlength={MAX_BIO_LENGTH}
+				required
+			></textarea>
+		</div>
+		<div>
+			<h2 class="text-lg font-semibold">
+				{$t('register.availabilities')}
+			</h2>
+			<div class="flex flex-row gap-2 mb-2 w-full">
+				<select class="select select-bordered flex-grow" bind:value={selectedWeekday}>
+					<option value="" disabled>{$t('register.weekday')}</option>
+					{#each days as dayKey}
+						<option value={dayKey}>{$t(`utils.days.${dayKey}`)}</option>
+					{/each}
+				</select>
+				<select class="select select-bordered flex-grow" bind:value={selectedTimeStart}>
+					<option value="" disabled>{$t('register.startTime')}</option>
+					{#each Array.from({ length: 24 }, (_, i) => `${i}:00`) as time}
+						<option value={time}>{time}</option>
+					{/each}
+				</select>
+				<select class="select select-bordered flex-grow" bind:value={selectedTimeEnd}>
+					<option value="" disabled>{$t('register.endTime')}</option>
+					{#each Array.from({ length: 24 }, (_, i) => `${i}:00`) as time}
+						<option value={time}>{time}</option>
+					{/each}
+				</select>
+				<button type="button" class="btn btn-primary flex-grow" onclick={addAvailability}>
+					{$t('register.addAvailability')}
+				</button>
+			</div>
+			{#if availabilities.length > 0}
+				<ul class="list-disc pl-6 py-2">
+					{#each availabilities as { day, start, end }, index}
+						<li class="flex justify-between items-center">
+							<input type="hidden" name="availability[]" value={`${day}-${start}-${end}`} />
+
+							<span>{$t(`utils.days.${day}`)}: {start} - {end}</span>
+							<button
+								type="button"
+								class="text-red-500 ml-4"
+								onclick={() => removeAvailability(index)}
+							>
+								{$t('register.remove')}
+							</button>
+						</li>
+					{/each}
+				</ul>
+			{:else}
+				<p class="text-gray-500 text-sm py-2">
+					{$t('register.noAvailabilities')}
+				</p>
+			{/if}
+		</div>
+		<div>
+			<button type="submit" class="btn btn-primary w-full mt-4" disabled={isLoading}>
+				{isLoading ? $t('profile.saving') : $t('profile.save')}
+			</button>
+		</div>
+	</form>
+</div>