import { useState } from 'react'; import { Loader2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { useLockActions } from '../useWalletData'; // Re-encrypt the seed under a new passphrase. The sidecar decrypts with the old one and re-seals with the // new one, then re-locks — there is no window where the wallet is left open as a side effect. // // Both passphrases live only in this component's state and are cleared as soon as the request settles. type ChangePassphraseDialogProps = { open: boolean; onOpenChange: (open: boolean) => void; walletId: number }; export const ChangePassphraseDialog = ({ open, onOpenChange, walletId }: ChangePassphraseDialogProps) => { const { changePassphrase } = useLockActions(walletId); // Never persisted: no localStorage, no sessionStorage, no React Query cache, no URL. const [oldPassphrase, setOldPassphrase] = useState(''); const [newPassphrase, setNewPassphrase] = useState(''); const [confirm, setConfirm] = useState(''); const clearSecrets = () => { setOldPassphrase(''); setNewPassphrase(''); setConfirm(''); }; const close = () => { clearSecrets(); onOpenChange(false); }; const mismatch = confirm.length > 0 && confirm !== newPassphrase; const canSubmit = !!oldPassphrase && newPassphrase.length >= 8 && confirm === newPassphrase && !changePassphrase.isPending; const submit = async (ev: React.FormEvent) => { ev.preventDefault(); if (!canSubmit) return; try { await changePassphrase.mutateAsync({ oldPassphrase, newPassphrase }); close(); } finally { // Cleared on failure too — a rejected passphrase is still a passphrase held in memory. clearSecrets(); } }; return ( (next ? onOpenChange(true) : close())}>
Change passphrase The seed is re-sealed under the new passphrase and the wallet is locked again. Lose it and the coins go with it — there is no reset.
{/* Controlled input → request body → cleared. Never written to any store. */} setOldPassphrase(ev.target.value)} />
{/* Controlled input → request body → cleared. Never written to any store. */} setNewPassphrase(ev.target.value)} />

At least 8 characters.

{/* Controlled input → request body → cleared. Never written to any store. */} setConfirm(ev.target.value)} /> {mismatch &&

These do not match.

}
); };