209 lines
7.7 KiB
JavaScript
209 lines
7.7 KiB
JavaScript
// Auth: connect/disconnect Adobe Sign and Docusign, auth status chips
|
|
|
|
import { api } from './api.js';
|
|
import { state, setState } from './state.js';
|
|
import { escHtml } 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',
|
|
});
|
|
} catch (e) {
|
|
console.warn('Auth status failed:', e.message);
|
|
}
|
|
renderAuthChips();
|
|
}
|
|
|
|
// ── Render connection pills in top bar ─────────────────────────────────────
|
|
|
|
export function renderAuthChips() {
|
|
renderChip('chip-adobe', state.auth.adobe, 'Adobe Sign', onClickAdobe);
|
|
renderChip('chip-docusign', state.auth.docusign, 'Docusign', onClickDocusign);
|
|
}
|
|
|
|
function renderChip(id, connected, label, onClick) {
|
|
const el = document.getElementById(id);
|
|
if (!el) return;
|
|
el.className = 'conn-pill ' + (connected ? 'connected' : 'disconnected');
|
|
el.innerHTML = `<span class="conn-dot"></span>${escHtml(label)}`;
|
|
el.onclick = onClick;
|
|
}
|
|
|
|
// ── Click handlers ─────────────────────────────────────────────────────────
|
|
|
|
async function onClickAdobe() {
|
|
if (state.auth.adobe) {
|
|
await disconnect('adobe');
|
|
} else {
|
|
await connectAdobeEnv();
|
|
}
|
|
}
|
|
|
|
async function onClickDocusign() {
|
|
if (state.auth.docusign) {
|
|
await disconnect('docusign');
|
|
} else {
|
|
await connectDocusign();
|
|
}
|
|
}
|
|
|
|
async function disconnect(platform) {
|
|
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();
|
|
} catch (e) {
|
|
console.error('Disconnect failed:', e.message);
|
|
renderAuthChips();
|
|
}
|
|
}
|
|
|
|
async function connectAdobeEnv() {
|
|
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() {
|
|
setChipConnecting('chip-docusign');
|
|
try {
|
|
const data = await api.auth.connectDocusign();
|
|
if (data.connected) {
|
|
setState('auth', { ...state.auth, docusign: true });
|
|
renderAuthChips();
|
|
const { refreshTemplates } = await import('./templates.js');
|
|
refreshTemplates();
|
|
} 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 = `<span class="conn-dot"></span><span class="spinner spinner-sm"></span>`;
|
|
}
|
|
|
|
// ── 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 = `
|
|
<div class="modal-backdrop"></div>
|
|
<div class="modal-box">
|
|
<div class="modal-header">
|
|
<span class="modal-title">Connect Adobe Sign</span>
|
|
<button class="btn btn-ghost btn-icon" id="adobe-dialog-close">✕</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<ol style="padding-left:18px;line-height:1.8;margin-bottom:14px;font-size:13px">
|
|
<li><a href="${escHtml(url)}" target="_blank" rel="noopener" style="color:var(--cobalt)">Click here to authorize in Adobe Sign ↗</a></li>
|
|
<li>After authorizing, your browser will show a page that fails to load — that's expected.</li>
|
|
<li>Copy the full URL from the address bar and paste it below.</li>
|
|
</ol>
|
|
<input type="text" id="adobe-redirect-input" class="form-input"
|
|
placeholder="https://localhost:8080/callback?code=…" />
|
|
<div id="adobe-dialog-error" style="color:var(--error);font-size:12px;min-height:18px;margin-top:6px"></div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" id="adobe-dialog-cancel">Cancel</button>
|
|
<button class="btn btn-primary" id="adobe-dialog-submit">Connect</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
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 {
|
|
const data = 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:360px;animation:fadeIn 0.2s ease;
|
|
`;
|
|
toast.textContent = message;
|
|
container.appendChild(toast);
|
|
setTimeout(() => toast.remove(), 4000);
|
|
}
|