Improve account switching controls

This commit is contained in:
Paul Huliganga 2026-04-21 21:48:07 -04:00
parent eb9ce84001
commit af92aa6c47
3 changed files with 187 additions and 8 deletions

View File

@ -199,6 +199,12 @@
background: var(--card-bg);
}
.conn-pill:hover { background: var(--ecru); }
.conn-pill-label { white-space: nowrap; }
.conn-caret {
font-size: 11px;
color: var(--text-muted);
margin-left: 2px;
}
.conn-dot {
width: 7px;
height: 7px;
@ -209,6 +215,50 @@
.conn-pill.disconnected .conn-dot { background: var(--error); }
.conn-pill.connecting .conn-dot { background: var(--warning-amber); }
.auth-chip-menu {
position: fixed;
width: 220px;
background: var(--card-bg);
border: 1px solid var(--border);
border-radius: 10px;
box-shadow: var(--shadow-lg);
padding: 8px;
z-index: 1200;
}
.auth-chip-menu-title {
font-size: var(--font-size-xs);
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-muted);
padding: 6px 8px 8px;
}
.auth-chip-menu-item {
width: 100%;
border: 0;
background: transparent;
text-align: left;
padding: 10px 8px;
border-radius: 8px;
display: flex;
flex-direction: column;
gap: 3px;
cursor: pointer;
}
.auth-chip-menu-item:hover {
background: var(--ecru);
}
.auth-chip-menu-label {
font-size: var(--font-size-sm);
font-weight: 600;
color: var(--text);
}
.auth-chip-menu-help {
font-size: var(--font-size-xs);
color: var(--text-muted);
line-height: 1.35;
}
/* ── Router outlet ── */
#router-outlet {
flex: 1;

View File

@ -32,7 +32,7 @@ function renderChip(id, connected, label, onClick) {
const el = document.getElementById(id);
if (!el) return;
el.className = 'conn-pill ' + (connected ? 'connected' : 'disconnected');
el.innerHTML = `<span class="conn-dot"></span>${escHtml(label)}`;
el.innerHTML = `<span class="conn-dot"></span><span class="conn-pill-label">${escHtml(label)}</span>${connected ? '<span class="conn-caret">▾</span>' : ''}`;
el.onclick = onClick;
}
@ -40,7 +40,7 @@ function renderChip(id, connected, label, onClick) {
async function onClickAdobe() {
if (state.auth.adobe) {
await disconnect('adobe');
showAuthMenu('adobe', 'chip-adobe');
} else {
await connectAdobeEnv();
}
@ -48,28 +48,53 @@ async function onClickAdobe() {
async function onClickDocusign() {
if (state.auth.docusign) {
await disconnect('docusign');
showAuthMenu('docusign', 'chip-docusign');
} else {
await connectDocusign();
}
}
async function disconnect(platform) {
export async function disconnectPlatform(platform, opts = {}) {
const { silent = false, skipRefresh = false } = opts;
setChipConnecting(platform === 'adobe' ? 'chip-adobe' : 'chip-docusign');
try {
await api.auth.disconnect(platform);
setState('auth', { ...state.auth, [platform]: false });
renderAuthChips();
// Reload templates (they'll be empty without auth)
const { refreshTemplates } = await import('./templates.js');
refreshTemplates();
if (!skipRefresh) {
const { refreshTemplates } = await import('./templates.js');
refreshTemplates();
}
if (!silent) {
showToast(`${platform === 'adobe' ? 'Adobe Sign' : 'Docusign'} disconnected.`, 'info');
}
} catch (e) {
console.error('Disconnect failed:', e.message);
renderAuthChips();
if (!silent) {
showToast(`Disconnect failed: ${e.message}`, 'error');
}
}
}
export async function switchAccount(platform) {
closeAuthMenu();
await disconnectPlatform(platform, { silent: true, skipRefresh: true });
if (platform === 'docusign') {
showToast('Starting a fresh Docusign authorization. If Docusign signs you in automatically, sign out there and try again to choose a different account.', 'info');
window.location.href = '/api/auth/docusign/start';
return;
}
if (platform === 'adobe') {
showToast('Adobe Sign disconnected. Reconnect to continue.', 'info');
await connectAdobeEnv();
}
}
async function connectAdobeEnv() {
closeAuthMenu();
setChipConnecting('chip-adobe');
try {
const data = await api.auth.connectAdobe();
@ -92,6 +117,7 @@ async function connectAdobeEnv() {
}
async function connectDocusign() {
closeAuthMenu();
setChipConnecting('chip-docusign');
try {
const data = await api.auth.connectDocusign();
@ -119,6 +145,75 @@ function setChipConnecting(id) {
el.innerHTML = `<span class="conn-dot"></span><span class="spinner spinner-sm"></span>`;
}
function closeAuthMenu() {
document.getElementById('auth-chip-menu')?.remove();
document.removeEventListener('click', onDocumentClickCloseMenu, true);
document.removeEventListener('keydown', onEscapeCloseMenu, true);
}
function onDocumentClickCloseMenu(event) {
const menu = document.getElementById('auth-chip-menu');
if (!menu) return;
if (menu.contains(event.target)) return;
if (event.target.closest('.conn-pill')) return;
closeAuthMenu();
}
function onEscapeCloseMenu(event) {
if (event.key === 'Escape') closeAuthMenu();
}
function showAuthMenu(platform, anchorId) {
const anchor = document.getElementById(anchorId);
if (!anchor) return;
const existing = document.getElementById('auth-chip-menu');
if (existing && existing.dataset.platform === platform && existing.dataset.anchorId === anchorId) {
closeAuthMenu();
return;
}
closeAuthMenu();
const rect = anchor.getBoundingClientRect();
const menu = document.createElement('div');
menu.id = 'auth-chip-menu';
menu.dataset.platform = platform;
menu.dataset.anchorId = anchorId;
menu.className = 'auth-chip-menu';
menu.style.top = `${rect.bottom + 8}px`;
menu.style.left = `${Math.max(12, rect.right - 220)}px`;
const accountLabel = platform === 'docusign' ? 'Docusign' : 'Adobe Sign';
const switchLabel = platform === 'docusign' ? 'Switch Account' : 'Reconnect';
const switchHelp = platform === 'docusign'
? 'Clear this browser session and start a fresh login flow.'
: 'Disconnect and reconnect Adobe Sign.';
menu.innerHTML = `
<div class="auth-chip-menu-title">${escHtml(accountLabel)}</div>
<button class="auth-chip-menu-item" data-action="disconnect">
<span class="auth-chip-menu-label">Disconnect</span>
<span class="auth-chip-menu-help">Clear this app session.</span>
</button>
<button class="auth-chip-menu-item" data-action="switch">
<span class="auth-chip-menu-label">${escHtml(switchLabel)}</span>
<span class="auth-chip-menu-help">${escHtml(switchHelp)}</span>
</button>
`;
menu.querySelector('[data-action="disconnect"]')?.addEventListener('click', async () => {
closeAuthMenu();
await disconnectPlatform(platform);
});
menu.querySelector('[data-action="switch"]')?.addEventListener('click', async () => {
await switchAccount(platform);
});
document.body.appendChild(menu);
document.addEventListener('click', onDocumentClickCloseMenu, true);
document.addEventListener('keydown', onEscapeCloseMenu, true);
}
// ── Adobe OAuth dialog (manual redirect URL paste) ─────────────────────────
async function showAdobeOAuthDialog() {

View File

@ -3,6 +3,7 @@
import { api } from './api.js';
import { state } from './state.js';
import { escHtml } from './utils.js';
import { disconnectPlatform, switchAccount } from './auth.js';
const SETTINGS_KEY = 'migrator_settings';
@ -103,7 +104,7 @@ export function renderSettings() {
<div class="settings-section">
<div class="settings-section-header">
<div class="settings-section-title">Connections</div>
<div class="settings-section-sub">Current platform connection status (connect via top bar)</div>
<div class="settings-section-sub">Current platform connection status and account actions</div>
</div>
<div class="settings-section-body" id="settings-conn-info">
<div style="padding:8px 0;font-size:13px;color:var(--text-muted)">Loading</div>
@ -167,6 +168,21 @@ async function _loadConnInfo() {
<span class="badge ${data.docusign ? 'badge-green' : 'badge-gray'}">${data.docusign ? '● Connected' : '○ Disconnected'}</span>
</span>
</div>
<div class="conn-info-row conn-info-actions">
<span class="conn-info-label">Adobe Actions</span>
<span class="conn-info-value">
<button class="btn btn-secondary" id="btn-disconnect-adobe" ${data.adobe ? '' : 'disabled'}>Disconnect Adobe Sign</button>
</span>
<span class="conn-info-status"></span>
</div>
<div class="conn-info-row conn-info-actions">
<span class="conn-info-label">Docusign Actions</span>
<span class="conn-info-value" style="display:flex;gap:8px;flex-wrap:wrap">
<button class="btn btn-secondary" id="btn-disconnect-docusign" ${data.docusign ? '' : 'disabled'}>Disconnect Docusign</button>
<button class="btn btn-primary" id="btn-switch-docusign" ${data.docusign ? '' : 'disabled'}>Switch Docusign Account</button>
</span>
<span class="conn-info-status"></span>
</div>
<div class="conn-info-row">
<span class="conn-info-label">Adobe Auth Mode</span>
<span class="conn-info-value mono">${escHtml(data.adobe_auth_mode || '—')}</span>
@ -182,6 +198,11 @@ async function _loadConnInfo() {
<span class="conn-info-value mono">${escHtml(data.session_id || '—')}</span>
<span class="conn-info-status"></span>
</div>
<div class="conn-info-row">
<span class="conn-info-label">Switch Account Note</span>
<span class="conn-info-value">Use <strong>Switch Docusign Account</strong> to clear this browser session and start a fresh login flow. If Docusign signs you straight back in, sign out of Docusign in that browser first.</span>
<span class="conn-info-status"></span>
</div>
<div class="conn-info-row">
<span class="conn-info-label">Docusign Account ID</span>
<span class="conn-info-value mono">${escHtml(data.docusign_account_id || '—')}</span>
@ -195,5 +216,18 @@ async function _loadConnInfo() {
`;
} catch (e) {
connEl.innerHTML = `<div class="callout error"><span class="callout-icon">❌</span>Failed to load connection info: ${escHtml(e.message)}</div>`;
return;
}
document.getElementById('btn-disconnect-adobe')?.addEventListener('click', async () => {
await disconnectPlatform('adobe');
await _loadConnInfo();
});
document.getElementById('btn-disconnect-docusign')?.addEventListener('click', async () => {
await disconnectPlatform('docusign');
await _loadConnInfo();
});
document.getElementById('btn-switch-docusign')?.addEventListener('click', async () => {
await switchAccount('docusign');
});
}