diff --git a/frontend/src/pages/admin/UsersSection.jsx b/frontend/src/pages/admin/UsersSection.jsx index 0928a26..8b1faaa 100644 --- a/frontend/src/pages/admin/UsersSection.jsx +++ b/frontend/src/pages/admin/UsersSection.jsx @@ -3,6 +3,7 @@ import * as XLSX from 'xlsx'; import { api } from '../../api.js'; import ConfirmModal from '../../components/ConfirmModal.jsx'; import Modal from '../../components/Modal.jsx'; +import ResultBanner from '../../components/ResultBanner.jsx'; import { fmt, Badge, UserStatusBadge } from './adminHelpers.jsx'; // ── Helpers ──────────────────────────────────────────────────────────────── @@ -191,7 +192,7 @@ function PBtn({ onClick, disabled, active, children }) { // ── Composant principal ──────────────────────────────────────────────────── // ── Modale invitation ────────────────────────────────────────────────────── -function InviteUserModal({ open, onClose }) { +function InviteUserModal({ open, onClose, onSent }) { const [form, setForm] = useState({ email: '', role: 'user' }); const [loading, setLoading] = useState(false); const [err, setErr] = useState(null); @@ -207,6 +208,7 @@ function InviteUserModal({ open, onClose }) { try { await api.post('/admin/invitations', { email: form.email, role: form.role }); setSent(true); + onSent?.(); } catch (e) { setErr(e.message); } finally { setLoading(false); } }; @@ -262,6 +264,65 @@ function InviteUserModal({ open, onClose }) { ); } +// ── Modale renvoi invitation ─────────────────────────────────────────────── +function ResendInviteModal({ user, onClose, onSent }) { + const [loading, setLoading] = useState(false); + const [err, setErr] = useState(null); + const [sent, setSent] = useState(false); + + const handleClose = () => { setSent(false); setErr(null); onClose(); }; + + const handleSend = async () => { + setLoading(true); setErr(null); + try { + await api.post('/admin/invitations', { email: user.email, role: user.role }); + setSent(true); + onSent?.(); + } catch (e) { setErr(e.message); } + finally { setLoading(false); } + }; + + const identity = user?.display_name ? `${user.display_name} (${user.email})` : user?.email; + + return ( + Fermer + ) : ( + <> + + + + )} + > + {sent ? ( +
+
+ + + +
+

Invitation renvoyée !

+

+ Un nouveau lien a été envoyé à {user?.email}.
+ L'ancien lien a été révoqué. Le nouveau est valable 7 jours. +

+
+ ) : ( + <> + {err &&
{err}
} +

+ Renvoyer un nouveau lien d'invitation à {identity} ? +
L'ancien lien sera révoqué et remplacé par un nouveau valable 7 jours. +

+ + )} +
+ ); +} + // ── Modale création utilisateur ──────────────────────────────────────────── function CreateUserModal({ open, onClose, onCreated }) { const [form, setForm] = useState({ email: '', password: '', displayName: '', role: 'user' }); @@ -334,6 +395,7 @@ export default function UsersSection({ currentUserId }) { const [confirmAction, setConfirmAction] = useState(null); const [showCreate, setShowCreate] = useState(false); const [showInvite, setShowInvite] = useState(false); + const [resendTarget, setResendTarget] = useState(null); const [search, setSearch] = useState(''); const [filterStatus, setFilterStatus] = useState('all'); const [filterRole, setFilterRole] = useState('all'); @@ -382,18 +444,20 @@ export default function UsersSection({ currentUserId }) { const verifyEmail = u => confirm("Vérifier l'email", `Marquer l'email de ${u.display_name || u.email} comme vérifié ?`, async () => { await api.patch(`/admin/users/${u.id}/verify-email`, {}); load(); }, 'Confirmer'); - const resendVerif = async u => { - try { await api.post('/auth/resend-verification', { email: u.email }); alert(`Email envoyé à ${u.email}`); } - catch (e) { setErr(e.message); } - }; + const resendInvitation = u => setResendTarget(u); const setStatus = (u, status) => { const labels = { deactivated: 'Désactiver', active: 'Réactiver', locked: 'Verrouiller' }; confirm(`${labels[status]} le compte`, `${labels[status]} le compte de ${u.display_name || u.email} ?`, async () => { await api.patch(`/admin/users/${u.id}/status`, { status }); load(); }, labels[status]); }; - const deleteUser = u => confirm("Supprimer l'utilisateur", - `Supprimer définitivement ${u.display_name || u.email} ? Toutes ses données seront effacées.`, - async () => { await api.del(`/admin/users/${u.id}`); load(); }); + const deleteUser = u => { + const identity = u.display_name ? `${u.display_name} (${u.email})` : u.email; + confirm( + "Supprimer l'utilisateur", + `Supprimer définitivement le compte de ${identity} ? Cette action est irréversible et effacera toutes ses données.`, + async () => { await api.del(`/admin/users/${u.id}`); load(); } + ); + }; const openMenuFor = (e, u) => { e.stopPropagation(); @@ -606,8 +670,8 @@ export default function UsersSection({ currentUserId }) { }}> } label={openMenu.user.role === 'admin' ? '→ Utilisateur' : '→ Admin'} onClick={() => { setOpenMenu(null); toggleRole(openMenu.user); }} /> {!openMenu.user.email_verified && <> + } label="Renvoyer l'invitation" onClick={() => { setOpenMenu(null); resendInvitation(openMenu.user); }} /> } label="Vérifier l'email" onClick={() => { setOpenMenu(null); verifyEmail(openMenu.user); }} /> - } label="Renvoyer la vérification" onClick={() => { setOpenMenu(null); resendVerif(openMenu.user); }} /> } {openMenu.user.id !== currentUserId && <> {(!openMenu.user.status || openMenu.user.status === 'active') && } label="Désactiver" onClick={() => { setOpenMenu(null); setStatus(openMenu.user, 'deactivated'); }} />} @@ -623,6 +687,13 @@ export default function UsersSection({ currentUserId }) { setShowInvite(false)} + onSent={() => load()} + /> + + setResendTarget(null)} + onSent={() => load()} />