FastAPI + Celery + Next.js + Postgres/Redis app with company monitoring, source collection, LLM-based change analysis, enrichment, and account security (Turnstile, escalating lockout, email verification).
44 lines
1.5 KiB
TypeScript
44 lines
1.5 KiB
TypeScript
"use client";
|
|
|
|
const LEVELS = [
|
|
{ label: "Weak", bar: "bg-red-500", text: "text-red-600" },
|
|
{ label: "Fair", bar: "bg-amber-500", text: "text-amber-600" },
|
|
{ label: "Good", bar: "bg-brand-500", text: "text-brand-600" },
|
|
{ label: "Strong", bar: "bg-green-500", text: "text-green-600" },
|
|
] as const;
|
|
|
|
/** A purely visual affordance - length/variety heuristics scored 0-4, shown
|
|
* as an animated bar. Doesn't gate submission or loosen the real policy
|
|
* (still enforced by the password field's own Zod schema). */
|
|
function scorePassword(password: string): number {
|
|
if (!password) return 0;
|
|
let score = 0;
|
|
if (password.length >= 10) score += 1;
|
|
if (password.length >= 14) score += 1;
|
|
if (/[^a-zA-Z0-9]/.test(password)) score += 1;
|
|
if (/[a-z]/.test(password) && /[A-Z]/.test(password)) score += 1;
|
|
return Math.min(score, 4);
|
|
}
|
|
|
|
export function PasswordStrengthMeter({ password }: { password: string }) {
|
|
const score = scorePassword(password);
|
|
if (!password) return null;
|
|
|
|
const level = LEVELS[Math.max(score - 1, 0)] ?? LEVELS[0];
|
|
const widthPercent = (score / 4) * 100;
|
|
|
|
return (
|
|
<div className="mt-2 animate-fade-in">
|
|
<div className="h-1.5 w-full overflow-hidden rounded-full bg-slate-100">
|
|
<div
|
|
className={`h-full rounded-full transition-[width,background-color] duration-300 ${level.bar}`}
|
|
style={{ width: `${widthPercent}%` }}
|
|
/>
|
|
</div>
|
|
<p key={level.label} className={`mt-1 animate-fade-in text-xs ${level.text}`}>
|
|
{level.label}
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|