feat(admin): add password management to user edit modal and profile page
- add new password field in UserEditModal with optional admin-set password on save - add send password reset link button with loading state in UserEditModal - add password change section with strength indicator in ProfilePage - expose sendPasswordResetEmail utility in auth api
This commit is contained in:
@@ -12,8 +12,9 @@ const UserEditModal = ({ userId, currentUserId, isOpen, onClose, onSaved }) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
const [formData, setFormData] = useState({ name: '', email: '', currentPassword: '' });
|
||||
const [formData, setFormData] = useState({ name: '', email: '', currentPassword: '', newPassword: '' });
|
||||
const [errors, setErrors] = useState({});
|
||||
const [sendingReset, setSendingReset] = useState(false);
|
||||
|
||||
const [allRoles, setAllRoles] = useState([]);
|
||||
const [selectedRoleIds, setSelectedRoleIds] = useState([]);
|
||||
@@ -70,6 +71,23 @@ const UserEditModal = ({ userId, currentUserId, isOpen, onClose, onSaved }) => {
|
||||
if (errors[field]) setErrors(prev => ({ ...prev, [field]: null }));
|
||||
};
|
||||
|
||||
const handleSendPasswordReset = async () => {
|
||||
setSendingReset(true);
|
||||
try {
|
||||
const res = await fetch(`/zen/api/users/${userId}/send-password-reset`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.error || data.message || 'Impossible d\'envoyer le lien');
|
||||
toast.success(data.message || 'Lien de réinitialisation envoyé');
|
||||
} catch {
|
||||
toast.error('Impossible d\'envoyer le lien de réinitialisation');
|
||||
} finally {
|
||||
setSendingReset(false);
|
||||
}
|
||||
};
|
||||
|
||||
const emailChanged = userData && formData.email !== userData.email;
|
||||
|
||||
const validate = () => {
|
||||
@@ -156,6 +174,23 @@ const UserEditModal = ({ userId, currentUserId, isOpen, onClose, onSaved }) => {
|
||||
toast.success('Utilisateur mis à jour');
|
||||
}
|
||||
|
||||
if (formData.newPassword) {
|
||||
const pwdRes = await fetch(`/zen/api/users/${userId}/password`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ newPassword: formData.newPassword }),
|
||||
});
|
||||
const pwdData = await pwdRes.json();
|
||||
if (!pwdRes.ok) {
|
||||
toast.error(pwdData.error || pwdData.message || 'Impossible de changer le mot de passe');
|
||||
onSaved?.();
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
toast.success('Mot de passe mis à jour');
|
||||
}
|
||||
|
||||
onSaved?.();
|
||||
onClose();
|
||||
} catch {
|
||||
@@ -219,6 +254,27 @@ const UserEditModal = ({ userId, currentUserId, isOpen, onClose, onSaved }) => {
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="border-t border-neutral-200 dark:border-neutral-700 pt-4 flex flex-col gap-3">
|
||||
<Input
|
||||
label="Nouveau mot de passe (optionnel)"
|
||||
type="password"
|
||||
value={formData.newPassword}
|
||||
onChange={(value) => handleInputChange('newPassword', value)}
|
||||
placeholder="Laisser vide pour ne pas modifier"
|
||||
description="Définir un nouveau mot de passe pour cet utilisateur"
|
||||
/>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSendPasswordReset}
|
||||
disabled={sendingReset}
|
||||
className="text-xs text-neutral-500 hover:text-neutral-800 dark:hover:text-neutral-200 underline transition-colors disabled:opacity-50"
|
||||
>
|
||||
{sendingReset ? 'Envoi en cours…' : 'Envoyer un lien de réinitialisation par courriel'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TagInput
|
||||
label="Rôles attribués"
|
||||
options={roleOptions}
|
||||
|
||||
Reference in New Issue
Block a user