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;
|
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 */
|
/* Buttons */
|
||||||
.btn {
|
.btn {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import type {
|
|||||||
ItemId, ItemType, ManifestEntry, Item, VaultSettings, GeneratorRequest,
|
ItemId, ItemType, ManifestEntry, Item, VaultSettings, GeneratorRequest,
|
||||||
} from '../shared/types';
|
} from '../shared/types';
|
||||||
import { registerHost } from '../shared/state';
|
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 { GLYPH_TRASH, GLYPH_DEVICES, GLYPH_SETTINGS, GLYPH_LOCK } from '../shared/glyphs';
|
||||||
import { renderItemDetail } from '../popup/components/item-detail';
|
import { renderItemDetail } from '../popup/components/item-detail';
|
||||||
import { renderItemForm } from '../popup/components/item-form';
|
import { renderItemForm } from '../popup/components/item-form';
|
||||||
@@ -41,6 +42,21 @@ function escapeHtml(str: string): string {
|
|||||||
.replace(/'/g, ''');
|
.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 {
|
function typeIcon(t: ItemType): string {
|
||||||
switch (t) {
|
switch (t) {
|
||||||
case 'login': return '\u{1F511}'; // key
|
case 'login': return '\u{1F511}'; // key
|
||||||
@@ -199,11 +215,29 @@ function renderLockScreen(app: HTMLElement): void {
|
|||||||
<div class="vault-lock-screen__form">
|
<div class="vault-lock-screen__form">
|
||||||
<input type="password" id="vault-passphrase" placeholder="passphrase" autocomplete="off" />
|
<input type="password" id="vault-passphrase" placeholder="passphrase" autocomplete="off" />
|
||||||
<button class="btn btn-primary" id="vault-unlock-btn" style="width:100%;">unlock</button>
|
<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>
|
||||||
</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 input = document.getElementById('vault-passphrase') as HTMLInputElement;
|
||||||
const btn = document.getElementById('vault-unlock-btn')!;
|
const btn = document.getElementById('vault-unlock-btn')!;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user