// Auth: connect/disconnect Adobe Sign and Docusign, account picker, auth chips import { api } from './api.js'; import { state, setState } from './state.js'; import { escHtml, initials } from './utils.js'; // ── Refresh auth state and update chips ──────────────────────────────────── export async function refreshAuth() { try { const data = await api.auth.status(); setState('auth', { adobe: !!data.adobe, docusign: !!data.docusign, adobeLabel: data.adobe_label || 'Adobe Sign', docusignLabel: data.docusign_label || 'Docusign', docusignAccountId: data.docusign_account_id || null, docusignAccountName: data.docusign_account_name || null, docusignAccountsCount: data.docusign_accounts_count || 0, docusignAccountSelectionRequired: !!data.docusign_account_selection_required, }); } catch (e) { console.warn('Auth status failed:', e.message); } renderAuthChips(); if (state.auth.docusign && state.auth.docusignAccountSelectionRequired) { showDocusignAccountPicker(); } } // ── Render connection pills in top bar ───────────────────────────────────── export function renderAuthChips() { renderChip('chip-adobe', state.auth.adobe, 'Adobe Sign', onClickAdobe); renderChip( 'chip-docusign', state.auth.docusign, state.auth.docusignAccountName || 'Docusign', onClickDocusign ); renderAvatar(); } function renderAvatar() { const el = document.getElementById('topbar-avatar'); if (!el) return; const name = state.auth.docusignLabel && state.auth.docusignLabel !== 'Docusign' ? state.auth.docusignLabel : state.auth.docusignAccountName || ''; el.textContent = name ? initials(name) : '?'; el.title = name || 'User'; el.setAttribute('aria-label', name ? `User ${name}` : 'User'); } 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)}${connected ? '' : ''}`; el.onclick = onClick; } // ── Click handlers ───────────────────────────────────────────────────────── async function onClickAdobe() { if (state.auth.adobe) { showAuthMenu('adobe', 'chip-adobe'); } else { await connectAdobeEnv(); } } async function onClickDocusign() { if (state.auth.docusign) { showAuthMenu('docusign', 'chip-docusign'); } else { await connectDocusign(); } } export async function disconnectPlatform(platform, opts = {}) { const { silent = false, skipRefresh = false } = opts; closeAuthMenu(); closeDocusignAccountPicker(); setChipConnecting(platform === 'adobe' ? 'chip-adobe' : 'chip-docusign'); try { await api.auth.disconnect(platform); if (platform === 'docusign') { setState('auth', { ...state.auth, docusign: false, docusignAccountId: null, docusignAccountName: null, docusignAccountsCount: 0, docusignAccountSelectionRequired: false, }); } else { setState('auth', { ...state.auth, adobe: false }); } renderAuthChips(); 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(); if (platform === 'docusign') { await showDocusignAccountPicker({ forceRefresh: true }); return; } await disconnectPlatform(platform, { silent: true, skipRefresh: true }); showToast('Adobe Sign disconnected. Reconnect to continue.', 'info'); await connectAdobeEnv(); } async function connectAdobeEnv() { closeAuthMenu(); setChipConnecting('chip-adobe'); try { const data = await api.auth.connectAdobe(); if (data.connected) { setState('auth', { ...state.auth, adobe: true }); renderAuthChips(); const { refreshTemplates } = await import('./templates.js'); refreshTemplates(); } else if (data.error && data.error.includes('No Adobe Sign credentials')) { renderAuthChips(); showAdobeOAuthDialog(); } else { renderAuthChips(); showToast('Adobe Sign error: ' + (data.error || 'unknown'), 'error'); } } catch (e) { renderAuthChips(); showAdobeOAuthDialog(); } } async function connectDocusign() { closeAuthMenu(); setChipConnecting('chip-docusign'); try { const data = await api.auth.connectDocusign(); if (data.connected) { await refreshAuth(); if (!data.account_selection_required) { const { refreshTemplates } = await import('./templates.js'); refreshTemplates(); } } else if (data.authorization_required && data.authorization_url) { window.location.href = data.authorization_url; } else { renderAuthChips(); showToast('Docusign error: ' + (data.error || 'unknown'), 'error'); } } catch (e) { renderAuthChips(); showToast('Docusign connection failed: ' + e.message, 'error'); } } function setChipConnecting(id) { const el = document.getElementById(id); if (!el) return; el.className = 'conn-pill connecting'; el.innerHTML = ``; } // ── Top-bar menu ─────────────────────────────────────────────────────────── 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' ? 'Pick a different DocuSign account from your account list.' : '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); } // ── DocuSign account picker ──────────────────────────────────────────────── export async function showDocusignAccountPicker(opts = {}) { const { forceRefresh = false } = opts; if (!forceRefresh && document.getElementById('docusign-account-dialog')) return; let data; try { data = await api.auth.docusignAccounts(); } catch (e) { showToast('Failed to load DocuSign accounts: ' + e.message, 'error'); return; } const accounts = [...(data.accounts || [])].sort((a, b) => { const nameCmp = (a.account_name || '').localeCompare(b.account_name || '', undefined, { sensitivity: 'base' }); return nameCmp || (a.account_id || '').localeCompare(b.account_id || '', undefined, { sensitivity: 'base' }); }); if (!accounts.length) { showToast('No DocuSign accounts were returned for this user.', 'error'); return; } if (accounts.length === 1) { await selectDocusignAccount(accounts[0].account_id); return; } closeDocusignAccountPicker(); const dialog = document.createElement('div'); dialog.id = 'docusign-account-dialog'; dialog.innerHTML = ` `; document.body.appendChild(dialog); const listEl = document.getElementById('docusign-account-list'); const searchEl = document.getElementById('docusign-account-search'); const errorEl = document.getElementById('docusign-account-error'); const renderList = () => { const query = (searchEl?.value || '').trim().toLowerCase(); const filtered = accounts.filter(acc => { const haystack = `${acc.account_name || ''} ${acc.account_id || ''} ${acc.organization_name || ''}`.toLowerCase(); return !query || haystack.includes(query); }); if (!filtered.length) { listEl.innerHTML = `
No matching accounts
`; return; } listEl.innerHTML = filtered.map(acc => ` `).join(''); listEl.querySelectorAll('.docusign-account-item').forEach(btn => { btn.addEventListener('click', async () => { errorEl.textContent = ''; await selectDocusignAccount(btn.dataset.accountId, errorEl); }); }); }; searchEl?.addEventListener('input', renderList); document.getElementById('docusign-account-close')?.addEventListener('click', closeDocusignAccountPicker); document.getElementById('docusign-account-cancel')?.addEventListener('click', closeDocusignAccountPicker); renderList(); } function closeDocusignAccountPicker() { document.getElementById('docusign-account-dialog')?.remove(); } async function selectDocusignAccount(accountId, errorEl = null) { try { await api.auth.selectDocusignAccount(accountId); closeDocusignAccountPicker(); await refreshAuth(); const { refreshTemplates } = await import('./templates.js'); refreshTemplates(); showToast('DocuSign account selected.', 'success'); } catch (e) { if (errorEl) { errorEl.textContent = e.data?.error || e.message || 'Failed to select account.'; } else { showToast('Failed to select account: ' + e.message, 'error'); } } } // ── Adobe OAuth dialog (manual redirect URL paste) ───────────────────────── async function showAdobeOAuthDialog() { const { url } = await api.auth.adobeUrl().catch(() => ({ url: '#' })); const existing = document.getElementById('adobe-auth-dialog'); if (existing) existing.remove(); const dialog = document.createElement('div'); dialog.id = 'adobe-auth-dialog'; dialog.innerHTML = ` `; document.body.appendChild(dialog); document.getElementById('adobe-dialog-close').onclick = () => dialog.remove(); document.getElementById('adobe-dialog-cancel').onclick = () => dialog.remove(); document.getElementById('adobe-dialog-submit').onclick = () => submitAdobeCode(dialog); document.getElementById('adobe-redirect-input').addEventListener('keydown', e => { if (e.key === 'Enter') submitAdobeCode(dialog); }); } async function submitAdobeCode(dialog) { const url = document.getElementById('adobe-redirect-input').value.trim(); if (!url) return; const submitBtn = document.getElementById('adobe-dialog-submit'); const errorEl = document.getElementById('adobe-dialog-error'); submitBtn.disabled = true; submitBtn.textContent = 'Connecting…'; errorEl.textContent = ''; try { await api.auth.exchangeAdobe(url); dialog.remove(); setState('auth', { ...state.auth, adobe: true }); renderAuthChips(); const { refreshTemplates } = await import('./templates.js'); refreshTemplates(); } catch (e) { errorEl.textContent = e.data?.error || e.message || 'Connection failed.'; submitBtn.disabled = false; submitBtn.textContent = 'Connect'; } } // ── Toast notification ───────────────────────────────────────────────────── export function showToast(message, type = 'info') { let container = document.getElementById('toast-container'); if (!container) { container = document.createElement('div'); container.id = 'toast-container'; container.style.cssText = 'position:fixed;bottom:24px;right:24px;z-index:9999;display:flex;flex-direction:column;gap:8px;'; document.body.appendChild(container); } const toast = document.createElement('div'); const colors = { info: 'var(--cobalt-light)', error: 'var(--error-bg)', success: 'var(--success-bg)' }; const borders = { info: 'var(--cobalt)', error: 'var(--error)', success: 'var(--success)' }; toast.style.cssText = ` padding:10px 16px;border-radius:6px;font-size:13px;font-weight:500; background:${colors[type] || colors.info};border:1px solid ${borders[type] || borders.info}; box-shadow:var(--shadow-md);max-width:420px;animation:fadeIn 0.2s ease; `; toast.textContent = message; container.appendChild(toast); setTimeout(() => toast.remove(), 4500); }