This commit is contained in:
Louis Mazin 2026-09-25 10:29:24 +02:00
parent 71978c7877
commit 667a0035c6
4 changed files with 1987 additions and 19 deletions

View File

@ -5,4 +5,6 @@ urlpatterns = [
path('', views.EventListView.as_view(), name='event-list'),
path('<int:id>/', views.EventDetailView.as_view(), name='event-detail'),
path('create/', views.EventCreateView.as_view(), name='event-create'),
path('change-password/', views.ChangePasswordView.as_view(), name='change-password',
),
]

View File

@ -7,6 +7,7 @@ from django.utils import timezone
from datetime import timedelta
from .models import Invitation, User
from .serializers import UserSerializer, RegisterSerializer
from django.contrib.auth import password_validation
class RegisterView(generics.CreateAPIView):
queryset = User.objects.all()
@ -73,6 +74,41 @@ class UserPromoteView(APIView):
except User.DoesNotExist:
return Response({'error': 'Utilisateur non trouvé'}, status=404)
class ChangePasswordView(APIView):
permission_classes = [IsAuthenticated]
def post(self, request):
current_password = request.data.get("current_password", "")
new_password = request.data.get("new_password", "")
confirm_password = request.data.get("confirm_password", "")
if not request.user.check_password(current_password):
return Response(
{"current_password": ["Mot de passe actuel incorrect."]},
status=status.HTTP_400_BAD_REQUEST,
)
if new_password != confirm_password:
return Response(
{"confirm_password": ["Les mots de passe ne correspondent pas."]},
status=status.HTTP_400_BAD_REQUEST,
)
try:
password_validation.validate_password(
new_password,
user=request.user,
)
except DjangoValidationError as error:
return Response(
{"new_password": error.messages},
status=status.HTTP_400_BAD_REQUEST,
)
request.user.set_password(new_password)
request.user.save(update_fields=["password"])
return Response({"detail": "Mot de passe modifié."})
class UserDeleteView(UserDetailView):
http_method_names = ['delete', 'options']

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,89 @@
import React from 'react';
import React, { useState } from 'react';
import LoadingCard from '@/components/LoadingCard';
import MainCard from '@/components/MainCard';
import { useAuth } from '../context/useAuth';
import {
CardContent,
} from '@/components/ui/card';
import { CardContent } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Separator } from '@/components/ui/separator';
import { User } from 'lucide-react';
import api from '../api/axios'; // Adapte ce chemin si ton instance Axios est ailleurs.
const MyspacePage = () => {
const { user } = useAuth();
const [passwordDialogOpen, setPasswordDialogOpen] = useState(false);
const [currentPassword, setCurrentPassword] = useState('');
const [newPassword, setNewPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [passwordError, setPasswordError] = useState('');
const [passwordSuccess, setPasswordSuccess] = useState('');
const [savingPassword, setSavingPassword] = useState(false);
const resetPasswordForm = () => {
setCurrentPassword('');
setNewPassword('');
setConfirmPassword('');
setPasswordError('');
};
const handlePasswordDialogChange = (open) => {
if (savingPassword) return;
setPasswordDialogOpen(open);
if (!open) resetPasswordForm();
};
const handleChangePassword = async (event) => {
event.preventDefault();
setPasswordError('');
setPasswordSuccess('');
if (!currentPassword || !newPassword || !confirmPassword) {
setPasswordError('Remplis tous les champs.');
return;
}
if (newPassword !== confirmPassword) {
setPasswordError('Les nouveaux mots de passe ne correspondent pas.');
return;
}
setSavingPassword(true);
try {
await api.post('/users/change-password/', {
current_password: currentPassword,
new_password: newPassword,
confirm_password: confirmPassword,
});
resetPasswordForm();
setPasswordDialogOpen(false);
setPasswordSuccess('Mot de passe modifié.');
} catch (error) {
const data = error.response?.data;
const message =
data?.current_password?.[0] ||
data?.new_password?.[0] ||
data?.confirm_password?.[0] ||
data?.detail ||
'Impossible de modifier le mot de passe.';
setPasswordError(message);
} finally {
setSavingPassword(false);
}
};
if (!user) {
return (
@ -24,25 +97,24 @@ const MyspacePage = () => {
return (
<MainCard title={user.first_name} icon={User}>
<CardContent className="overflow-y-auto">
<div className="space-y-4">
<div className="flex items-center justify-between">
<span className="font-medium">Prénom:</span>
<div className="flex items-center justify-between gap-4">
<span className="font-medium">Prénom :</span>
<span>{user.first_name}</span>
</div>
<Separator />
<div className="flex items-center justify-between">
<span className="font-medium">Nom:</span>
<div className="flex items-center justify-between gap-4">
<span className="font-medium">Nom :</span>
<span>{user.last_name}</span>
</div>
<Separator />
<div className="flex items-center justify-between">
<span className="font-medium">Admin:</span>
<div className="flex items-center justify-between gap-4">
<span className="font-medium">Admin :</span>
<Badge variant={user.is_admin ? 'default' : 'secondary'}>
{user.is_admin ? 'Oui' : 'Non'}
</Badge>
@ -50,23 +122,122 @@ const MyspacePage = () => {
<Separator />
<div className="flex items-center justify-between">
<span className="font-medium">Nom d'utilisateur:</span>
<div className="flex items-center justify-between gap-4">
<span className="font-medium">Nom d'utilisateur :</span>
<span>{user.username}</span>
</div>
<Separator />
<div className="flex items-center justify-between">
<span className="font-medium">Date d'inscription:</span>
<div className="flex items-center justify-between gap-4">
<span className="font-medium">Date d'inscription :</span>
<span>
{user.date_joined
? new Date(user.date_joined).toLocaleDateString('fr-FR')
: 'Non spécifié'}
</span>
</div>
<Separator />
<div className="flex items-center justify-between gap-4">
<span className="font-medium">Mot de passe :</span>
<Button
type="button"
variant="outline"
onClick={() => {
setPasswordSuccess('');
setPasswordDialogOpen(true);
}}
>
Modifier le mot de passe
</Button>
</div>
{passwordSuccess && (
<p role="status" className="text-sm text-green-600">
{passwordSuccess}
</p>
)}
</div>
</CardContent>
<Dialog
open={passwordDialogOpen}
onOpenChange={handlePasswordDialogChange}
>
<DialogContent>
<DialogHeader>
<DialogTitle>Modifier le mot de passe</DialogTitle>
<DialogDescription>
Saisis ton mot de passe actuel, puis le nouveau deux fois.
</DialogDescription>
</DialogHeader>
<form onSubmit={handleChangePassword} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="current-password">Mot de passe actuel</Label>
<Input
id="current-password"
type="password"
autoComplete="current-password"
value={currentPassword}
onChange={(event) => setCurrentPassword(event.target.value)}
disabled={savingPassword}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="new-password">Nouveau mot de passe</Label>
<Input
id="new-password"
type="password"
autoComplete="new-password"
value={newPassword}
onChange={(event) => setNewPassword(event.target.value)}
disabled={savingPassword}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="confirm-password">
Confirmer le nouveau mot de passe
</Label>
<Input
id="confirm-password"
type="password"
autoComplete="new-password"
value={confirmPassword}
onChange={(event) => setConfirmPassword(event.target.value)}
disabled={savingPassword}
required
/>
</div>
{passwordError && (
<p role="alert" className="text-sm text-destructive">
{passwordError}
</p>
)}
<DialogFooter>
<Button
type="button"
variant="outline"
onClick={() => handlePasswordDialogChange(false)}
disabled={savingPassword}
>
Annuler
</Button>
<Button type="submit" disabled={savingPassword}>
{savingPassword ? 'Modification...' : 'Enregistrer'}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
</MainCard>
);
};