import { createSignal, createMemo, For, onCleanup, onMount, Show } from "solid-js"; import { sendMessage } from "../App"; import { PeachWordmark } from "../components/PeachMark"; import { RecoveryRuneBackground } from "../components/RecoveryRuneBackground"; interface RecoveryWordDisplayViewProps { onDone: () => void; } // After showing the words, user must type the 3 words at these positions const CONFIRM_WORD_INDICES = [2, 6, 10]; // 0-indexed → positions 3, 7, 11 export function RecoveryWordDisplayView(props: RecoveryWordDisplayViewProps) { const [words, setWords] = createSignal([]); const [error, setError] = createSignal(""); const [loading, setLoading] = createSignal(true); const [confirmWords, setConfirmWords] = createSignal(["", "", ""]); const [confirmError, setConfirmError] = createSignal(""); const [confirmed, setConfirmed] = createSignal(false); // 5-second countdown before confirmation input is enabled const [countdown, setCountdown] = createSignal(5); let countdownTimer: ReturnType | null = null; onMount(async () => { // Fetch the recovery words from the background (vault must be unlocked). const res = (await sendMessage({ type: "GET_RECOVERY_WORDS", })) as { ok: boolean; data?: string[]; error?: string }; if (res.ok && res.data) { setWords(res.data); } else { setError("could not retrieve recovery words"); } setLoading(false); // Start 5-second countdown let remaining = 5; countdownTimer = setInterval(() => { remaining -= 1; setCountdown(remaining); if (remaining <= 0 && countdownTimer) { clearInterval(countdownTimer); countdownTimer = null; } }, 1000); }); onCleanup(() => { if (countdownTimer) clearInterval(countdownTimer); }); async function handleConfirm() { const confirmed = confirmWords().map((w) => w.toLowerCase().trim()); if (confirmed.some((w) => !w)) { setConfirmError("all three words are required"); return; } // Verify against stored words const stored = words(); for (let i = 0; i < CONFIRM_WORD_INDICES.length; i++) { const pos = CONFIRM_WORD_INDICES[i]; if (confirmed[i] !== stored[pos]) { setConfirmError(`word ${pos + 1} is incorrect`); return; } } setConfirmed(true); setConfirmError(""); // Small pause so user sees the success state, then proceed. await new Promise((r) => setTimeout(r, 600)); props.onDone(); } const canConfirm = createMemo(() => countdown() <= 0 && !confirmed()); return (

save your recovery words

write these down — you will never see them again

loading...

{error()}

your 12 recovery words
    {(word, i) => (
  1. {toRoman(i() + 1)} {word}
  2. )}
{/* Confirmation step */}

{countdown() > 0 ? `type the highlighted words to confirm (${countdown()}s)` : "type the highlighted words to confirm"}

✓ words verified

{(pos, i) => (
word {pos + 1} { const updated = [...confirmWords()]; updated[i()] = (e.target as HTMLInputElement).value; setConfirmWords(updated); if (confirmError()) setConfirmError(""); }} onKeyDown={(e) => { if (e.key === "Enter" && canConfirm()) void handleConfirm(); }} />
)}

{confirmError()}

); } function toRoman(n: number): string { const map: [number, string][] = [ [1000, "M"], [900, "CM"], [500, "D"], [400, "CD"], [100, "C"], [90, "XC"], [50, "L"], [40, "XL"], [10, "X"], [9, "IX"], [5, "V"], [4, "IV"], [1, "I"], ]; let out = ""; for (const [value, numeral] of map) { while (n >= value) { out += numeral; n -= value; } } return out; }