New components: - RegisterForm — name, email, password, confirm, terms, password strength - ForgotPasswordForm — email input with success/error states, back link - ResetPasswordForm — new password + confirm with strength indicator - VerifyEmailForm — 6-digit code input with resend, numeric-only filter - OnboardingShell — step indicator, progress bar, back/next/complete nav - AuthPageLayout — full-page centered card with product branding - PasswordStrengthBar — visual bar + label (weak/fair/good/strong) Existing components preserved: LoginForm, MfaChallenge, SocialButtons All styled via --bl-* CSS custom properties for product theming 54 tests (13 existing + 41 new) — all passing
68 lines
2.0 KiB
TypeScript
68 lines
2.0 KiB
TypeScript
import { useMemo } from 'react';
|
|
import type { PasswordStrength } from './types.js';
|
|
|
|
interface PasswordStrengthBarProps {
|
|
password: string;
|
|
className?: string;
|
|
}
|
|
|
|
const STRENGTH_CONFIG: Record<PasswordStrength, { color: string; label: string }> = {
|
|
weak: { color: 'var(--bl-error, #dc3545)', label: 'Weak' },
|
|
fair: { color: 'var(--bl-warning, #f59e0b)', label: 'Fair' },
|
|
good: { color: 'var(--bl-info, #3b82f6)', label: 'Good' },
|
|
strong: { color: 'var(--bl-success, #22c55e)', label: 'Strong' },
|
|
};
|
|
|
|
export function getPasswordStrength(password: string): PasswordStrength {
|
|
let score = 0;
|
|
if (password.length >= 8) score++;
|
|
if (password.length >= 12) score++;
|
|
if (/[A-Z]/.test(password)) score++;
|
|
if (/[a-z]/.test(password)) score++;
|
|
if (/\d/.test(password)) score++;
|
|
if (/[^A-Za-z0-9]/.test(password)) score++;
|
|
|
|
if (score <= 2) return 'weak';
|
|
if (score <= 3) return 'fair';
|
|
if (score <= 4) return 'good';
|
|
return 'strong';
|
|
}
|
|
|
|
export function PasswordStrengthBar({ password, className }: PasswordStrengthBarProps) {
|
|
const strength = useMemo(() => getPasswordStrength(password), [password]);
|
|
const config = STRENGTH_CONFIG[strength];
|
|
const widthPercent = { weak: 25, fair: 50, good: 75, strong: 100 }[strength];
|
|
|
|
if (!password) return null;
|
|
|
|
return (
|
|
<div className={className} data-testid="bl-password-strength">
|
|
<div
|
|
style={{
|
|
height: '4px',
|
|
borderRadius: '2px',
|
|
background: 'var(--bl-border, #e5e7eb)',
|
|
overflow: 'hidden',
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
height: '100%',
|
|
width: `${widthPercent}%`,
|
|
background: config.color,
|
|
transition: 'width 0.2s, background 0.2s',
|
|
borderRadius: '2px',
|
|
}}
|
|
data-testid="bl-password-strength-fill"
|
|
/>
|
|
</div>
|
|
<div
|
|
style={{ fontSize: '12px', color: config.color, marginTop: '4px' }}
|
|
data-testid="bl-password-strength-label"
|
|
>
|
|
{config.label}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|