diff --git a/extension/src/popup/styles.css b/extension/src/popup/styles.css index 5379e8e..09c3509 100644 --- a/extension/src/popup/styles.css +++ b/extension/src/popup/styles.css @@ -424,6 +424,41 @@ textarea { background: #aa812a; } +/* Setup wizard — Style C progress track */ +.setup-progress-track { + display: flex; + gap: 4px; + width: 100%; + max-width: 560px; + margin: 8px auto 16px; +} +.setup-progress-segment { + flex: 1; + height: 4px; + border-radius: 2px; +} +.setup-progress-segment--completed { background: var(--success, #238636); } +.setup-progress-segment--active { background: var(--gold, #b8860b); } +.setup-progress-segment--pending { background: var(--border, #30363d); } + +/* Setup wizard — Recovery QR banner */ +.recovery-qr-banner { + padding: 12px 16px; + background: var(--bg-elevated, #161b22); + border: 1px solid var(--gold, #b8860b); + border-radius: 8px; +} +.recovery-qr-banner__header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 4px; +} +.recovery-qr-banner__actions { + display: flex; + gap: 8px; +} + /* Spinner */ .spinner { display: inline-block; diff --git a/extension/src/setup/setup.ts b/extension/src/setup/setup.ts index 50895ee..ef92f14 100644 --- a/extension/src/setup/setup.ts +++ b/extension/src/setup/setup.ts @@ -93,6 +93,17 @@ const state: WizardState = { deviceName: '', }; +// --- Progress track --- + +const SETUP_STEP_NAMES = ['mode', 'host', 'connection', 'vault', 'device', 'done']; + +function renderProgressTrack(current: number): string { + return `
${SETUP_STEP_NAMES.map((_, i) => { + const cls = i < current ? 'completed' : i === current ? 'active' : 'pending'; + return `
`; + }).join('')}
`; +} + // --- State-coupled helpers (pure helpers live in ./setup-helpers.ts) --- /// Update just the meter DOM without a full re-render (so the input keeps @@ -168,16 +179,7 @@ function render(): void { const app = document.getElementById('app'); if (!app) return; - const progressHtml = ` -
-
-
-
-
-
-
-
- `; + const progressHtml = renderProgressTrack(state.step); let stepHtml = ''; switch (state.step) { @@ -224,6 +226,7 @@ function renderStep0(): string {

+ +
+ + ` : ''; + return `
@@ -992,6 +1012,8 @@ function renderStep5(): string {

+ ${qrBannerHtml} + ${isAttach ? '' : `
@@ -1026,6 +1048,48 @@ function renderStep5(): string { } function attachStep5(): void { + document.getElementById('setup-gen-qr')?.addEventListener('click', async () => { + if (!state.verifiedHandle) return; + const btn = document.getElementById('setup-gen-qr') as HTMLButtonElement | null; + if (btn) { btn.disabled = true; btn.textContent = 'Generating…'; } + try { + const { sendMessage } = await import('../shared/state'); + const resp = await sendMessage({ + type: 'generate_recovery_qr', + sessionHandle: state.verifiedHandle.value, + passphrase: state.passphrase, + } as any) as any; + if (!resp.ok || !resp.data) throw new Error(resp.error ?? 'unknown error'); + const svg = (resp.data as { svg: string }).svg; + await new Promise((resolve) => { + chrome.storage.local.set({ recovery_qr_generated_at: Date.now() }, resolve); + }); + const banner = document.getElementById('recovery-qr-banner'); + if (banner) { + banner.innerHTML = ` +
${svg}
+

+ ◉ Recovery QR generated — save or print this now. +

+
+ +
+ `; + document.getElementById('setup-qr-done')?.addEventListener('click', () => { + banner.style.display = 'none'; + }); + } + } catch (err) { + if (btn) { btn.disabled = false; btn.textContent = 'Generate now'; } + alert(`Failed to generate QR: ${err instanceof Error ? err.message : String(err)}`); + } + }); + + document.getElementById('setup-skip-qr')?.addEventListener('click', () => { + const banner = document.getElementById('recovery-qr-banner'); + if (banner) banner.style.display = 'none'; + }); + document.getElementById('download-ref-btn')?.addEventListener('click', () => { if (!state.referenceImageBytes) return; const blob = new Blob([state.referenceImageBytes.buffer as ArrayBuffer], { type: 'image/jpeg' });