mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-06 00:15:32 +00:00
80 lines
2.9 KiB
TypeScript
80 lines
2.9 KiB
TypeScript
"use client";
|
|
|
|
import { useId } from "react";
|
|
import { useTranslations } from "next-intl";
|
|
import { Input } from "@/components/ui/input";
|
|
import { pinIsObvious, pinLongEnough } from "@/lib/app-lock/crypto";
|
|
|
|
/** The new PIN, twice. Says what's wrong only once there's something to say. */
|
|
export function NewPinFields({ pin, repeat, onPin, onRepeat }: {
|
|
pin: string;
|
|
repeat: string;
|
|
onPin: (value: string) => void;
|
|
onRepeat: (value: string) => void;
|
|
}) {
|
|
const t = useTranslations("appLock");
|
|
const pinId = useId();
|
|
const repeatId = useId();
|
|
const hintId = useId();
|
|
const tooShort = pin.length > 0 && !pinLongEnough(pin);
|
|
const obvious = pinLongEnough(pin) && pinIsObvious(pin);
|
|
const mismatch = repeat.length > 0 && repeat !== pin;
|
|
return (
|
|
<div className="space-y-3">
|
|
<div className="space-y-1">
|
|
<label htmlFor={pinId} className="text-sm font-medium text-foreground">{t("pin_new")}</label>
|
|
<Input
|
|
id={pinId}
|
|
data-testid="app-lock-new-pin"
|
|
type="password"
|
|
autoComplete="new-password"
|
|
value={pin}
|
|
onChange={(event) => onPin(event.target.value)}
|
|
aria-describedby={hintId}
|
|
aria-invalid={tooShort || obvious || undefined}
|
|
/>
|
|
<p id={hintId} className="text-xs text-muted-foreground">{t("pin_hint")}</p>
|
|
{obvious && <p role="alert" className="text-xs text-destructive">{t("pin_obvious")}</p>}
|
|
</div>
|
|
<div className="space-y-1">
|
|
<label htmlFor={repeatId} className="text-sm font-medium text-foreground">{t("pin_repeat")}</label>
|
|
<Input
|
|
id={repeatId}
|
|
data-testid="app-lock-repeat-pin"
|
|
type="password"
|
|
autoComplete="new-password"
|
|
value={repeat}
|
|
onChange={(event) => onRepeat(event.target.value)}
|
|
aria-invalid={mismatch || undefined}
|
|
/>
|
|
{mismatch && <p role="alert" className="text-xs text-destructive">{t("pin_mismatch")}</p>}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function newPinReady(pin: string, repeat: string): boolean {
|
|
return pinLongEnough(pin) && !pinIsObvious(pin) && pin === repeat;
|
|
}
|
|
|
|
/** A PIN already set, asked again before something that needs it. */
|
|
export function CurrentPinField({ value, onChange, error }: { value: string; onChange: (value: string) => void; error?: string | null }) {
|
|
const t = useTranslations("appLock");
|
|
const id = useId();
|
|
return (
|
|
<div className="space-y-1">
|
|
<label htmlFor={id} className="text-sm font-medium text-foreground">{t("pin_current")}</label>
|
|
<Input
|
|
id={id}
|
|
data-testid="app-lock-current-pin"
|
|
type="password"
|
|
autoComplete="off"
|
|
value={value}
|
|
onChange={(event) => onChange(event.target.value)}
|
|
aria-invalid={error ? true : undefined}
|
|
/>
|
|
{error && <p role="alert" className="text-xs text-destructive">{error}</p>}
|
|
</div>
|
|
);
|
|
}
|