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);