import { useEffect, useRef, useState } from 'react'; import { PasswordInput } from './PasswordInput.js'; import { useSessionAuth } from './session-auth.js'; // Small modal that asks for the session PIN before letting a // sensitive action proceed. Currently used by Send when the // "Require PIN on send" security setting is on. // // Verifies the PIN via session-auth.unlockWithPin — same code path // as unlocking, so the same lockout budget applies: three wrong // PINs will lock the session (the master password is then needed // to unlock and restore the PIN). That's a deliberate security // posture, not a bug — a stranger with the unlocked wallet gets // three tries before the master password is re-required. export function PinConfirmModal({ title, message, onConfirm, onCancel, }: { title: string; message?: string; onConfirm: () => void; onCancel: () => void; }): JSX.Element { const { unlockWithPin, pinAttemptsLeft } = useSessionAuth(); const [pin, setPin] = useState(''); const [busy, setBusy] = useState(false); const [err, setErr] = useState(null); const inputRef = useRef(null); useEffect(() => { inputRef.current?.focus(); }, []); async function submit(): Promise { if (pin.length < 4 || busy) return; setBusy(true); setErr(null); try { const ok = await unlockWithPin(pin); if (ok) { onConfirm(); return; } // unlockWithPin returned false → wrong PIN. The session-auth // hook already decremented the attempt budget; if the last // attempt was used it also flipped state to 'locked', which // AppShell will render as the master-password screen — this // modal will unmount naturally. setPin(''); setErr(`Wrong PIN. ${Math.max(0, pinAttemptsLeft - 1)} attempt${pinAttemptsLeft - 1 === 1 ? '' : 's'} left before the session locks.`); } finally { setBusy(false); } } // Reuses the shared .modal-backdrop + .modal chrome from styles.css // (defined for the Receive modal). PIN-specific rows below are the // only new visual surface. return (
{ if (e.target === e.currentTarget) onCancel(); }} >

{title}

{message &&

{message}

} { setPin(e.target.value); setErr(null); }} onKeyDown={(e) => { if (e.key === 'Enter') void submit(); if (e.key === 'Escape') onCancel(); }} maxLength={12} autoComplete="off" style={{ padding: '10px 12px', fontSize: 15, letterSpacing: '0.2em' }} /> {err &&
{err}
}
); }