const $ = (id) => document.getElementById(id); let lastSearchQuery = ''; let currentFolderId = null; let activeClientId = null; let currentHostname = ''; function setText(id, text) { const el = $(id); if (el) el.textContent = text; } function setHidden(id, hidden) { const el = $(id); if (!el) return; el.classList.toggle('hidden', !!hidden); } async function storageGet(keys) { return await chrome.storage.local.get(keys); } async function storageSet(obj) { return await chrome.storage.local.set(obj); } async function storageRemove(keys) { return await chrome.storage.local.remove(keys); } function fmtAddress(c) { const parts = [c.Address1 || c.address1 || c.addressLine1, c.Address2, c.City || c.city, c.State || c.state, c.Zip || c.zip || c.zipCode].filter(Boolean); return parts.join(' '); } function getBillPayUrl(carrierString) { if (!carrierString || typeof carrierString !== 'string') return null; const name = carrierString.toLowerCase(); if (name.includes('progressive')) return 'https://account.apps.progressive.com/access/ez-payment/policy-info'; if (name.includes('encova')) return 'https://www.encova.com/pay-bill/encova-insurance-pay-bill/'; if (name.includes('westfield') || name.includes('old guard')) return 'https://www.westfieldinsurance.com/billing'; if (name.includes('erie')) return 'https://www.erieinsurance.com/paymentcenterweb/billpay/payment/billpayment'; if (name.includes('western reserve') || name.includes('wrg')) return 'https://www.wrg-ins.com/Customers/Payment'; return null; } function formatDisplayDate(dateStr) { if (!dateStr) return ''; const isoMatch = String(dateStr).match(/^(\d{4})-(\d{2})-(\d{2})/); if (isoMatch) return `${isoMatch[2]}/${isoMatch[3]}/${isoMatch[1]}`; try { const d = new Date(dateStr); if (!isNaN(d.getTime())) return d.toLocaleDateString(); } catch (e) {} return dateStr; } // ── Progress bar helpers ─────────────────────────────────────────────────────── function showProgress(label, pct) { const wrap = $('load-progress'); const bar = $('load-progress-bar'); const lbl = $('load-progress-label'); if (!wrap) return; wrap.style.display = 'block'; if (bar) bar.style.width = pct + '%'; if (lbl) lbl.textContent = label; } function hideProgress() { const wrap = $('load-progress'); if (wrap) wrap.style.display = 'none'; } function makePasteRow(label, initialValue, onPaste, editConfig = null) { const row = document.createElement('div'); row.className = 'paste-row'; const l = document.createElement('div'); l.className = 'paste-label'; l.textContent = label; let v; let currentValue = initialValue || ''; if (editConfig) { v = document.createElement('input'); v.className = 'paste-input'; v.value = currentValue; v.placeholder = `Add ${label}...`; v.title = 'Click to edit and save'; v.addEventListener('change', async (e) => { const newVal = e.target.value.trim(); currentValue = newVal; v.style.borderColor = '#10b981'; v.style.backgroundColor = '#ecfdf5'; setTimeout(() => { v.style.borderColor = ''; v.style.backgroundColor = ''; }, 1000); if (editConfig.onSave) editConfig.onSave(newVal, editConfig.key); }); } else { v = document.createElement('div'); v.className = 'paste-value'; v.title = currentValue; v.textContent = currentValue; } const pasteBtn = document.createElement('button'); pasteBtn.className = 'paste-btn'; pasteBtn.textContent = 'Paste'; pasteBtn.addEventListener('click', () => onPaste(currentValue)); const copyBtn = document.createElement('button'); copyBtn.className = 'copy-btn'; copyBtn.textContent = 'Copy'; copyBtn.addEventListener('click', () => { navigator.clipboard.writeText(currentValue).then(() => { const orig = copyBtn.textContent; copyBtn.textContent = 'Copied!'; copyBtn.style.background = '#059669'; copyBtn.style.color = 'white'; setTimeout(() => { copyBtn.textContent = orig; copyBtn.style.background = ''; copyBtn.style.color = ''; }, 1200); }).catch(() => alert('Copy failed')); }); const btnContainer = document.createElement('div'); btnContainer.className = 'paste-btn-container'; btnContainer.appendChild(pasteBtn); btnContainer.appendChild(copyBtn); row.appendChild(l); row.appendChild(v); row.appendChild(btnContainer); return row; } async function getActiveTab() { const tabs = await chrome.tabs.query({ active: true, currentWindow: true }); return tabs && tabs[0] ? tabs[0] : null; } async function sendToActiveTab(message) { const tab = await getActiveTab(); if (!tab?.id) throw new Error('No active tab'); return await chrome.tabs.sendMessage(tab.id, message); } async function getApiToken() { const { ncApiToken } = await storageGet(['ncApiToken']); if (!ncApiToken) throw new Error('Missing API Token, please sign in in Settings.'); return ncApiToken; } async function apiFetch(url, options = {}) { const token = await getApiToken(); const res = await fetch(url, { ...options, headers: { 'Authorization': `Bearer ${token}`, 'Accept': 'application/json', ...(options.headers || {}) } }); if (!res.ok) { const text = await res.text().catch(() => ''); throw new Error(`API Error ${res.status}: ${text || res.statusText}`); } return res.json(); } async function saveNote(insuredId, htmlContent) { const body = { insured_database_id: insuredId, subject: htmlContent, body: htmlContent, creator_name: "Carrier Bridge Extension", type: "General" }; return apiFetch('https://api.nowcerts.com/api/Zapier/InsertNote', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); } async function getInsuredFiles(insuredId, folderId = null) { let url = `https://api.nowcerts.com/api/Insured/GetInsuredFilesList?insuredId=${insuredId}&isInsuredVisibleFolder=false`; if (folderId) url += `&folderId=${folderId}`; return apiFetch(url); } async function createFolder(insuredId, parentId, name) { const body = { parentId: parentId, name: name, InsuredDatabaseId: insuredId, creatorName: "Carrier Bridge Extension" }; return apiFetch('https://api.nowcerts.com/api/Files/CreateFolder', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); } async function uploadFile(insuredId, file, folderId = null) { const url = new URL('https://api.nowcerts.com/api/Insured/UploadInsuredFile'); url.searchParams.append('insuredId', insuredId); if (folderId) url.searchParams.append('folderId', folderId); url.searchParams.append('creatorName', 'Carrier Bridge Extension'); url.searchParams.append('isInsuredVisibleFolder', 'false'); const formData = new FormData(); formData.append('file', file); const token = await getApiToken(); const res = await fetch(url.toString(), { method: 'PUT', headers: { 'Authorization': `Bearer ${token}` }, body: formData }); if (!res.ok) { const text = await res.text().catch(() => ''); throw new Error(`Upload failed ${res.status}: ${text || res.statusText}`); } return res.json(); } // ── Render the paste fields for a client ────────────────────────────────────── function renderPasteFields(client) { const container = $('paste-fields'); if (!container) return; container.innerHTML = ''; const executePaste = async (val) => { try { await sendToActiveTab({ action: 'paste_to_focused', value: String(val || '') }); } catch (e) { alert('Paste failed: ' + (e?.message || String(e))); } }; const handleSaveField = async (newVal, key) => { client[key] = newVal; await storageSet({ activeClient: client }); const noteHTML = `INFO QUICK-EDITED via Extension:
${key} was updated to: ${newVal}`; saveNote(activeClientId, noteHTML).catch(e => console.warn('Silent note save failed', e)); }; // ── Client Info ── const clientHeader = document.createElement('h4'); clientHeader.style.cssText = 'margin:12px 0 6px 0;font-size:13px;color:#1e293b;'; clientHeader.textContent = 'Client Info'; container.appendChild(clientHeader); const clientFields = [ { label: 'Commercial', value: client.CommercialName || client.commercialName || '', key: 'CommercialName' }, { label: 'First Name', value: client.FirstName || client.firstName || '', key: 'FirstName' }, { label: 'Middle Name', value: client.MiddleName || client.middleName || client.MI || '', key: 'MiddleName' }, { label: 'Last Name', value: client.LastName || client.lastName || '', key: 'LastName' }, { label: 'Email', value: client.Email || client.email || client.PrimaryEmail || '', key: 'Email' }, { label: 'Phone', value: client.Phone || client.phone || client.PrimaryPhone || '', key: 'Phone' }, { label: 'Mobile', value: client.CellPhone || client.cellPhone || client.MobilePhone || '', key: 'CellPhone' }, { label: 'Home Phone', value: client.HomePhone || client.homePhone || '', key: 'HomePhone' }, { label: 'DOB', value: formatDisplayDate(client.BirthDate || client.dateOfBirth || client.DOB || ''), key: 'BirthDate' }, { label: 'SSN', value: client.SocialSecurityNumber || client.SSN || client.socialSecurityNumber || '', key: 'SocialSecurityNumber' }, { label: 'Tax ID', value: client.TaxId || client.taxId || client.TaxID || '', key: 'TaxId' }, { label: 'Street', value: client.Address1 || client.address1 || client.addressLine1 || '', key: 'Address1' }, { label: 'Apt/Suite', value: client.Address2 || client.address2 || '', key: 'Address2' }, { label: 'City', value: client.City || client.city || '', key: 'City' }, { label: 'State', value: client.State || client.state || '', key: 'State' }, { label: 'Zip', value: client.Zip || client.zip || client.zipCode || '', key: 'Zip' }, ]; const hideIfEmpty = ['CommercialName', 'MiddleName', 'CellPhone', 'HomePhone', 'TaxId', 'Address2']; clientFields.forEach(f => { if (hideIfEmpty.includes(f.key) && !f.value) return; container.appendChild(makePasteRow(f.label, f.value, executePaste, { key: f.key, onSave: handleSaveField })); }); // ── Vehicles ── if (Array.isArray(client.vehicles) && client.vehicles.length > 0) { const h = document.createElement('h4'); h.style.cssText = 'margin:16px 0 6px 0;font-size:13px;color:#1e293b;'; h.textContent = `Vehicles (${client.vehicles.length})`; container.appendChild(h); client.vehicles.forEach((v, i) => { const vin = v.VIN || v.vin || ''; const desc = `${v.Year || v.year || ''} ${v.Make || v.make || ''} ${v.Model || v.model || ''}`.trim(); if (vin) container.appendChild(makePasteRow(`VIN #${i+1}${desc ? ' (' + desc + ')' : ''}`, vin, executePaste)); if (desc) container.appendChild(makePasteRow(`Vehicle #${i+1}`, desc, executePaste)); }); } // ── Drivers ── if (Array.isArray(client.drivers) && client.drivers.length > 0) { const h = document.createElement('h4'); h.style.cssText = 'margin:16px 0 6px 0;font-size:13px;color:#1e293b;'; h.textContent = `Drivers (${client.drivers.length})`; container.appendChild(h); client.drivers.forEach((d, i) => { const name = `${d.firstName || d.FirstName || ''} ${d.lastName || d.LastName || ''}`.trim(); const lic = d.LicenseNumber || d.licenseNumber || ''; const dob = formatDisplayDate(d.birthday || d.BirthDate || d.dateOfBirth || d.DOB || ''); if (name) container.appendChild(makePasteRow(`Driver #${i+1}`, name, executePaste)); if (lic) container.appendChild(makePasteRow(`License #${i+1}`, lic, executePaste)); if (dob) container.appendChild(makePasteRow(`DOB #${i+1}`, dob, executePaste)); }); } // ── Policies ── if (Array.isArray(client.policies) && client.policies.length > 0) { const h = document.createElement('h4'); h.style.cssText = 'margin:16px 0 6px 0;font-size:13px;color:#1e293b;'; h.textContent = `Policies (${client.policies.length})`; container.appendChild(h); client.policies.forEach((p, i) => { const num = p.Number || p.number || p.PolicyNumber || ''; const eff = formatDisplayDate(p.EffectiveDate || p.effectiveDate || ''); const exp = formatDisplayDate(p.ExpirationDate || p.expirationDate || ''); const carrier = p.CarrierName || p.carrierName || p.CompanyName || p.companyName || (p.Carrier && p.Carrier.Name) || (p.Company && p.Company.Name) || ''; if (num) container.appendChild(makePasteRow(`Policy #${i+1}`, num, executePaste)); if (eff) container.appendChild(makePasteRow(`Eff Date #${i+1}`, eff, executePaste)); if (exp) container.appendChild(makePasteRow(`Exp Date #${i+1}`, exp, executePaste)); if (carrier) container.appendChild(makePasteRow(`Carrier #${i+1}`, carrier, executePaste)); const billUrl = getBillPayUrl(carrier); if (billUrl) { const payRow = document.createElement('div'); payRow.className = 'paste-row'; payRow.style.background = '#eff6ff'; payRow.style.borderColor = '#bfdbfe'; payRow.innerHTML = `
💳 Quick Pay
${carrier}
`; const payBtn = document.createElement('button'); payBtn.className = 'paste-btn'; payBtn.style.background = '#3b82f6'; payBtn.style.color = 'white'; payBtn.textContent = 'Pay Bill ↗'; payBtn.addEventListener('click', () => chrome.tabs.create({ url: billUrl })); payRow.querySelector('.paste-btn-container').appendChild(payBtn); container.appendChild(payRow); } }); } if (container.children.length < 3) { const note = document.createElement('p'); note.style.cssText = 'font-size:11px;color:#64748b;text-align:center;'; note.textContent = 'No additional policy/vehicle data found.'; container.appendChild(note); } renderNotesSection(); renderDocumentsSection(); } // ── Main client display — shows basic info instantly, loads rest in background ─ function renderActiveClient(client) { if (!client) { setHidden('active-client', true); hideProgress(); return; } setHidden('active-client', false); setText('active-name', client.FullName || client.commercialName || client.CommercialName || 'Selected Client'); activeClientId = client.Id || client.insuredId || client.DatabaseId || client.id || ''; // Render whatever we have immediately renderPasteFields(client); } // ── Load full client data with progress bar, update UI as each step completes ─ async function loadFullClient(basicClient) { const insuredId = basicClient.Id || basicClient.insuredId || basicClient.id || ''; if (!insuredId) return; // Show client immediately with basic info renderActiveClient(basicClient); showProgress('Loading client details...', 10); try { const token = (await storageGet(['ncApiToken'])).ncApiToken; if (!token) { hideProgress(); return; } const headers = { 'Authorization': `Bearer ${token}`, 'Accept': 'application/json' }; // Step 1: Full details showProgress('Loading client details...', 25); let insured = basicClient; try { const detailRes = await fetch(`https://api.nowcerts.com/api/InsuredDetailList?key=${insuredId}&Active=true&showAll=true`, { headers }); if (detailRes.ok) { const detailJson = await detailRes.json(); const raw = Array.isArray(detailJson?.value) ? detailJson.value[0] : Array.isArray(detailJson) ? detailJson[0] : detailJson || {}; insured = { ...basicClient, ...raw, FullName: raw.FullName || raw.fullName || basicClient.FullName || `${raw.FirstName || raw.firstName || ''} ${raw.LastName || raw.lastName || ''}`.trim(), FirstName: raw.FirstName || raw.firstName || basicClient.FirstName || '', LastName: raw.LastName || raw.lastName || basicClient.LastName || '', Email: raw.Email || raw.email || basicClient.Email || '', Phone: raw.Phone || raw.phone || raw.cellPhone || basicClient.Phone || '', Address1: raw.Address1 || raw.address1 || raw.addressLine1 || basicClient.Address1 || '', Address2: raw.Address2 || raw.address2 || raw.addressLine2 || basicClient.Address2 || '', City: raw.City || raw.city || basicClient.City || '', State: raw.State || raw.state || raw.StateAbbreviation || basicClient.State || '', Zip: raw.Zip || raw.zip || raw.zipCode || basicClient.Zip || '', BirthDate: raw.BirthDate || raw.dateOfBirth || raw.DateOfBirth || basicClient.BirthDate || '', SSN: raw.SocialSecurityNumber || raw.SSN || raw.socialSecurityNumber || '', Id: raw.Id || raw.id || raw.DatabaseId || insuredId, policies: basicClient.policies || [], vehicles: basicClient.vehicles || [], drivers: basicClient.drivers || [], }; } } catch (e) { console.warn('Detail fetch failed', e); } // Update UI with full details immediately renderActiveClient(insured); showProgress('Loading policies...', 50); // Step 2: Policies let policies = []; try { const polRes = await fetch('https://api.nowcerts.com/api/Insured/InsuredPolicies', { method: 'POST', headers: { ...headers, 'Content-Type': 'application/json' }, body: JSON.stringify({ insuredDataBaseId: [insuredId] }) }); if (polRes.ok) { const polJson = await polRes.json(); policies = Array.isArray(polJson) ? polJson : (polJson?.value || []); } } catch (e) { console.warn('Policies fetch failed', e); } insured = { ...insured, policies }; renderActiveClient(insured); showProgress('Loading vehicles & drivers...', 75); // Step 3 & 4: Vehicles + Drivers in parallel let vehicles = [], drivers = []; const policyIds = policies.map(p => p.DatabaseId || p.databaseId || p.PolicyDatabaseId || '').filter(Boolean); if (policyIds.length) { await Promise.all([ fetch('https://api.nowcerts.com/api/Policy/PolicyVehicles', { method: 'POST', headers: { ...headers, 'Content-Type': 'application/json' }, body: JSON.stringify({ policyDataBaseId: policyIds }) }).then(r => r.ok ? r.json() : []).then(d => { vehicles = Array.isArray(d) ? d : []; }).catch(() => {}), fetch('https://api.nowcerts.com/api/Policy/PolicyDrivers', { method: 'POST', headers: { ...headers, 'Content-Type': 'application/json' }, body: JSON.stringify({ policyDataBaseId: policyIds }) }).then(r => r.ok ? r.json() : []).then(d => { drivers = Array.isArray(d) ? d : []; }).catch(() => {}), ]); } showProgress('Done!', 100); const fullClient = { ...insured, policies, vehicles, drivers }; await storageSet({ activeClient: fullClient }); renderActiveClient(fullClient); setTimeout(hideProgress, 600); } catch (e) { console.error('loadFullClient error', e); hideProgress(); } } $('supapaste-search')?.addEventListener('input', (e) => { const term = e.target.value.toLowerCase(); const rows = document.querySelectorAll('#paste-fields .paste-row'); rows.forEach(row => { const text = row.textContent.toLowerCase(); const input = row.querySelector('input'); const inputVal = input ? input.value.toLowerCase() : ''; row.style.display = (text.includes(term) || inputVal.includes(term)) ? 'flex' : 'none'; }); document.querySelectorAll('#paste-fields h4').forEach(h => { let next = h.nextElementSibling; let hasVisible = false; while (next && next.tagName !== 'H4') { if (next.style.display !== 'none' && next.classList.contains('paste-row')) { hasVisible = true; break; } next = next.nextElementSibling; } h.style.display = hasVisible ? 'block' : 'none'; }); }); function renderNotesSection() { const editor = $('note-editor'); editor.innerHTML = ''; editor.focus(); document.querySelectorAll('.rich-toolbar button').forEach(btn => { btn.onclick = () => { document.execCommand(btn.dataset.cmd, false, null); editor.focus(); }; }); $('save-note-btn').onclick = async () => { if (!activeClientId) return alert('No client selected'); const content = editor.innerHTML.trim(); if (!content) return alert('Note is empty'); try { await saveNote(activeClientId, content); alert('Note saved successfully'); editor.innerHTML = ''; } catch (e) { alert('Failed to save note: ' + e.message); } }; $('log-quote-btn').onclick = async () => { if (!activeClientId) return alert('No client selected'); const carrier = $('quote-carrier').value.trim(); const premium = $('quote-premium').value.trim(); if (!carrier || !premium) return alert('Enter carrier and premium.'); const noteHTML = `Quote Logged: ${carrier} — $${premium}`; try { await saveNote(activeClientId, noteHTML); $('quote-carrier').value = ''; $('quote-premium').value = ''; alert('Quote logged!'); } catch (e) { alert('Failed: ' + e.message); } }; } async function loadFiles(folderId = null) { if (!activeClientId) { $('files-list').innerHTML = '
No client selected
'; $('current-path').textContent = 'Current: Root (Files)'; return; } const list = $('files-list'); list.innerHTML = '
Loading documents...
'; try { const response = await getInsuredFiles(activeClientId, folderId); const data = response.Data || response.data || response; currentFolderId = data.CurrentFolderId || data.currentFolderId || folderId; $('current-path').textContent = currentFolderId ? `Current: Folder (ID: ${currentFolderId})` : 'Current: Root (Files)'; list.innerHTML = ''; const items = data.Files || data.files || data.value || []; if (!items.length) { list.innerHTML = '
This folder is empty
'; return; } items.forEach(item => { const div = document.createElement('div'); div.className = 'file-item'; const isFolder = item.Type === 2 || item.type === 2 || item.fileOrFolder?.toLowerCase() === 'folder'; if (isFolder) { div.innerHTML = `📁 ${item.Name || item.name || 'Folder'}`; div.style.cursor = 'pointer'; div.onclick = () => loadFiles(item.DatabaseId || item.databaseId || item.id || item.Id); } else { div.innerHTML = `📄 ${item.Name || item.name || 'File'}`; const dlUrl = item.DownloadUrl || item.downloadUrl; if (dlUrl) { const link = document.createElement('a'); link.href = dlUrl; link.textContent = ' ↓'; link.target = '_blank'; link.style.cssText = 'margin-left:10px;font-size:11px;'; div.appendChild(link); } } list.appendChild(div); }); } catch (e) { list.innerHTML = `
Error: ${e.message}
`; } } function renderDocumentsSection() { $('refresh-files-btn')?.addEventListener('click', () => { currentFolderId = null; loadFiles(null); }); const dropZone = $('drop-zone'); if (dropZone) { dropZone.addEventListener('click', () => { const input = document.createElement('input'); input.type = 'file'; input.multiple = true; input.onchange = (e) => handleFileUpload(e.target.files); input.click(); }); dropZone.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.classList.add('dragover'); }); dropZone.addEventListener('dragleave', () => dropZone.classList.remove('dragover')); dropZone.addEventListener('drop', (e) => { e.preventDefault(); dropZone.classList.remove('dragover'); handleFileUpload(e.dataTransfer.files); }); } $('create-folder-btn')?.addEventListener('click', async () => { const name = $('folder-name')?.value.trim(); if (!name) return alert('Enter a folder name.'); if (!activeClientId) return alert('No client selected.'); try { await createFolder(activeClientId, currentFolderId, name); if ($('folder-name')) $('folder-name').value = ''; await loadFiles(currentFolderId); } catch (e) { alert('Failed to create folder: ' + e.message); } }); loadFiles(null); } async function handleFileUpload(files) { if (!files || !files.length) return; if (!activeClientId) return alert('No client selected.'); const status = $('upload-status'); for (const file of files) { if (status) status.textContent = `Uploading ${file.name}...`; try { await uploadFile(activeClientId, file, currentFolderId); if (status) status.textContent = `✅ ${file.name} uploaded`; await loadFiles(currentFolderId); } catch (e) { if (status) status.textContent = `❌ Failed: ${e.message}`; } } } function renderResults(results) { const container = $('results-container'); if (!container) return; container.innerHTML = ''; if (!Array.isArray(results) || results.length === 0) { const p = document.createElement('p'); p.style.cssText = 'font-size:12px;color:#64748b;text-align:center;'; p.textContent = 'No matching clients found.'; container.appendChild(p); const btnRow = document.createElement('div'); btnRow.style.marginTop = '10px'; const btn = document.createElement('button'); btn.className = 'btn'; btn.textContent = 'Create Prospect'; btn.addEventListener('click', () => showCreateProspectForm(lastSearchQuery)); btnRow.appendChild(btn); container.appendChild(btnRow); return; } results.slice(0, 20).forEach((c) => { const card = document.createElement('div'); card.className = 'card'; const name = document.createElement('h3'); name.textContent = c.FullName || c.commercialName || c.CommercialName || 'Client'; const sub = document.createElement('p'); sub.className = 'subtext'; sub.textContent = [c.Email, c.Phone || c.CellPhone, fmtAddress(c)].filter(Boolean).join(' · '); card.appendChild(name); card.appendChild(sub); const tag = document.createElement('div'); tag.className = 'tag'; tag.textContent = c.Id ? `ID: ${c.Id}` : 'Select'; card.appendChild(tag); // ── Click: show basic info instantly, load rest in background ── card.addEventListener('click', async () => { // Immediately show what we have from search results await storageSet({ activeClient: c }); renderActiveClient(c); // Then load full data with progress bar in background loadFullClient(c); }); container.appendChild(card); }); } function showCreateProspectForm(seedText) { const container = $('results-container'); if (!container) return; container.innerHTML = ''; const seed = String(seedText || '').trim(); const parts = seed.split(/\s+/).filter(Boolean); const guessFirst = parts[0] || ''; const guessLast = parts.length > 1 ? parts.slice(1).join(' ') : ''; const card = document.createElement('div'); card.className = 'card'; const title = document.createElement('h3'); title.textContent = 'Create Prospect'; card.appendChild(title); const note = document.createElement('p'); note.style.fontSize = '11px'; note.textContent = 'Creates an insured in NowCerts and refreshes results.'; card.appendChild(note); const grid = document.createElement('div'); grid.style.cssText = 'display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px;'; const mkField = (label, value = '', span2 = false, placeholder = '') => { const wrap = document.createElement('div'); if (span2) wrap.style.gridColumn = 'span 2'; const l = document.createElement('div'); l.style.fontSize = '11px'; l.textContent = label; const inp = document.createElement('input'); inp.style.marginBottom = '5px'; inp.value = value; if (placeholder) inp.placeholder = placeholder; wrap.appendChild(l); wrap.appendChild(inp); return { wrap, inp }; }; const fFirst = mkField('First name', guessFirst); const fLast = mkField('Last name', guessLast); const fEmail = mkField('Email', '', false, 'name@example.com'); const fPhone = mkField('Phone', '', false, '(555) 555-5555'); const fStreet = mkField('Street', '', true, '123 Main St'); const fCity = mkField('City'); const fState = mkField('State', '', false, 'OH'); const fZip = mkField('ZIP', '', false, '43950'); [fFirst, fLast, fEmail, fPhone, fStreet, fCity, fState, fZip].forEach(f => grid.appendChild(f.wrap)); card.appendChild(grid); const actions = document.createElement('div'); actions.style.cssText = 'display:flex;gap:8px;margin-top:10px;'; const btnCreate = document.createElement('button'); btnCreate.textContent = 'Create'; btnCreate.style.background = '#10b981'; const btnCancel = document.createElement('button'); btnCancel.textContent = 'Cancel'; btnCancel.style.background = '#64748b'; actions.appendChild(btnCreate); actions.appendChild(btnCancel); card.appendChild(actions); const status = document.createElement('div'); status.style.cssText = 'font-size:11px;margin-top:8px;'; card.appendChild(status); btnCancel.addEventListener('click', async () => { if (lastSearchQuery) { if ($('search-input')) $('search-input').value = lastSearchQuery; await doSearch(); } else { renderResults([]); } }); btnCreate.addEventListener('click', async () => { const payload = { firstName: fFirst.inp.value, lastName: fLast.inp.value, email: fEmail.inp.value, phone: fPhone.inp.value, street: fStreet.inp.value, city: fCity.inp.value, state: fState.inp.value, zip: fZip.inp.value, }; try { status.textContent = 'Creating...'; btnCreate.disabled = true; const resp = await chrome.runtime.sendMessage({ action: 'create_prospect', payload }); if (!resp?.ok) throw new Error(resp?.error || 'Create failed'); const results = Array.isArray(resp.results) ? resp.results : []; const match = results[0]; if (match) { await storageSet({ activeClient: match }); renderActiveClient(match); loadFullClient(match); } status.textContent = 'Created successfully.'; renderResults(results); } catch (e) { status.textContent = 'Error: ' + (e?.message || String(e)); } finally { btnCreate.disabled = false; } }); container.appendChild(card); } async function loadSettingsIntoUI() { const { ncApiToken, ncTokenUrl, geminiApiKey } = await storageGet(['ncApiToken', 'ncTokenUrl', 'geminiApiKey']); if ($('api-token')) $('api-token').value = ncApiToken ? '••••••••••••' : ''; if ($('token-url')) $('token-url').value = ncTokenUrl || 'https://api.nowcerts.com/token'; if ($('gemini-api-key')) $('gemini-api-key').value = geminiApiKey || ''; } async function doSearch() { const q = ($('search-input')?.value || '').trim(); if (!q) return; lastSearchQuery = q; $('search-btn').disabled = true; $('search-btn').textContent = 'Searching...'; try { const resp = await chrome.runtime.sendMessage({ action: 'search_nowcerts', query: q }); if (!resp?.ok) throw new Error(resp?.error || 'Search failed'); renderResults(resp.results || []); } catch (err) { alert(err.message); } finally { $('search-btn').disabled = false; $('search-btn').textContent = 'Search NowCerts'; } } async function saveTokenManual() { const token = ($('api-token')?.value || '').trim(); if (!token || token.includes('••••')) { alert('Paste the real token first.'); return; } await storageSet({ ncApiToken: token }); alert('Token saved.'); await loadSettingsIntoUI(); } async function clearToken() { await storageRemove(['ncApiToken']); alert('Token cleared.'); await loadSettingsIntoUI(); } async function loginAndSaveToken() { const status = $('login-status'); const setStatus = (t) => { if (status) status.textContent = t; }; const tokenUrl = ($('token-url')?.value || '').trim() || 'https://api.nowcerts.com/token'; const username = ($('nc-username')?.value || '').trim(); const password = ($('nc-password')?.value || '').trim(); const clientId = ($('nc-client-id')?.value || '').trim(); const clientSecret= ($('nc-client-secret')?.value || '').trim(); if (!username || !password) { alert('Enter your username and password.'); return; } $('login-save').disabled = true; $('login-save').textContent = 'Signing in...'; setStatus('Requesting token...'); try { await storageSet({ ncTokenUrl: tokenUrl }); const params = new URLSearchParams(); params.set('grant_type', 'password'); params.set('username', username); params.set('password', password); if (clientId) params.set('client_id', clientId); if (clientSecret) params.set('client_secret', clientSecret); const res = await fetch(tokenUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: params.toString(), }); const text = await res.text(); let data = null; try { data = JSON.parse(text); } catch (_) {} if (!res.ok) { const msg = data && (data.error_description || data.error || data.message) ? (data.error_description || data.error || data.message) : text; throw new Error(msg || `Token request failed (${res.status})`); } const token = data?.access_token || data?.token || data?.AccessToken; if (!token) throw new Error('Token response did not include access_token.'); await storageSet({ ncApiToken: token }); setStatus('Token saved.'); alert('Signed in and token saved.'); await loadSettingsIntoUI(); } catch (e) { setStatus('Login failed: ' + (e?.message || String(e))); alert('Login failed: ' + (e?.message || String(e))); } finally { $('login-save').disabled = false; $('login-save').textContent = 'Sign in and save token'; } } async function main() { $('toggle-settings')?.addEventListener('click', () => { $('settings-view')?.classList.toggle('hidden'); }); $('search-btn')?.addEventListener('click', doSearch); $('create-prospect-btn')?.addEventListener('click', () => showCreateProspectForm(($('search-input')?.value || lastSearchQuery || '').trim())); $('search-input')?.addEventListener('keydown', (e) => { if (e.key === 'Enter') doSearch(); }); $('save-token')?.addEventListener('click', saveTokenManual); $('clear-token')?.addEventListener('click', clearToken); $('login-save')?.addEventListener('click', loginAndSaveToken); $('save-gemini-key')?.addEventListener('click', async () => { const key = $('gemini-api-key').value.trim(); if (!key) return alert("Please enter an API key."); await storageSet({ geminiApiKey: key }); alert("Gemini API Key Saved!"); }); $('fill-form-btn')?.addEventListener('click', async () => { try { await sendToActiveTab({ action: 'fill_form' }); } catch (e) { alert('Auto-fill failed: ' + (e?.message || String(e))); } }); $('open-nc-btn')?.addEventListener('click', async () => { if (!activeClientId) return; const { activeClient } = await chrome.storage.local.get(['activeClient']); let isProspect = false; if (activeClient) { if (activeClient.IsProspect === true || activeClient.isProspect === true) isProspect = true; const typeStr = String(activeClient.type || activeClient.Type || activeClient.InsuredType || activeClient.insuredType || '').toLowerCase(); const statusStr = String(activeClient.Status || activeClient.status || '').toLowerCase(); if (typeStr.includes('prospect') || statusStr.includes('prospect')) isProspect = true; if (typeof activeClient.InsuredType === 'object' && activeClient.InsuredType !== null) { if (String(activeClient.InsuredType.Name || activeClient.InsuredType.name || '').toLowerCase().includes('prospect')) isProspect = true; } } const baseUrl = 'https://www6.nowcerts.com/AMSINS'; const urlPath = isProspect ? 'Prospects/Details' : 'Insureds/Details'; chrome.tabs.create({ url: `${baseUrl}/${urlPath}/${activeClientId}/Information` }); }); $('clear-client')?.addEventListener('click', async () => { await storageRemove(['activeClient']); renderActiveClient(null); }); await loadSettingsIntoUI(); // Handle Active Tab Hostname and AI State const activeTab = await getActiveTab(); if (activeTab && activeTab.url && activeTab.url.startsWith('http')) { try { currentHostname = new URL(activeTab.url).hostname; const mapKey = `smartMap_${currentHostname}`; const res = await storageGet([mapKey]); if (res[mapKey]) { $('ai-status-title').textContent = "✨ Site Mapped!"; $('ai-status-text').textContent = "AI knows these fields."; $('ai-status-icon').textContent = "✅"; $('ai-scan-btn').textContent = "Rescan"; $('ai-scan-btn').style.background = "#10b981"; await sendToActiveTab({ action: 'show_cute_boxes', smartMap: res[mapKey] }); } else { $('ai-status-title').textContent = "🔍 Unmapped Site"; $('ai-status-text').textContent = "Click to scan fields."; $('ai-status-icon').textContent = "🤖"; } } catch(e) {} } // Handle AI Scan Button Click // Handle AI Scan Button Click $('ai-scan-btn')?.addEventListener('click', async () => { // Grab the active tab RIGHT NOW, not when the panel first opened const activeTab = await getActiveTab(); if (!activeTab || !activeTab.url || !activeTab.url.startsWith('http')) { return alert("Open a valid webpage to scan."); } currentHostname = new URL(activeTab.url).hostname; const { geminiApiKey } = await storageGet(['geminiApiKey']); if (!geminiApiKey) { $('settings-view').classList.remove('hidden'); return alert("Please save your Gemini API Key in Settings first!"); } $('ai-scan-btn').textContent = "Scanning..."; $('ai-scan-btn').disabled = true; try { const extractRes = await sendToActiveTab({ action: 'extract_form_structure' }); if (!extractRes || !extractRes.fields) throw new Error("Could not extract any fields from this page."); const aiRes = await chrome.runtime.sendMessage({ action: 'run_ai_scan', apiKey: geminiApiKey, fields: extractRes.fields, hostname: currentHostname }); if (!aiRes.ok) throw new Error(aiRes.error); $('ai-status-title').textContent = "✨ Site Mapped!"; $('ai-status-text').textContent = "AI learned these fields!"; $('ai-status-icon').textContent = "✅"; $('ai-scan-btn').textContent = "Rescan"; $('ai-scan-btn').style.background = "#10b981"; await sendToActiveTab({ action: 'show_cute_boxes', smartMap: aiRes.smartMap }); } catch(e) { alert("Scan failed: " + e.message); $('ai-scan-btn').textContent = "Scan Page"; } finally { $('ai-scan-btn').disabled = false; } }); const { activeClient } = await storageGet(['activeClient']); if (activeClient) { renderActiveClient(activeClient); // If stored client is missing policies/vehicles, reload in background if (!activeClient.policies?.length && !activeClient.vehicles?.length) { loadFullClient(activeClient); } } } document.addEventListener('DOMContentLoaded', main);