diff --git a/src/app/account/page.tsx b/src/app/account/page.tsx
new file mode 100644
index 0000000..d816fbe
--- /dev/null
+++ b/src/app/account/page.tsx
@@ -0,0 +1,34 @@
+import { UserProfileCard } from "@/components/account/UserProfileCard";
+import { ChangePasswordForm } from "@/components/account/ChangePasswordForm";
+import { UserService } from "@/lib/services/user.service";
+import { redirect } from "next/navigation";
+
+export default async function AccountPage() {
+ try {
+ const [profile, stats] = await Promise.all([
+ UserService.getUserProfile(),
+ UserService.getUserStats(),
+ ]);
+
+ return (
+
+
+
+
Mon compte
+
+ Gérez vos informations personnelles et votre sécurité
+
+
+
+
+
+
+
+
+
+ );
+ } catch (error) {
+ console.error("Erreur lors du chargement du compte:", error);
+ redirect("/login");
+ }
+}
diff --git a/src/app/api/user/password/route.ts b/src/app/api/user/password/route.ts
new file mode 100644
index 0000000..ce62236
--- /dev/null
+++ b/src/app/api/user/password/route.ts
@@ -0,0 +1,49 @@
+import { NextRequest, NextResponse } from "next/server";
+import { UserService } from "@/lib/services/user.service";
+import { AppError } from "@/utils/errors";
+import { AuthServerService } from "@/lib/services/auth-server.service";
+
+export async function PUT(request: NextRequest) {
+ try {
+ const body = await request.json();
+ const { currentPassword, newPassword } = body;
+
+ if (!currentPassword || !newPassword) {
+ return NextResponse.json(
+ { error: "Mots de passe manquants" },
+ { status: 400 }
+ );
+ }
+
+ // Vérifier que le nouveau mot de passe est fort
+ if (!AuthServerService.isPasswordStrong(newPassword)) {
+ return NextResponse.json(
+ {
+ error: "Le nouveau mot de passe doit contenir au moins 8 caractères, une majuscule et un chiffre"
+ },
+ { status: 400 }
+ );
+ }
+
+ await UserService.changePassword(currentPassword, newPassword);
+
+ return NextResponse.json({ success: true });
+ } catch (error) {
+ console.error("Erreur lors du changement de mot de passe:", error);
+
+ if (error instanceof AppError) {
+ return NextResponse.json(
+ { error: error.message, code: error.code },
+ {
+ status: error.code === "AUTH_INVALID_PASSWORD" ? 400 :
+ error.code === "AUTH_UNAUTHENTICATED" ? 401 : 500
+ }
+ );
+ }
+
+ return NextResponse.json(
+ { error: "Erreur lors du changement de mot de passe" },
+ { status: 500 }
+ );
+ }
+}
diff --git a/src/app/api/user/profile/route.ts b/src/app/api/user/profile/route.ts
new file mode 100644
index 0000000..de6a55c
--- /dev/null
+++ b/src/app/api/user/profile/route.ts
@@ -0,0 +1,28 @@
+import { NextResponse } from "next/server";
+import { UserService } from "@/lib/services/user.service";
+import { AppError } from "@/utils/errors";
+
+export async function GET() {
+ try {
+ const [profile, stats] = await Promise.all([
+ UserService.getUserProfile(),
+ UserService.getUserStats(),
+ ]);
+
+ return NextResponse.json({ ...profile, stats });
+ } catch (error) {
+ console.error("Erreur lors de la récupération du profil:", error);
+
+ if (error instanceof AppError) {
+ return NextResponse.json(
+ { error: error.message, code: error.code },
+ { status: error.code === "AUTH_UNAUTHENTICATED" ? 401 : 500 }
+ );
+ }
+
+ return NextResponse.json(
+ { error: "Erreur lors de la récupération du profil" },
+ { status: 500 }
+ );
+ }
+}
diff --git a/src/components/account/ChangePasswordForm.tsx b/src/components/account/ChangePasswordForm.tsx
new file mode 100644
index 0000000..84cf3c1
--- /dev/null
+++ b/src/components/account/ChangePasswordForm.tsx
@@ -0,0 +1,139 @@
+"use client";
+
+import { useState } from "react";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { useToast } from "@/components/ui/use-toast";
+import { Lock } from "lucide-react";
+
+export function ChangePasswordForm() {
+ const [currentPassword, setCurrentPassword] = useState("");
+ const [newPassword, setNewPassword] = useState("");
+ const [confirmPassword, setConfirmPassword] = useState("");
+ const [isLoading, setIsLoading] = useState(false);
+ const { toast } = useToast();
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+
+ if (newPassword !== confirmPassword) {
+ toast({
+ variant: "destructive",
+ title: "Erreur",
+ description: "Les mots de passe ne correspondent pas",
+ });
+ return;
+ }
+
+ if (newPassword.length < 8) {
+ toast({
+ variant: "destructive",
+ title: "Erreur",
+ description: "Le mot de passe doit contenir au moins 8 caractères",
+ });
+ return;
+ }
+
+ setIsLoading(true);
+
+ try {
+ const response = await fetch("/api/user/password", {
+ method: "PUT",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ currentPassword, newPassword }),
+ });
+
+ if (!response.ok) {
+ const data = await response.json();
+ throw new Error(data.error || "Erreur lors du changement de mot de passe");
+ }
+
+ toast({
+ title: "Succès",
+ description: "Votre mot de passe a été modifié avec succès",
+ });
+
+ // Reset form
+ setCurrentPassword("");
+ setNewPassword("");
+ setConfirmPassword("");
+ } catch (error) {
+ toast({
+ variant: "destructive",
+ title: "Erreur",
+ description: error instanceof Error ? error.message : "Une erreur est survenue",
+ });
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return (
+
+
+ Changer le mot de passe
+
+ Assurez-vous d'utiliser un mot de passe fort (8 caractères minimum, une majuscule et un chiffre)
+
+
+
+
+
+
+ );
+}
+
diff --git a/src/components/account/UserProfileCard.tsx b/src/components/account/UserProfileCard.tsx
new file mode 100644
index 0000000..f0a240b
--- /dev/null
+++ b/src/components/account/UserProfileCard.tsx
@@ -0,0 +1,76 @@
+"use client";
+
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import { Badge } from "@/components/ui/badge";
+import { Mail, Calendar, Shield, Heart } from "lucide-react";
+import type { UserProfile } from "@/lib/services/user.service";
+
+interface UserProfileCardProps {
+ profile: UserProfile & { stats: { favoritesCount: number; hasPreferences: boolean; hasKomgaConfig: boolean } };
+}
+
+export function UserProfileCard({ profile }: UserProfileCardProps) {
+ return (
+
+
+ Informations du compte
+ Vos informations personnelles
+
+
+
+
+
+
Email
+
{profile.email}
+
+
+
+
+
+
+
Rôles
+
+ {profile.roles.map((role) => (
+
+ {role.replace("ROLE_", "")}
+
+ ))}
+
+
+
+
+
+
+
+
Membre depuis
+
+ {new Date(profile.createdAt).toLocaleDateString("fr-FR", {
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ })}
+
+
+
+
+
+
+
+
Favoris
+
+ {profile.stats.favoritesCount} séries favorites
+
+
+
+
+
+
+ Dernière mise à jour:{" "}
+ {new Date(profile.updatedAt).toLocaleDateString("fr-FR")}
+
+
+
+
+ );
+}
+
diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx
index 687aa05..e6296d6 100644
--- a/src/components/layout/Sidebar.tsx
+++ b/src/components/layout/Sidebar.tsx
@@ -1,6 +1,6 @@
"use client";
-import { Home, Library, Settings, LogOut, RefreshCw, Star, Download } from "lucide-react";
+import { Home, Library, Settings, LogOut, RefreshCw, Star, Download, User } from "lucide-react";
import { usePathname, useRouter } from "next/navigation";
import { cn } from "@/lib/utils";
import { signOut } from "next-auth/react";
@@ -274,6 +274,16 @@ export function Sidebar({ isOpen, onClose, initialLibraries, initialFavorites }:
{t("sidebar.settings.title")}
+