diff --git a/frontend/src/components/PasswordStrength.jsx b/frontend/src/components/PasswordStrength.jsx
new file mode 100644
index 0000000..40156ba
--- /dev/null
+++ b/frontend/src/components/PasswordStrength.jsx
@@ -0,0 +1,94 @@
+/**
+ * PasswordStrength — indicateur de complexité de mot de passe.
+ *
+ * Props :
+ * password : string — valeur en cours de saisie
+ *
+ * Affiche uniquement quand password est non vide.
+ */
+
+const RULES = [
+ { id: 'len', label: '8 caractères minimum', test: p => p.length >= 8 },
+ { id: 'upper', label: 'Une lettre majuscule (A–Z)', test: p => /[A-Z]/.test(p) },
+ { id: 'lower', label: 'Une lettre minuscule (a–z)', test: p => /[a-z]/.test(p) },
+ { id: 'digit', label: 'Un chiffre (0–9)', test: p => /[0-9]/.test(p) },
+ { id: 'special', label: 'Un caractère spécial (!@#…)', test: p => /[^A-Za-z0-9]/.test(p) },
+];
+
+const LEVELS = [
+ { min: 0, label: 'Très faible', color: '#ef4444' },
+ { min: 1, label: 'Faible', color: '#f97316' },
+ { min: 2, label: 'Moyen', color: '#eab308' },
+ { min: 3, label: 'Bon', color: '#84cc16' },
+ { min: 4, label: 'Fort', color: '#22c55e' },
+ { min: 5, label: 'Très fort', color: '#10b981' },
+];
+
+function getLevel(score) {
+ // score = nombre de règles respectées (0..5)
+ for (let i = LEVELS.length - 1; i >= 0; i--) {
+ if (score >= LEVELS[i].min) return LEVELS[i];
+ }
+ return LEVELS[0];
+}
+
+export default function PasswordStrength({ password }) {
+ if (!password) return null;
+
+ const results = RULES.map(r => ({ ...r, ok: r.test(password) }));
+ const score = results.filter(r => r.ok).length;
+ const level = getLevel(score);
+ const pct = Math.round((score / RULES.length) * 100);
+
+ return (
+
+ {/* Barre de progression */}
+
+
+ {/* Liste des règles */}
+
+ {results.map(r => (
+
+ {r.ok ? (
+
+
+
+ ) : (
+
+
+
+ )}
+ {r.label}
+
+ ))}
+
+
+ );
+}
diff --git a/frontend/src/pages/InvitationRegister.jsx b/frontend/src/pages/InvitationRegister.jsx
index ae14ada..9877b9f 100644
--- a/frontend/src/pages/InvitationRegister.jsx
+++ b/frontend/src/pages/InvitationRegister.jsx
@@ -1,4 +1,5 @@
import { useState, useEffect } from 'react';
+import PasswordStrength from '../components/PasswordStrength.jsx';
import { useParams, useNavigate, Link } from 'react-router-dom';
// Wrap défini en dehors du composant pour éviter le remontage à chaque frappe
@@ -173,6 +174,7 @@ export default function InvitationRegister() {
Confirmer le nouveau mot de passe
diff --git a/frontend/src/pages/Register.jsx b/frontend/src/pages/Register.jsx
index 3974260..d88c832 100644
--- a/frontend/src/pages/Register.jsx
+++ b/frontend/src/pages/Register.jsx
@@ -1,6 +1,7 @@
import { useState, useEffect } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { useAuth } from '../context/AuthContext.jsx';
+import PasswordStrength from '../components/PasswordStrength.jsx';
export default function Register() {
const { register } = useAuth();
@@ -205,7 +206,7 @@ export default function Register() {
{ setPassword(e.target.value); setStatus(null); }}
style={{ width: '100%' }}
/>
+
diff --git a/frontend/src/pages/admin/UsersSection.jsx b/frontend/src/pages/admin/UsersSection.jsx
index 27b0621..9284a36 100644
--- a/frontend/src/pages/admin/UsersSection.jsx
+++ b/frontend/src/pages/admin/UsersSection.jsx
@@ -4,6 +4,7 @@ import { api } from '../../api.js';
import ConfirmModal from '../../components/ConfirmModal.jsx';
import Modal from '../../components/Modal.jsx';
import ResultBanner from '../../components/ResultBanner.jsx';
+import PasswordStrength from '../../components/PasswordStrength.jsx';
import { fmt, Badge, UserStatusBadge } from './adminHelpers.jsx';
// ── Helpers ────────────────────────────────────────────────────────────────
@@ -356,6 +357,7 @@ function CreateUserModal({ open, onClose, onCreated }) {
Rôle