diff --git a/dev.db b/dev.db index 5629128..26e613c 100644 Binary files a/dev.db and b/dev.db differ diff --git a/src/actions/profile.ts b/src/actions/profile.ts new file mode 100644 index 0000000..f4dc3c7 --- /dev/null +++ b/src/actions/profile.ts @@ -0,0 +1,58 @@ +'use server'; + +import { auth } from '@/lib/auth'; +import { updateUserProfile, updateUserPassword, getUserById } from '@/services/auth'; + +export async function getProfileAction() { + const session = await auth(); + if (!session?.user?.id) { + return { success: false, error: 'Non authentifié', data: null }; + } + + const user = await getUserById(session.user.id); + if (!user) { + return { success: false, error: 'Utilisateur non trouvé', data: null }; + } + + return { + success: true, + data: { + id: user.id, + name: user.name, + email: user.email, + createdAt: user.createdAt, + }, + }; +} + +export async function updateProfileAction(data: { name?: string; email?: string }) { + const session = await auth(); + if (!session?.user?.id) { + return { success: false, error: 'Non authentifié' }; + } + + const result = await updateUserProfile(session.user.id, data); + return result; +} + +export async function updatePasswordAction(data: { + currentPassword: string; + newPassword: string; +}) { + const session = await auth(); + if (!session?.user?.id) { + return { success: false, error: 'Non authentifié' }; + } + + if (data.newPassword.length < 6) { + return { success: false, error: 'Le nouveau mot de passe doit faire au moins 6 caractères' }; + } + + const result = await updateUserPassword( + session.user.id, + data.currentPassword, + data.newPassword + ); + return result; +} + diff --git a/src/app/profile/PasswordForm.tsx b/src/app/profile/PasswordForm.tsx new file mode 100644 index 0000000..37b0a69 --- /dev/null +++ b/src/app/profile/PasswordForm.tsx @@ -0,0 +1,115 @@ +'use client'; + +import { useState, useTransition } from 'react'; +import { Input, Button } from '@/components/ui'; +import { updatePasswordAction } from '@/actions/profile'; + +export function PasswordForm() { + const [isPending, startTransition] = useTransition(); + const [currentPassword, setCurrentPassword] = useState(''); + const [newPassword, setNewPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null); + + const canSubmit = + currentPassword.length > 0 && + newPassword.length >= 6 && + newPassword === confirmPassword; + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setMessage(null); + + if (newPassword !== confirmPassword) { + setMessage({ type: 'error', text: 'Les mots de passe ne correspondent pas' }); + return; + } + + startTransition(async () => { + const result = await updatePasswordAction({ currentPassword, newPassword }); + + if (result.success) { + setMessage({ type: 'success', text: 'Mot de passe modifié avec succès' }); + setCurrentPassword(''); + setNewPassword(''); + setConfirmPassword(''); + } else { + setMessage({ type: 'error', text: result.error || 'Erreur lors de la modification' }); + } + }); + } + + return ( +
+
+ + setCurrentPassword(e.target.value)} + required + /> +
+ +
+ + setNewPassword(e.target.value)} + required + minLength={6} + /> +

Minimum 6 caractères

+
+ +
+ + setConfirmPassword(e.target.value)} + required + /> + {confirmPassword && newPassword !== confirmPassword && ( +

+ Les mots de passe ne correspondent pas +

+ )} +
+ + {message && ( +

+ {message.text} +

+ )} + + +
+ ); +} + diff --git a/src/app/profile/ProfileForm.tsx b/src/app/profile/ProfileForm.tsx new file mode 100644 index 0000000..60b0879 --- /dev/null +++ b/src/app/profile/ProfileForm.tsx @@ -0,0 +1,84 @@ +'use client'; + +import { useState, useTransition } from 'react'; +import { useRouter } from 'next/navigation'; +import { Input, Button } from '@/components/ui'; +import { updateProfileAction } from '@/actions/profile'; + +interface ProfileFormProps { + initialData: { + name: string; + email: string; + }; +} + +export function ProfileForm({ initialData }: ProfileFormProps) { + const router = useRouter(); + const [isPending, startTransition] = useTransition(); + const [name, setName] = useState(initialData.name); + const [email, setEmail] = useState(initialData.email); + const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null); + + const hasChanges = name !== initialData.name || email !== initialData.email; + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setMessage(null); + + startTransition(async () => { + const result = await updateProfileAction({ name, email }); + + if (result.success) { + setMessage({ type: 'success', text: 'Profil mis à jour avec succès' }); + router.refresh(); + } else { + setMessage({ type: 'error', text: result.error || 'Erreur lors de la mise à jour' }); + } + }); + } + + return ( +
+
+ + setName(e.target.value)} + placeholder="Votre nom" + /> +
+ +
+ + setEmail(e.target.value)} + required + /> +
+ + {message && ( +

+ {message.text} +

+ )} + + +
+ ); +} + diff --git a/src/app/profile/page.tsx b/src/app/profile/page.tsx new file mode 100644 index 0000000..2c18ce2 --- /dev/null +++ b/src/app/profile/page.tsx @@ -0,0 +1,75 @@ +import { auth } from '@/lib/auth'; +import { redirect } from 'next/navigation'; +import { getUserById } from '@/services/auth'; +import { ProfileForm } from './ProfileForm'; +import { PasswordForm } from './PasswordForm'; + +export default async function ProfilePage() { + const session = await auth(); + + if (!session?.user?.id) { + redirect('/login'); + } + + const user = await getUserById(session.user.id); + + if (!user) { + redirect('/login'); + } + + return ( +
+
+

Mon Profil

+

Gérez vos informations personnelles

+
+ +
+ {/* Profile Info */} +
+

+ Informations personnelles +

+ +
+ + {/* Password */} +
+

+ Changer le mot de passe +

+ +
+ + {/* Account Info */} +
+

+ Informations du compte +

+
+
+ ID du compte + {user.id} +
+
+ Membre depuis + + {new Date(user.createdAt).toLocaleDateString('fr-FR', { + day: 'numeric', + month: 'long', + year: 'numeric', + })} + +
+
+
+
+
+ ); +} + diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index 78a060d..5c0021c 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -75,6 +75,13 @@ export function Header() { {session.user.email}

+ setMenuOpen(false)} + className="block w-full px-4 py-2 text-left text-sm text-foreground hover:bg-card-hover" + > + 👤 Mon Profil +