// Adobe Sign → DocuSign Migrator — frontend app // Vanilla JS, no build step. const $ = id => document.getElementById(id); let statusTemplates = []; // [{adobe_id, name, status, docusign_id, ...}] let dsTemplates = []; // [{id, name, lastModified}] let authState = { adobe: false, docusign: false }; // ── Init ──────────────────────────────────────────────────────────────────── document.addEventListener('DOMContentLoaded', async () => { await refreshAuth(); await refreshTemplates(); await refreshHistory(); $('btn-migrate').addEventListener('click', onMigrate); $('btn-refresh').addEventListener('click', async () => { await refreshTemplates(); await refreshHistory(); }); }); // ── Auth ───────────────────────────────────────────────────────────────────── async function refreshAuth() { const resp = await fetch('/api/auth/status'); authState = await resp.json(); renderAuthBar(); } function renderAuthBar() { // Adobe: use .env credentials (primary), OAuth dialog (secondary) const adobeEl = $('badge-adobe'); adobeEl.textContent = authState.adobe ? '✓ Adobe Sign' : 'Connect Adobe Sign'; adobeEl.className = 'auth-badge' + (authState.adobe ? ' connected' : ''); adobeEl.onclick = authState.adobe ? () => disconnectPlatform('adobe') : () => connectAdobeEnv(); // DocuSign: JWT grant from .env — no browser sign-in needed const dsEl = $('badge-docusign'); dsEl.textContent = authState.docusign ? '✓ DocuSign' : 'Connect DocuSign'; dsEl.className = 'auth-badge' + (authState.docusign ? ' connected' : ''); dsEl.onclick = authState.docusign ? () => disconnectPlatform('docusign') : () => connectDocusign(); } async function disconnectPlatform(platform) { await fetch(`/api/auth/${platform}/disconnect`); authState[platform] = false; renderAuthBar(); await refreshTemplates(); } async function connectAdobeEnv() { const el = $('badge-adobe'); el.textContent = 'Connecting…'; const resp = await fetch('/api/auth/adobe/connect'); const data = await resp.json(); if (data.connected) { authState.adobe = true; renderAuthBar(); await refreshTemplates(); } else { el.textContent = 'Connect Adobe Sign'; // If .env has no credentials, fall back to the OAuth dialog if (data.error && data.error.includes('No Adobe Sign credentials')) { startAdobeAuth(); } else { setStatus('Adobe Sign error: ' + (data.error || 'unknown')); } } } async function connectDocusign() { const dsEl = $('badge-docusign'); dsEl.textContent = 'Connecting…'; const resp = await fetch('/api/auth/docusign/connect'); const data = await resp.json(); if (data.connected) { authState.docusign = true; renderAuthBar(); await refreshTemplates(); } else { dsEl.textContent = 'Connect DocuSign'; setStatus('DocuSign error: ' + (data.error || 'unknown')); } } // Adobe Sign uses the same manual-paste flow as the CLI: // 1. Open auth URL in new tab // 2. User authorizes → lands on failed https://localhost:8080/callback page // 3. User copies that URL, pastes it into the dialog here // 4. We POST it to /api/auth/adobe/exchange async function startAdobeAuth() { const resp = await fetch('/api/auth/adobe/url'); const { url } = await resp.json(); showAdobeDialog(url); } function showAdobeDialog(authUrl) { // Remove any existing dialog const existing = $('adobe-auth-dialog'); if (existing) existing.remove(); const dialog = document.createElement('div'); dialog.id = 'adobe-auth-dialog'; dialog.innerHTML = `

Connect Adobe Sign

  1. Click here to authorize in Adobe Sign
  2. After authorizing, your browser will show a page that fails to load — that's expected.
  3. Copy the full URL from the address bar and paste it below.
`; document.body.appendChild(dialog); $('btn-cancel-dialog').onclick = () => dialog.remove(); $('btn-submit-code').onclick = () => submitAdobeCode(dialog); // Also handle Enter key $('adobe-redirect-input').addEventListener('keydown', e => { if (e.key === 'Enter') submitAdobeCode(dialog); }); } async function submitAdobeCode(dialog) { const url = $('adobe-redirect-input').value.trim(); if (!url) return; $('btn-submit-code').disabled = true; $('btn-submit-code').textContent = 'Connecting…'; $('dialog-error').textContent = ''; try { const resp = await fetch('/api/auth/adobe/exchange', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ redirect_url: url }), }); const data = await resp.json(); if (!resp.ok || data.error) { $('dialog-error').textContent = data.error || 'Connection failed.'; $('btn-submit-code').disabled = false; $('btn-submit-code').textContent = 'Connect'; return; } dialog.remove(); authState.adobe = true; renderAuthBar(); await refreshTemplates(); } catch (e) { $('dialog-error').textContent = 'Error: ' + e.message; $('btn-submit-code').disabled = false; $('btn-submit-code').textContent = 'Connect'; } } // ── Templates ──────────────────────────────────────────────────────────────── async function refreshTemplates() { renderAdobeList([]); renderDsList([]); if (!authState.adobe || !authState.docusign) { setStatus(authState.adobe || authState.docusign ? 'Connect both platforms to see migration status.' : 'Connect Adobe Sign and DocuSign to get started.'); $('btn-migrate').disabled = true; return; } setStatus('Loading templates…'); try { const [statusResp, dsResp] = await Promise.all([ fetch('/api/templates/status'), fetch('/api/templates/docusign'), ]); statusTemplates = (await statusResp.json()).templates || []; dsTemplates = (await dsResp.json()).templates || []; renderAdobeList(statusTemplates); renderDsList(dsTemplates); setStatus(`${statusTemplates.length} Adobe template(s) loaded.`); } catch (e) { setStatus('Error loading templates: ' + e.message); } } function renderAdobeList(items) { const ul = $('adobe-list'); if (!items.length) { ul.innerHTML = '
  • No templates found.
  • '; return; } ul.innerHTML = items.map(t => `
  • ${escHtml(t.name)} ${statusLabel(t.status)}
  • `).join(''); ul.querySelectorAll('.template-item').forEach(li => { li.addEventListener('click', e => { if (e.target.type === 'checkbox') return; const cb = li.querySelector('input[type=checkbox]'); cb.checked = !cb.checked; li.classList.toggle('selected', cb.checked); updateMigrateButton(); }); li.querySelector('input').addEventListener('change', () => { li.classList.toggle('selected', li.querySelector('input').checked); updateMigrateButton(); }); }); } function renderDsList(items) { const ul = $('ds-list'); if (!items.length) { ul.innerHTML = '
  • No templates found.
  • '; return; } ul.innerHTML = items.map(t => `
  • ${escHtml(t.name)} ${(t.lastModified || '').slice(0, 10)}
  • `).join(''); } function updateMigrateButton() { const checked = document.querySelectorAll('#adobe-list input[type=checkbox]:checked'); $('btn-migrate').disabled = checked.length === 0; } // ── Migration ───────────────────────────────────────────────────────────────── async function onMigrate() { const checked = [...document.querySelectorAll('#adobe-list input[type=checkbox]:checked')]; const ids = checked.map(cb => cb.dataset.id); if (!ids.length) return; $('btn-migrate').disabled = true; setStatus(`Migrating ${ids.length} template(s)…`); ids.forEach(id => { const spin = $('spin-' + id); if (spin) spin.textContent = '⏳'; }); try { const resp = await fetch('/api/migrate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ adobe_template_ids: ids }), }); const data = await resp.json(); let successCount = 0; (data.results || []).forEach(r => { const spin = $('spin-' + r.adobe_template_id); if (r.status === 'success') { successCount++; if (spin) spin.textContent = r.action === 'updated' ? '✏️' : '✅'; } else { if (spin) spin.textContent = '❌'; } }); setStatus(`Done: ${successCount}/${ids.length} succeeded.`); await refreshTemplates(); await refreshHistory(); } catch (e) { setStatus('Migration error: ' + e.message); } } // ── History ─────────────────────────────────────────────────────────────────── async function refreshHistory() { try { const resp = await fetch('/api/migrate/history'); const { history } = await resp.json(); renderHistory(history || []); } catch { renderHistory([]); } } function renderHistory(records) { const tbody = $('history-tbody'); if (!records.length) { tbody.innerHTML = 'No migrations yet.'; return; } tbody.innerHTML = [...records].reverse().slice(0, 50).map(r => ` ${(r.timestamp || '').replace('T', ' ').slice(0, 19)} ${escHtml(r.adobe_template_name || r.adobe_template_id || '')} ${escHtml(r.docusign_template_id || '—')} ${escHtml(r.action || '—')} ${r.status} `).join(''); } // ── Utilities ───────────────────────────────────────────────────────────────── function setStatus(msg) { $('status-msg').textContent = msg; } function statusLabel(s) { return { not_migrated: 'Not Migrated', migrated: 'Migrated', needs_update: 'Needs Update' }[s] || s; } function escHtml(str) { return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); }