diff --git a/web/static/css/nav.css b/web/static/css/nav.css index 1657695..a44ab7d 100644 --- a/web/static/css/nav.css +++ b/web/static/css/nav.css @@ -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; diff --git a/web/static/js/auth.js b/web/static/js/auth.js index 5943871..16c9a6d 100644 --- a/web/static/js/auth.js +++ b/web/static/js/auth.js @@ -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 = `${escHtml(label)}`; + el.innerHTML = `${escHtml(label)}${connected ? '' : ''}`; 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 = ``; } +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 = ` +
${escHtml(accountLabel)}
+ + + `; + + 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() { diff --git a/web/static/js/settings.js b/web/static/js/settings.js index 238afbd..35330d9 100644 --- a/web/static/js/settings.js +++ b/web/static/js/settings.js @@ -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() {
Connections
-
Current platform connection status (connect via top bar)
+
Current platform connection status and account actions
Loading…
@@ -167,6 +168,21 @@ async function _loadConnInfo() { ${data.docusign ? '● Connected' : '○ Disconnected'}
+
+ Adobe Actions + + + + +
+
+ Docusign Actions + + + + + +
Adobe Auth Mode ${escHtml(data.adobe_auth_mode || '—')} @@ -182,6 +198,11 @@ async function _loadConnInfo() { ${escHtml(data.session_id || '—')}
+
+ Switch Account Note + Use Switch Docusign Account 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. + +
Docusign Account ID ${escHtml(data.docusign_account_id || '—')} @@ -195,5 +216,18 @@ async function _loadConnInfo() { `; } catch (e) { connEl.innerHTML = `
Failed to load connection info: ${escHtml(e.message)}
`; + 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'); + }); }