veza/apps/web/src/features/auth/components/PasswordStrengthIndicator.tsx

116 lines
2.9 KiB
TypeScript
Raw Normal View History

interface PasswordStrengthIndicatorProps {
password: string;
}
2025-12-13 02:34:34 +00:00
export function PasswordStrengthIndicator({
password,
}: PasswordStrengthIndicatorProps) {
const getStrength = (
pwd: string,
): {
level: number;
label: string;
color: string;
requirements: string[];
} => {
const requirements: string[] = [];
let strength = 0;
2025-12-13 02:34:34 +00:00
// Minimum 12 caractères (requis)
if (pwd.length >= 12) {
strength++;
} else {
requirements.push(`Au moins 12 caractères (${pwd.length}/12)`);
}
2025-12-13 02:34:34 +00:00
// Majuscule et minuscule
if (/[a-z]/.test(pwd) && /[A-Z]/.test(pwd)) {
strength++;
} else {
if (!/[a-z]/.test(pwd)) requirements.push('Une minuscule');
if (!/[A-Z]/.test(pwd)) requirements.push('Une majuscule');
}
2025-12-13 02:34:34 +00:00
// Chiffre
if (/\d/.test(pwd)) {
strength++;
} else {
requirements.push('Un chiffre');
}
2025-12-13 02:34:34 +00:00
// Caractère spécial
if (/[^a-zA-Z\d]/.test(pwd)) {
strength++;
} else {
requirements.push('Un caractère spécial (!@#$%^&*...)');
}
let label: string;
let color: string;
if (strength <= 1) {
label = 'Très faible';
color = 'bg-kodo-red';
} else if (strength === 2) {
label = 'Faible';
color = 'bg-orange-500';
} else if (strength === 3) {
label = 'Moyen';
color = 'bg-yellow-500';
} else if (strength === 4) {
label = 'Fort';
color = 'bg-kodo-lime/100';
} else {
label = 'Très fort';
color = 'bg-green-600';
}
2025-12-13 02:34:34 +00:00
return { level: strength, label, color, requirements };
};
if (!password) return null;
const { level, label, color, requirements } = getStrength(password);
const width = (level / 4) * 100;
return (
2025-12-13 02:34:34 +00:00
<div
className="mt-2 space-y-2"
role="status"
aria-live="polite"
aria-atomic="true"
>
<div>
2025-12-13 02:34:34 +00:00
<div
className="w-full bg-kodo-slate rounded-full h-2"
role="progressbar"
aria-valuenow={level}
aria-valuemin={0}
aria-valuemax={4}
aria-label={`Force du mot de passe: ${label}`}
>
<div
className={`${color} h-2 rounded-full transition-all`}
style={{ width: `${width}%` }}
aria-hidden="true"
/>
</div>
<p className="text-xs text-kodo-content-dim mt-1" id="password-strength-label">
Force: <span aria-live="polite">{label}</span>
</p>
</div>
{requirements.length > 0 && (
<div className="text-xs text-kodo-content-dim">
<p className="font-medium mb-1">Requis :</p>
<ul className="list-disc list-inside space-y-0.5">
{requirements.map((req, index) => (
<li key={index} className="text-kodo-red">
2025-12-13 02:34:34 +00:00
{req}
</li>
))}
</ul>
</div>
)}
</div>
);
}