diff --git a/extension/src/vault/vault.css b/extension/src/vault/vault.css index 1cfb456..d506c18 100644 --- a/extension/src/vault/vault.css +++ b/extension/src/vault/vault.css @@ -144,6 +144,30 @@ body { margin-top: 8px; } +.error-block { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + padding: 12px 16px; + border: 1px solid rgba(171, 43, 32, 0.4); + border-radius: 6px; + background: rgba(171, 43, 32, 0.08); + margin-top: 12px; +} +.error-block .error-title { + font-weight: 600; + color: var(--danger); +} +.error-block .error-body { + color: var(--text); + font-size: 12px; + text-align: center; +} +.error-block .error-cta { + margin-top: 6px; +} + /* Buttons */ .btn { display: inline-block; diff --git a/extension/src/vault/vault.ts b/extension/src/vault/vault.ts index 4f0bb92..5c240c6 100644 --- a/extension/src/vault/vault.ts +++ b/extension/src/vault/vault.ts @@ -9,6 +9,7 @@ import type { ItemId, ItemType, ManifestEntry, Item, VaultSettings, GeneratorRequest, } from '../shared/types'; import { registerHost } from '../shared/state'; +import { lookupErrorCopy, type ErrorCta } from '../shared/error-copy'; import { GLYPH_TRASH, GLYPH_DEVICES, GLYPH_SETTINGS, GLYPH_LOCK } from '../shared/glyphs'; import { renderItemDetail } from '../popup/components/item-detail'; import { renderItemForm } from '../popup/components/item-form'; @@ -41,6 +42,21 @@ function escapeHtml(str: string): string { .replace(/'/g, '''); } +function renderErrorBlock(code: string | null | undefined): string { + if (!code) return ''; + const copy = lookupErrorCopy(code); + const ctaHtml = copy.cta + ? `` + : ''; + return ` +
+
${escapeHtml(copy.title)}
+
${escapeHtml(copy.body)}
+ ${ctaHtml} +
+ `; +} + function typeIcon(t: ItemType): string { switch (t) { case 'login': return '\u{1F511}'; // key @@ -199,11 +215,29 @@ function renderLockScreen(app: HTMLElement): void {
- ${state.error ? `
${escapeHtml(state.error)}
` : ''} + ${renderErrorBlock(state.error)}
`; + app.querySelector('.error-cta')?.addEventListener('click', (e) => { + const cta = (e.currentTarget as HTMLElement).dataset.cta as ErrorCta['action']; + switch (cta) { + case 'unlock': { + document.getElementById('vault-passphrase')?.focus(); + break; + } + case 'open_setup': { + void chrome.tabs.create({ url: chrome.runtime.getURL('setup.html') }); + break; + } + case 'reload_extension': { + chrome.runtime.reload(); + break; + } + } + }); + const input = document.getElementById('vault-passphrase') as HTMLInputElement; const btn = document.getElementById('vault-unlock-btn')!;