190 lines
5.8 KiB
JavaScript
190 lines
5.8 KiB
JavaScript
/**
|
|
* Password Strength Indicator Component
|
|
* Shows password strength and validation requirements
|
|
*/
|
|
|
|
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
|
|
export default function PasswordStrengthIndicator({ password = '', showRequirements = true }) {
|
|
const [strength, setStrength] = useState(0);
|
|
const [requirements, setRequirements] = useState({
|
|
length: false,
|
|
maxLength: false,
|
|
uppercase: false,
|
|
lowercase: false,
|
|
number: false
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (!password) {
|
|
setStrength(0);
|
|
setRequirements({
|
|
length: false,
|
|
uppercase: false,
|
|
lowercase: false,
|
|
number: false
|
|
});
|
|
return;
|
|
}
|
|
|
|
const newRequirements = {
|
|
length: password.length >= 8,
|
|
maxLength: password.length <= 128,
|
|
uppercase: /[A-Z]/.test(password),
|
|
lowercase: /[a-z]/.test(password),
|
|
number: /\d/.test(password)
|
|
};
|
|
|
|
setRequirements(newRequirements);
|
|
|
|
// Calculate strength based on both requirements and length
|
|
const requirementsMet = Object.values(newRequirements).filter(Boolean).length;
|
|
let strengthValue = requirementsMet;
|
|
|
|
// Adjust strength based on password length
|
|
if (password.length >= 12) {
|
|
strengthValue = Math.min(5, strengthValue + 1); // Bonus for long passwords
|
|
} else if (password.length >= 8 && password.length < 10) {
|
|
strengthValue = Math.max(1, strengthValue - 1); // Penalty for short passwords
|
|
} else if (password.length < 8) {
|
|
strengthValue = Math.max(0, strengthValue - 2); // Big penalty for very short passwords
|
|
}
|
|
|
|
// Ensure strength is between 0 and 5
|
|
strengthValue = Math.max(0, Math.min(5, strengthValue));
|
|
|
|
setStrength(strengthValue);
|
|
}, [password]);
|
|
|
|
const getStrengthColor = () => {
|
|
switch (strength) {
|
|
case 0:
|
|
case 1:
|
|
return 'bg-red-500';
|
|
case 2:
|
|
return 'bg-orange-500';
|
|
case 3:
|
|
return 'bg-yellow-500';
|
|
case 4:
|
|
return 'bg-blue-500';
|
|
case 5:
|
|
return 'bg-green-500';
|
|
default:
|
|
return 'bg-gray-500';
|
|
}
|
|
};
|
|
|
|
const getStrengthText = () => {
|
|
switch (strength) {
|
|
case 0:
|
|
return 'Très faible';
|
|
case 1:
|
|
return 'Faible';
|
|
case 2:
|
|
return 'Moyen';
|
|
case 3:
|
|
return 'Bon';
|
|
case 4:
|
|
return 'Très bon';
|
|
case 5:
|
|
return 'Excellent';
|
|
default:
|
|
return '';
|
|
}
|
|
};
|
|
|
|
const getStrengthTextColor = () => {
|
|
switch (strength) {
|
|
case 0:
|
|
case 1:
|
|
return 'text-red-700 dark:text-red-400';
|
|
case 2:
|
|
return 'text-orange-700 dark:text-orange-400';
|
|
case 3:
|
|
return 'text-yellow-700 dark:text-yellow-500';
|
|
case 4:
|
|
return 'text-blue-700 dark:text-blue-400';
|
|
case 5:
|
|
return 'text-green-700 dark:text-green-400';
|
|
default:
|
|
return 'text-neutral-600 dark:text-neutral-400';
|
|
}
|
|
};
|
|
|
|
if (!password && !showRequirements) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<div className="mt-2 space-y-2">
|
|
{/* Strength Bar */}
|
|
{password && (
|
|
<div className="space-y-1">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-xs text-neutral-600 dark:text-neutral-400">Force du mot de passe :</span>
|
|
<span className={`text-xs font-medium ${getStrengthTextColor()}`}>
|
|
{getStrengthText()}
|
|
</span>
|
|
</div>
|
|
<div className="w-full bg-neutral-200 dark:bg-neutral-700 rounded-full h-1.5">
|
|
<div
|
|
className={`h-1.5 rounded-full transition-all duration-300 ${getStrengthColor()}`}
|
|
style={{ width: `${(strength / 5) * 100}%` }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Requirements - Only show if not all requirements are met */}
|
|
{showRequirements && password && !Object.values(requirements).every(Boolean) && (
|
|
<div className="space-y-1">
|
|
<div className="space-y-1">
|
|
{!requirements.length && (
|
|
<div className="flex items-center space-x-2">
|
|
<div className="w-1.5 h-1.5 rounded-full flex-shrink-0 bg-red-500" />
|
|
<span className="text-xs text-red-700 dark:text-red-400">
|
|
Au moins 8 caractères
|
|
</span>
|
|
</div>
|
|
)}
|
|
{!requirements.maxLength && (
|
|
<div className="flex items-center space-x-2">
|
|
<div className="w-1.5 h-1.5 rounded-full flex-shrink-0 bg-red-500" />
|
|
<span className="text-xs text-red-700 dark:text-red-400">
|
|
Maximum 128 caractères
|
|
</span>
|
|
</div>
|
|
)}
|
|
{!requirements.uppercase && (
|
|
<div className="flex items-center space-x-2">
|
|
<div className="w-1.5 h-1.5 rounded-full flex-shrink-0 bg-red-500" />
|
|
<span className="text-xs text-red-700 dark:text-red-400">
|
|
Au moins une majuscule
|
|
</span>
|
|
</div>
|
|
)}
|
|
{!requirements.lowercase && (
|
|
<div className="flex items-center space-x-2">
|
|
<div className="w-1.5 h-1.5 rounded-full flex-shrink-0 bg-red-500" />
|
|
<span className="text-xs text-red-700 dark:text-red-400">
|
|
Au moins une minuscule
|
|
</span>
|
|
</div>
|
|
)}
|
|
{!requirements.number && (
|
|
<div className="flex items-center space-x-2">
|
|
<div className="w-1.5 h-1.5 rounded-full flex-shrink-0 bg-red-500" />
|
|
<span className="text-xs text-red-700 dark:text-red-400">
|
|
Au moins un chiffre
|
|
</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|