import { useMemo } from 'react';
import { Shield, ShieldAlert, ShieldCheck } from 'lucide-react';
interface PasswordStrengthProps {
password: string;
}
export function usePasswordStrength(password: string) {
return useMemo(() => {
if (!password) return { score: 0, label: '', color: '' };
let score = 0;
const checks = {
length: password.length >= 8,
long: password.length >= 12,
veryLong: password.length >= 16,
hasUpper: /[A-Z]/.test(password),
hasLower: /[a-z]/.test(password),
hasNumber: /[0-9]/.test(password),
hasSpecial: /[^A-Za-z0-9]/.test(password),
mixedCase: /[A-Z]/.test(password) && /[a-z]/.test(password),
};
if (checks.length) score += 1;
if (checks.long) score += 1;
if (checks.veryLong) score += 1;
if (checks.hasUpper) score += 0.5;
if (checks.hasLower) score += 0.5;
if (checks.hasNumber) score += 1;
if (checks.hasSpecial) score += 1;
if (checks.mixedCase) score += 0.5;
// Cap at 5
score = Math.min(5, score);
const levels = [
{ label: '', color: '' },
{ label: 'Very weak', color: 'bg-red-500' },
{ label: 'Weak', color: 'bg-orange-500' },
{ label: 'Fair', color: 'bg-yellow-500' },
{ label: 'Good', color: 'bg-blue-500' },
{ label: 'Strong', color: 'bg-green-500' },
];
return {
score,
...levels[Math.floor(score)],
checks,
};
}, [password]);
}
export default function PasswordStrength({ password }: PasswordStrengthProps) {
const { score, label, color, checks } = usePasswordStrength(password);
if (score === 0) return null;
const bars = Array.from({ length: 5 }, (_, i) => (
<div
key={i}
className={`h-1.5 flex-1 rounded-full transition-all duration-300 ${
i < Math.floor(score) ? color : 'bg-gray-200'
}`}
/>
));
return (
<div className="mt-3 space-y-2">
{/* Strength bar */}
<div className="flex items-center gap-2">
<div className="flex gap-1 flex-1">{bars}</div>
<span
className={`text-xs font-medium ${
score <= 1
? 'text-red-600'
: score <= 2
? 'text-orange-600'
: score <= 3
? 'text-yellow-600'
: score <= 4
? 'text-blue-600'
: 'text-green-600'
}`}
>
{label}
</span>
</div>
{/* Requirements checklist */}
{password && (
<div className="grid grid-cols-2 gap-1 text-xs text-gray-500">
<Requirement met={checks.length} label="8+ characters" />
<Requirement met={checks.long} label="12+ characters" />
<Requirement met={checks.hasUpper} label="Uppercase" />
<Requirement met={checks.hasLower} label="Lowercase" />
<Requirement met={checks.hasNumber} label="Number" />
<Requirement met={checks.hasSpecial} label="Special char" />
</div>
)}
</div>
);
}
function Requirement({ met, label }: { met: boolean; label: string }) {
const Icon = met ? ShieldCheck : ShieldAlert;
return (
<span className={`inline-flex items-center gap-1 ${met ? 'text-green-600' : 'text-gray-400'}`}>
<Icon className="h-3 w-3" />
<span>{label}</span>
</span>
);
}