'use client'; import { useState, useEffect } from 'react'; import { Input, TagInput, Modal, RoleBadge } from '@zen/core/shared/components'; import { useToast } from '@zen/core/toast'; const UserEditModal = ({ userId, currentUserId, isOpen, onClose, onSaved }) => { const toast = useToast(); const isSelf = userId && currentUserId && userId === currentUserId; const [userData, setUserData] = useState(null); const [loading, setLoading] = useState(false); const [saving, setSaving] = useState(false); 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([]); const [initialRoleIds, setInitialRoleIds] = useState([]); useEffect(() => { if (!isOpen || !userId) return; loadAll(); }, [isOpen, userId]); const loadAll = async () => { try { setLoading(true); setErrors({}); const [userRes, rolesRes, userRolesRes] = await Promise.all([ fetch(`/zen/api/users/${userId}`, { credentials: 'include' }), fetch('/zen/api/roles', { credentials: 'include' }), fetch(`/zen/api/users/${userId}/roles`, { credentials: 'include' }), ]); const [userJson, rolesJson, userRolesJson] = await Promise.all([ userRes.json(), rolesRes.json(), userRolesRes.json(), ]); if (userJson.user) { setUserData(userJson.user); setFormData({ name: userJson.user.name || '', email: userJson.user.email || '', currentPassword: '', newPassword: '', }); } else { toast.error(userJson.message || 'Utilisateur introuvable'); onClose(); return; } setAllRoles(rolesJson.roles || []); const ids = (userRolesJson.roles || []).map(r => r.id); setSelectedRoleIds(ids); setInitialRoleIds(ids); } catch { toast.error("Impossible de charger l'utilisateur"); onClose(); } finally { setLoading(false); } }; const handleInputChange = (field, value) => { setFormData(prev => ({ ...prev, [field]: value })); 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 needsCurrentPassword = isSelf && (emailChanged || !!formData.newPassword); const validate = () => { const newErrors = {}; if (!formData.name?.trim()) newErrors.name = 'Le nom est requis'; if (needsCurrentPassword && !formData.currentPassword) newErrors.currentPassword = 'Le mot de passe actuel est requis'; setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleSubmit = async () => { if (!validate()) return; try { setSaving(true); const userRes = await fetch(`/zen/api/users/${userId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ name: formData.name.trim(), }), }); const userResData = await userRes.json(); if (!userRes.ok) { toast.error(userResData.message || userResData.error || "Impossible de mettre à jour l'utilisateur"); return; } const toAdd = selectedRoleIds.filter(id => !initialRoleIds.includes(id)); const toRemove = initialRoleIds.filter(id => !selectedRoleIds.includes(id)); await Promise.all([ ...toAdd.map(roleId => fetch(`/zen/api/users/${userId}/roles`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ roleId }), }) ), ...toRemove.map(roleId => fetch(`/zen/api/users/${userId}/roles/${roleId}`, { method: 'DELETE', credentials: 'include', }) ), ]); if (emailChanged) { if (isSelf) { const emailRes = await fetch('/zen/api/users/profile/email', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ newEmail: formData.email.trim(), password: formData.currentPassword }), }); const emailData = await emailRes.json(); if (!emailRes.ok) { toast.error(emailData.error || emailData.message || 'Impossible de changer le courriel'); onSaved?.(); onClose(); return; } toast.success('Utilisateur mis à jour'); toast.info(emailData.message || 'Un courriel de confirmation a été envoyé'); } else { const emailRes = await fetch(`/zen/api/users/${userId}/email`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ newEmail: formData.email.trim() }), }); const emailData = await emailRes.json(); if (!emailRes.ok) { toast.error(emailData.error || emailData.message || 'Impossible de changer le courriel'); onSaved?.(); onClose(); return; } toast.success('Utilisateur mis à jour'); } } else { toast.success('Utilisateur mis à jour'); } if (formData.newPassword) { const pwdUrl = isSelf ? '/zen/api/users/profile/password' : `/zen/api/users/${userId}/password`; const pwdMethod = isSelf ? 'POST' : 'PUT'; const pwdBody = isSelf ? { currentPassword: formData.currentPassword, newPassword: formData.newPassword } : { newPassword: formData.newPassword }; const pwdRes = await fetch(pwdUrl, { method: pwdMethod, headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify(pwdBody), }); 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 { toast.error("Impossible de mettre à jour l'utilisateur"); } finally { setSaving(false); } }; const roleOptions = allRoles.map(r => ({ value: r.id, label: r.name, color: r.color || '#6b7280', description: r.description || undefined, })); return ( {loading ? (
) : (
handleInputChange('name', value)} placeholder="Nom de l'utilisateur" error={errors.name} /> handleInputChange('email', value)} placeholder="courriel@exemple.com" />
( )} />
handleInputChange('newPassword', value)} placeholder="Laisser vide pour ne pas modifier" />
{needsCurrentPassword && ( handleInputChange('currentPassword', value)} placeholder="Votre mot de passe" error={errors.currentPassword} description={emailChanged && formData.newPassword ? 'Requis pour confirmer le changement de courriel et de mot de passe' : emailChanged ? 'Requis pour confirmer le changement de courriel' : 'Requis pour confirmer le changement de mot de passe'} /> )}
)} ); }; export default UserEditModal;