test
This commit is contained in:
parent
71978c7877
commit
667a0035c6
@ -5,4 +5,6 @@ urlpatterns = [
|
|||||||
path('', views.EventListView.as_view(), name='event-list'),
|
path('', views.EventListView.as_view(), name='event-list'),
|
||||||
path('<int:id>/', views.EventDetailView.as_view(), name='event-detail'),
|
path('<int:id>/', views.EventDetailView.as_view(), name='event-detail'),
|
||||||
path('create/', views.EventCreateView.as_view(), name='event-create'),
|
path('create/', views.EventCreateView.as_view(), name='event-create'),
|
||||||
|
path('change-password/', views.ChangePasswordView.as_view(), name='change-password',
|
||||||
|
),
|
||||||
]
|
]
|
||||||
@ -7,6 +7,7 @@ from django.utils import timezone
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from .models import Invitation, User
|
from .models import Invitation, User
|
||||||
from .serializers import UserSerializer, RegisterSerializer
|
from .serializers import UserSerializer, RegisterSerializer
|
||||||
|
from django.contrib.auth import password_validation
|
||||||
|
|
||||||
class RegisterView(generics.CreateAPIView):
|
class RegisterView(generics.CreateAPIView):
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.all()
|
||||||
@ -73,6 +74,41 @@ class UserPromoteView(APIView):
|
|||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
return Response({'error': 'Utilisateur non trouvé'}, status=404)
|
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):
|
class UserDeleteView(UserDetailView):
|
||||||
http_method_names = ['delete', 'options']
|
http_method_names = ['delete', 'options']
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,16 +1,89 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import LoadingCard from '@/components/LoadingCard';
|
import LoadingCard from '@/components/LoadingCard';
|
||||||
import MainCard from '@/components/MainCard';
|
import MainCard from '@/components/MainCard';
|
||||||
import { useAuth } from '../context/useAuth';
|
import { useAuth } from '../context/useAuth';
|
||||||
import {
|
import { CardContent } from '@/components/ui/card';
|
||||||
CardContent,
|
|
||||||
} from '@/components/ui/card';
|
|
||||||
import { Badge } from '@/components/ui/badge';
|
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 { Separator } from '@/components/ui/separator';
|
||||||
import { User } from 'lucide-react';
|
import { User } from 'lucide-react';
|
||||||
|
import api from '../api/axios'; // Adapte ce chemin si ton instance Axios est ailleurs.
|
||||||
|
|
||||||
const MyspacePage = () => {
|
const MyspacePage = () => {
|
||||||
const { user } = useAuth();
|
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) {
|
if (!user) {
|
||||||
return (
|
return (
|
||||||
@ -24,25 +97,24 @@ const MyspacePage = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MainCard title={user.first_name} icon={User}>
|
<MainCard title={user.first_name} icon={User}>
|
||||||
|
|
||||||
<CardContent className="overflow-y-auto">
|
<CardContent className="overflow-y-auto">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between gap-4">
|
||||||
<span className="font-medium">Prénom:</span>
|
<span className="font-medium">Prénom :</span>
|
||||||
<span>{user.first_name}</span>
|
<span>{user.first_name}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between gap-4">
|
||||||
<span className="font-medium">Nom:</span>
|
<span className="font-medium">Nom :</span>
|
||||||
<span>{user.last_name}</span>
|
<span>{user.last_name}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between gap-4">
|
||||||
<span className="font-medium">Admin:</span>
|
<span className="font-medium">Admin :</span>
|
||||||
<Badge variant={user.is_admin ? 'default' : 'secondary'}>
|
<Badge variant={user.is_admin ? 'default' : 'secondary'}>
|
||||||
{user.is_admin ? 'Oui' : 'Non'}
|
{user.is_admin ? 'Oui' : 'Non'}
|
||||||
</Badge>
|
</Badge>
|
||||||
@ -50,23 +122,122 @@ const MyspacePage = () => {
|
|||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between gap-4">
|
||||||
<span className="font-medium">Nom d'utilisateur:</span>
|
<span className="font-medium">Nom d'utilisateur :</span>
|
||||||
<span>{user.username}</span>
|
<span>{user.username}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between gap-4">
|
||||||
<span className="font-medium">Date d'inscription:</span>
|
<span className="font-medium">Date d'inscription :</span>
|
||||||
<span>
|
<span>
|
||||||
{user.date_joined
|
{user.date_joined
|
||||||
? new Date(user.date_joined).toLocaleDateString('fr-FR')
|
? new Date(user.date_joined).toLocaleDateString('fr-FR')
|
||||||
: 'Non spécifié'}
|
: 'Non spécifié'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</CardContent>
|
</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>
|
</MainCard>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user