cx-nexredirect/components/PasswordField.tsx

71 lines
2.7 KiB
TypeScript
Raw Permalink Normal View History

"use client";
import { useEffect, useState } from "react";
import { Eye, EyeOff, Check, AlertTriangle, Loader2 } from "lucide-react";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
type Strength = { ok: boolean; reason?: string; checking?: boolean };
export function PasswordField({
value, onChange, onValidationChange, label = "Passwort", id = "password", autoComplete = "new-password", disabled,
}: {
value: string;
onChange: (v: string) => void;
onValidationChange?: (v: Strength) => void;
label?: string;
id?: string;
autoComplete?: string;
disabled?: boolean;
}) {
const [show, setShow] = useState(false);
const [strength, setStrength] = useState<Strength>({ ok: false });
useEffect(() => {
if (!value) { setStrength({ ok: false }); onValidationChange?.({ ok: false }); return; }
setStrength((s) => ({ ...s, checking: true }));
const t = setTimeout(async () => {
try {
const r = await fetch("/api/password-check", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ password: value }) });
const d = await r.json();
const next: Strength = { ok: !!d.ok, reason: d.reason };
setStrength(next);
onValidationChange?.(next);
} catch {
setStrength({ ok: false, reason: "Prüfung nicht möglich." });
}
}, 400);
return () => clearTimeout(t);
}, [value, onValidationChange]);
return (
<div className="space-y-2">
<Label htmlFor={id}>{label}</Label>
<div className="relative">
<Input
id={id}
type={show ? "text" : "password"}
value={value}
onChange={(e) => onChange(e.target.value)}
disabled={disabled}
autoComplete={autoComplete}
className="pr-10"
/>
<button type="button" onClick={() => setShow((s) => !s)} className="absolute right-2 top-1/2 -translate-y-1/2 text-zinc-500 hover:text-zinc-200" tabIndex={-1}>
{show ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</button>
</div>
{value && (
<div className="flex items-start gap-2 text-[11px]">
{strength.checking ? (
<><Loader2 className="h-3 w-3 animate-spin text-muted-foreground" /><span className="text-muted-foreground">prüfe</span></>
) : strength.ok ? (
<><Check className="h-3 w-3 text-green-400" /><span className="text-green-400">Passwort OK</span></>
) : (
<><AlertTriangle className="h-3 w-3 text-amber-400" /><span className="text-amber-400">{strength.reason || "zu schwach"}</span></>
)}
</div>
)}
</div>
);
}