131 lines
3.3 KiB
TypeScript
131 lines
3.3 KiB
TypeScript
import React from 'react';
|
|
|
|
interface PasswordCriteria {
|
|
label: string;
|
|
isValid: boolean;
|
|
regex?: RegExp;
|
|
checkFn?: (password: string) => boolean;
|
|
}
|
|
|
|
interface PasswordCriteriaProps {
|
|
password: string;
|
|
className?: string;
|
|
showOnlyFailed?: boolean;
|
|
}
|
|
|
|
export const PasswordCriteria: React.FC<PasswordCriteriaProps> = ({
|
|
password,
|
|
className = '',
|
|
showOnlyFailed = false
|
|
}) => {
|
|
const criteria: PasswordCriteria[] = [
|
|
{
|
|
label: 'Al menos 8 caracteres',
|
|
isValid: password.length >= 8,
|
|
checkFn: (pwd) => pwd.length >= 8
|
|
},
|
|
{
|
|
label: 'Máximo 128 caracteres',
|
|
isValid: password.length <= 128,
|
|
checkFn: (pwd) => pwd.length <= 128
|
|
},
|
|
{
|
|
label: 'Al menos una letra mayúscula',
|
|
isValid: /[A-Z]/.test(password),
|
|
regex: /[A-Z]/
|
|
},
|
|
{
|
|
label: 'Al menos una letra minúscula',
|
|
isValid: /[a-z]/.test(password),
|
|
regex: /[a-z]/
|
|
},
|
|
{
|
|
label: 'Al menos un número',
|
|
isValid: /\d/.test(password),
|
|
regex: /\d/
|
|
}
|
|
];
|
|
|
|
const validatedCriteria = criteria.map(criterion => ({
|
|
...criterion,
|
|
isValid: criterion.regex
|
|
? criterion.regex.test(password)
|
|
: criterion.checkFn
|
|
? criterion.checkFn(password)
|
|
: false
|
|
}));
|
|
|
|
const displayCriteria = showOnlyFailed
|
|
? validatedCriteria.filter(c => !c.isValid)
|
|
: validatedCriteria;
|
|
|
|
if (displayCriteria.length === 0) return null;
|
|
|
|
return (
|
|
<div className={`text-sm space-y-1 ${className}`}>
|
|
<p className="text-text-secondary font-medium mb-2">
|
|
Requisitos de contraseña:
|
|
</p>
|
|
<ul className="space-y-1">
|
|
{displayCriteria.map((criterion, index) => (
|
|
<li key={index} className="flex items-center space-x-2">
|
|
<span
|
|
className={`w-4 h-4 rounded-full flex items-center justify-center text-xs font-bold ${
|
|
criterion.isValid
|
|
? 'bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300'
|
|
: 'bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300'
|
|
}`}
|
|
>
|
|
{criterion.isValid ? '✓' : '✗'}
|
|
</span>
|
|
<span
|
|
className={`${
|
|
criterion.isValid
|
|
? 'text-green-700 dark:text-green-300'
|
|
: 'text-red-700 dark:text-red-300'
|
|
}`}
|
|
>
|
|
{criterion.label}
|
|
</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export const validatePassword = (password: string): boolean => {
|
|
return (
|
|
password.length >= 8 &&
|
|
password.length <= 128 &&
|
|
/[A-Z]/.test(password) &&
|
|
/[a-z]/.test(password) &&
|
|
/\d/.test(password)
|
|
);
|
|
};
|
|
|
|
export const getPasswordErrors = (password: string): string[] => {
|
|
const errors: string[] = [];
|
|
|
|
if (password.length < 8) {
|
|
errors.push('La contraseña debe tener al menos 8 caracteres');
|
|
}
|
|
|
|
if (password.length > 128) {
|
|
errors.push('La contraseña no puede exceder 128 caracteres');
|
|
}
|
|
|
|
if (!/[A-Z]/.test(password)) {
|
|
errors.push('La contraseña debe contener al menos una letra mayúscula');
|
|
}
|
|
|
|
if (!/[a-z]/.test(password)) {
|
|
errors.push('La contraseña debe contener al menos una letra minúscula');
|
|
}
|
|
|
|
if (!/\d/.test(password)) {
|
|
errors.push('La contraseña debe contener al menos un número');
|
|
}
|
|
|
|
return errors;
|
|
}; |