fix(ext/vault): friendly error block in fullscreen tab (closes B2)
Replaces raw escapeHtml(state.error) renders with lookupErrorCopy()-driven title/body/CTA blocks. vault_locked specifically gets an 'Unlock vault' CTA that refocuses the passphrase input. Other CTAs route to setup.html or chrome.runtime.reload(). Closes B2; concludes P4.
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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
|
||||
? `<button class="btn btn-primary error-cta" data-cta="${escapeHtml(copy.cta.action ?? '')}">${escapeHtml(copy.cta.label)}</button>`
|
||||
: '';
|
||||
return `
|
||||
<div class="error error-block">
|
||||
<div class="error-title">${escapeHtml(copy.title)}</div>
|
||||
<div class="error-body">${escapeHtml(copy.body)}</div>
|
||||
${ctaHtml}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function typeIcon(t: ItemType): string {
|
||||
switch (t) {
|
||||
case 'login': return '\u{1F511}'; // key
|
||||
@@ -199,11 +215,29 @@ function renderLockScreen(app: HTMLElement): void {
|
||||
<div class="vault-lock-screen__form">
|
||||
<input type="password" id="vault-passphrase" placeholder="passphrase" autocomplete="off" />
|
||||
<button class="btn btn-primary" id="vault-unlock-btn" style="width:100%;">unlock</button>
|
||||
${state.error ? `<div class="error" style="text-align:center;">${escapeHtml(state.error)}</div>` : ''}
|
||||
${renderErrorBlock(state.error)}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
app.querySelector<HTMLButtonElement>('.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')!;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user