Improve account switching controls
This commit is contained in:
parent
eb9ce84001
commit
af92aa6c47
|
|
@ -199,6 +199,12 @@
|
||||||
background: var(--card-bg);
|
background: var(--card-bg);
|
||||||
}
|
}
|
||||||
.conn-pill:hover { background: var(--ecru); }
|
.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 {
|
.conn-dot {
|
||||||
width: 7px;
|
width: 7px;
|
||||||
height: 7px;
|
height: 7px;
|
||||||
|
|
@ -209,6 +215,50 @@
|
||||||
.conn-pill.disconnected .conn-dot { background: var(--error); }
|
.conn-pill.disconnected .conn-dot { background: var(--error); }
|
||||||
.conn-pill.connecting .conn-dot { background: var(--warning-amber); }
|
.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 ── */
|
||||||
#router-outlet {
|
#router-outlet {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ function renderChip(id, connected, label, onClick) {
|
||||||
const el = document.getElementById(id);
|
const el = document.getElementById(id);
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
el.className = 'conn-pill ' + (connected ? 'connected' : 'disconnected');
|
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;
|
el.onclick = onClick;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,7 +40,7 @@ function renderChip(id, connected, label, onClick) {
|
||||||
|
|
||||||
async function onClickAdobe() {
|
async function onClickAdobe() {
|
||||||
if (state.auth.adobe) {
|
if (state.auth.adobe) {
|
||||||
await disconnect('adobe');
|
showAuthMenu('adobe', 'chip-adobe');
|
||||||
} else {
|
} else {
|
||||||
await connectAdobeEnv();
|
await connectAdobeEnv();
|
||||||
}
|
}
|
||||||
|
|
@ -48,28 +48,53 @@ async function onClickAdobe() {
|
||||||
|
|
||||||
async function onClickDocusign() {
|
async function onClickDocusign() {
|
||||||
if (state.auth.docusign) {
|
if (state.auth.docusign) {
|
||||||
await disconnect('docusign');
|
showAuthMenu('docusign', 'chip-docusign');
|
||||||
} else {
|
} else {
|
||||||
await connectDocusign();
|
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');
|
setChipConnecting(platform === 'adobe' ? 'chip-adobe' : 'chip-docusign');
|
||||||
try {
|
try {
|
||||||
await api.auth.disconnect(platform);
|
await api.auth.disconnect(platform);
|
||||||
setState('auth', { ...state.auth, [platform]: false });
|
setState('auth', { ...state.auth, [platform]: false });
|
||||||
renderAuthChips();
|
renderAuthChips();
|
||||||
// Reload templates (they'll be empty without auth)
|
if (!skipRefresh) {
|
||||||
const { refreshTemplates } = await import('./templates.js');
|
const { refreshTemplates } = await import('./templates.js');
|
||||||
refreshTemplates();
|
refreshTemplates();
|
||||||
|
}
|
||||||
|
if (!silent) {
|
||||||
|
showToast(`${platform === 'adobe' ? 'Adobe Sign' : 'Docusign'} disconnected.`, 'info');
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Disconnect failed:', e.message);
|
console.error('Disconnect failed:', e.message);
|
||||||
renderAuthChips();
|
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() {
|
async function connectAdobeEnv() {
|
||||||
|
closeAuthMenu();
|
||||||
setChipConnecting('chip-adobe');
|
setChipConnecting('chip-adobe');
|
||||||
try {
|
try {
|
||||||
const data = await api.auth.connectAdobe();
|
const data = await api.auth.connectAdobe();
|
||||||
|
|
@ -92,6 +117,7 @@ async function connectAdobeEnv() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function connectDocusign() {
|
async function connectDocusign() {
|
||||||
|
closeAuthMenu();
|
||||||
setChipConnecting('chip-docusign');
|
setChipConnecting('chip-docusign');
|
||||||
try {
|
try {
|
||||||
const data = await api.auth.connectDocusign();
|
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>`;
|
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) ─────────────────────────
|
// ── Adobe OAuth dialog (manual redirect URL paste) ─────────────────────────
|
||||||
|
|
||||||
async function showAdobeOAuthDialog() {
|
async function showAdobeOAuthDialog() {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import { api } from './api.js';
|
import { api } from './api.js';
|
||||||
import { state } from './state.js';
|
import { state } from './state.js';
|
||||||
import { escHtml } from './utils.js';
|
import { escHtml } from './utils.js';
|
||||||
|
import { disconnectPlatform, switchAccount } from './auth.js';
|
||||||
|
|
||||||
const SETTINGS_KEY = 'migrator_settings';
|
const SETTINGS_KEY = 'migrator_settings';
|
||||||
|
|
||||||
|
|
@ -103,7 +104,7 @@ export function renderSettings() {
|
||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
<div class="settings-section-header">
|
<div class="settings-section-header">
|
||||||
<div class="settings-section-title">Connections</div>
|
<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>
|
||||||
<div class="settings-section-body" id="settings-conn-info">
|
<div class="settings-section-body" id="settings-conn-info">
|
||||||
<div style="padding:8px 0;font-size:13px;color:var(--text-muted)">Loading…</div>
|
<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 class="badge ${data.docusign ? 'badge-green' : 'badge-gray'}">${data.docusign ? '● Connected' : '○ Disconnected'}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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">
|
<div class="conn-info-row">
|
||||||
<span class="conn-info-label">Adobe Auth Mode</span>
|
<span class="conn-info-label">Adobe Auth Mode</span>
|
||||||
<span class="conn-info-value mono">${escHtml(data.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-value mono">${escHtml(data.session_id || '—')}</span>
|
||||||
<span class="conn-info-status"></span>
|
<span class="conn-info-status"></span>
|
||||||
</div>
|
</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">
|
<div class="conn-info-row">
|
||||||
<span class="conn-info-label">Docusign Account ID</span>
|
<span class="conn-info-label">Docusign Account ID</span>
|
||||||
<span class="conn-info-value mono">${escHtml(data.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) {
|
} catch (e) {
|
||||||
connEl.innerHTML = `<div class="callout error"><span class="callout-icon">❌</span>Failed to load connection info: ${escHtml(e.message)}</div>`;
|
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');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue