// Verification view — send test envelopes to confirm migrated templates work import { api } from './api.js'; import { state } from './state.js'; import { escHtml, formatDateTime } from './utils.js'; const POLL_MS = 30_000; // DocuSign rate-limit guidance: no more than once per 15 min in prod const POLL_TIMEOUT = 300_000; // 5 minutes — treat as manual quick-test only; prod should use DS Connect const _envelopes = {}; // { adobeId: { envelopeId, status, sentAt, completedAt, polling } } function getSettings() { try { return JSON.parse(localStorage.getItem('migrator_settings')) || {}; } catch { return {}; } } export async function renderVerification(preloadedIds = null) { const outlet = document.getElementById('router-outlet'); const ids = preloadedIds || state.verifyIds || null; // Candidate templates: recently migrated (from state) or all migrated const candidates = (state.templates || []).filter(t => t.status === 'migrated' || t.status === 'needs_update' || t.docusign_id ); if (!state.auth.docusign) { outlet.innerHTML = `
ℹ️Connect Docusign to send verification envelopes.
`; return; } if (!candidates.length) { outlet.innerHTML = `
ℹ️ No migrated templates yet. Run a migration first, then return here to verify.
`; return; } _renderVerifyView(candidates); } function _renderVerifyView(candidates) { const outlet = document.getElementById('router-outlet'); const settings = getSettings(); outlet.innerHTML = `
ℹ️ Verification sends a real Docusign envelope using each template. Test envelopes should be voided after use. Configure default recipient in Settings.
Migrated Templates ${candidates.length} templates
${candidates.map(t => _verifyRow(t, settings)).join('')}
Template Docusign ID Verification Status Actions
`; // Wire Send Test buttons document.querySelectorAll('.btn-send-test').forEach(btn => { btn.addEventListener('click', () => { const adobeId = btn.dataset.id; const t = candidates.find(t => t.adobe_id === adobeId); if (t) _showSendDialog(t, settings); }); }); // Wire Void buttons document.querySelectorAll('.btn-void-envelope').forEach(btn => { btn.addEventListener('click', () => { _voidEnvelope(btn.dataset.id, btn.dataset.envelopeid); }); }); } function _verifyRow(t, settings) { const env = _envelopes[t.adobe_id]; let statusCell = 'Not Tested'; let actionsCell = ``; if (env) { if (env.status === 'completed') { statusCell = '✓ Verified'; actionsCell = ``; } else if (env.status === 'sent' || env.status === 'delivered') { statusCell = ` ${env.status}`; actionsCell = ``; } else if (env.status === 'voided') { statusCell = 'Voided'; actionsCell = ``; } else if (env.status === 'timeout') { statusCell = 'Timed Out'; actionsCell = ``; } else { statusCell = `${escHtml(env.status || 'pending')}`; } } return `
${escHtml(t.name)}
${escHtml(t.adobe_id)}
${t.docusign_id ? escHtml(t.docusign_id.slice(0,12)) + '…' : '—'} ${statusCell} ${actionsCell} `; } function _showSendDialog(t, settings) { const existing = document.getElementById('send-dialog'); if (existing) existing.remove(); const wrapper = document.createElement('div'); wrapper.id = 'send-dialog'; wrapper.innerHTML = ` `; document.body.appendChild(wrapper); document.getElementById('sd-close').onclick = () => wrapper.remove(); document.getElementById('sd-cancel').onclick = () => wrapper.remove(); document.getElementById('sd-send').onclick = () => _sendEnvelope(t, wrapper); } async function _sendEnvelope(t, wrapper) { const name = document.getElementById('sd-name')?.value.trim(); const email = document.getElementById('sd-email')?.value.trim(); const errorEl = document.getElementById('sd-error'); if (!name || !email) { if (errorEl) errorEl.textContent = 'Recipient name and email are required.'; return; } if (errorEl) errorEl.textContent = ''; const sendBtn = document.getElementById('sd-send'); sendBtn.disabled = true; sendBtn.textContent = 'Sending…'; try { const data = await api.verify.send(t.docusign_id || t.adobe_id, name, email); wrapper.remove(); _envelopes[t.adobe_id] = { envelopeId: data.envelope_id, status: 'sent', sentAt: new Date().toISOString() }; _updateVerifyRow(t.adobe_id); _startPolling(t.adobe_id, data.envelope_id); } catch (e) { if (errorEl) errorEl.textContent = 'Send failed: ' + e.message; sendBtn.disabled = false; sendBtn.textContent = 'Send Test →'; } } function _startPolling(adobeId, envelopeId) { const env = _envelopes[adobeId]; if (!env || env.polling) return; env.polling = true; const deadline = Date.now() + POLL_TIMEOUT; const poll = async () => { try { const data = await api.verify.status(envelopeId); if (!_envelopes[adobeId]) return; _envelopes[adobeId].status = data.status; _envelopes[adobeId].completedAt = data.completed_at; _updateVerifyRow(adobeId); if (data.status === 'completed' || data.status === 'voided') { _envelopes[adobeId].polling = false; } else if (Date.now() >= deadline) { _envelopes[adobeId].polling = false; _envelopes[adobeId].status = 'timeout'; _updateVerifyRow(adobeId); } else { setTimeout(poll, POLL_MS); } } catch (e) { console.warn('Polling error:', e.message); } }; setTimeout(poll, POLL_MS); } function _updateVerifyRow(adobeId) { const t = (state.templates || []).find(t => t.adobe_id === adobeId); const env = _envelopes[adobeId]; if (!t || !env) return; const statusEl = document.getElementById(`verify-status-${adobeId}`); const actionsEl = document.getElementById(`verify-actions-${adobeId}`); if (!statusEl) return; if (env.status === 'completed') { statusEl.innerHTML = '✓ Verified'; actionsEl.innerHTML = ``; } else if (env.status === 'sent' || env.status === 'delivered') { statusEl.innerHTML = ` ${env.status}`; actionsEl.innerHTML = ``; } else if (env.status === 'voided') { statusEl.innerHTML = 'Voided'; actionsEl.innerHTML = ``; } else if (env.status === 'timeout') { statusEl.innerHTML = 'Timed Out'; actionsEl.innerHTML = ``; } // Re-wire newly injected buttons actionsEl.querySelectorAll('.btn-void-envelope').forEach(btn => { btn.onclick = () => _voidEnvelope(btn.dataset.id, btn.dataset.envelopeid); }); actionsEl.querySelectorAll('.btn-send-test').forEach(btn => { btn.onclick = () => { const settings = getSettings(); _showSendDialog(t, settings); }; }); } async function _voidEnvelope(adobeId, envelopeId) { if (!confirm('Void this test envelope?')) return; try { await api.verify.void(envelopeId); if (_envelopes[adobeId]) { _envelopes[adobeId].status = 'voided'; _envelopes[adobeId].polling = false; } _updateVerifyRow(adobeId); } catch (e) { alert('Failed to void envelope: ' + e.message); } }