Manage token auth, licenses, and calculator behavior from a single control panel.
Not authenticated
Admin Token
Token is stored in sessionStorage (browser session only).
Calculator Origin
Used for embed + Open Calculator.
License Management
Where estimate requests are delivered. Falls back to the contact email below if blank. Never sent to the browser.
Name the visitor sees on their emailed estimate. Defaults to the firm name in the config.
Where a reply to the firm notification goes. Blank means replies reach the visitor directly.
How long stored leads survive before expiring. Blank uses the 90-day default. Max 365.
Appears on both the visitor's estimate and the firm notification. Blank uses a built-in default — emails are never sent without one.
Fetch a license to see lead counts.
Store hostnames only (no protocol, paths, or trailing slashes).
Embed Code Per-license
The iframe will auto-resize based on the calculator's content height.
Configuration JSON
Locations
Fee is optional for “other”; leave blank to omit.
Line Items
Edits below immediately update the Configuration JSON.
Calculator Inputs
End-users enter the amount at runtime. Do not set a default fee here.
License Dashboard
License Key Status Firm Firm Phone Contact Name Contact Phone Contact Email Valid Through Actions
Response
No response yet
`; } function parseConfigJsonOrThrow() { const raw = (configJsonEl.value || "").trim(); if (!raw) return {}; try { return ensureBrokerConfig(JSON.parse(raw)); } catch { throw new Error("Config JSON is invalid."); } } function prettifyConfigJson() { const obj = parseConfigJsonOrThrow(); configJsonEl.value = JSON.stringify(obj, null, 2); } function getRecordFromUI() { const key = (licenseKeyEl.value || "").trim(); if (!key) throw new Error("Missing license key."); const status = String(statusEl.value || "active").trim().toLowerCase(); if (!["active","inactive"].includes(status)) throw new Error("Status must be active or inactive."); const allowNoReferer = !!allowNoRefererEl.checked; const configObj = parseConfigJsonOrThrow(); const notifyEmail = (leadsNotifyEmailEl.value || "").trim(); if (notifyEmail && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(notifyEmail)) { throw new Error("Lead notification email is not a valid address."); } if (leadsEnabledEl.checked && !notifyEmail && !(loadedRecord?.contact?.email || "").trim()) { throw new Error("Lead capture needs a notification email, or a contact email to fall back to."); } const replyToOverride = (leadsReplyToOverrideEl.value || "").trim(); if (replyToOverride && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(replyToOverride)) { throw new Error("Reply-to override is not a valid address."); } const retentionRaw = (leadsRetentionDaysEl.value || "").trim(); let retentionDays; if (retentionRaw) { const n = Number(retentionRaw); if (!Number.isFinite(n) || n < 1 || n > 365) { throw new Error("Lead retention must be a number of days between 1 and 365."); } retentionDays = Math.floor(n); } // Start from whatever the server last gave us for this key so fields this // form does not render (contact, validThrough) survive the round trip. const base = loadedRecordKey === key && loadedRecord ? loadedRecord : null; return { key, record: mergeLicenseRecord(base, { status, allowedDomains: getDomainsFromUI(), allowNoReferer, config: configObj, leads: { enabled: !!leadsEnabledEl.checked, notifyEmail, firmDisplayName: (leadsFirmDisplayNameEl.value || "").trim(), replyToOverride, disclaimerHtml: (leadsDisclaimerHtmlEl.value || "").trim(), // Omitted rather than sent empty, so a blank box means "use the // default" instead of writing a value the Worker would reject. ...(retentionDays === undefined ? {} : { retentionDays }) } }) }; } // Pure, DOM-free, and extracted on purpose so it can be tested directly. // // Save previously rebuilt the record from only the fields this form renders // and POSTed the result whole, which silently destroyed contact and // validThrough on every save. Anything not named in `edits` must survive // untouched; `leads` merges key-by-key so a future field added to the record // is not wiped by an older form that does not know about it. // // See lexsheet-gateway/scripts/test-admin-record.mjs. function mergeLicenseRecord(base, edits) { const out = base ? structuredClone(base) : {}; for (const [field, value] of Object.entries(edits || {})) { if (value === undefined) continue; if (field === "leads") { out.leads = { ...(out.leads || {}), ...value }; } else { out[field] = value; } } return out; } function fillUIFromRecord(key, record) { loadedRecord = record ? structuredClone(record) : null; loadedRecordKey = key || ""; licenseKeyEl.value = key || ""; statusEl.value = record?.status === "inactive" ? "inactive" : "active"; allowNoRefererEl.checked = record?.allowNoReferer === true; leadsEnabledEl.checked = record?.leads?.enabled === true; leadsNotifyEmailEl.value = record?.leads?.notifyEmail || ""; leadsFirmDisplayNameEl.value = record?.leads?.firmDisplayName || ""; leadsReplyToOverrideEl.value = record?.leads?.replyToOverride || ""; leadsRetentionDaysEl.value = record?.leads?.retentionDays ?? ""; leadsDisclaimerHtmlEl.value = record?.leads?.disclaimerHtml || ""; leadStatsEl.textContent = "Fetch counts to see leads for this license."; setDomainsUI(record?.allowedDomains || []); const cfg = record?.config ? ensureBrokerConfig(record.config) : null; configJsonEl.value = cfg ? JSON.stringify(cfg, null, 2) : ""; updateEmbedBox(); loadLocationsFromConfig(); loadBrokerSettingsFromConfig(); renderQuickEditor(); } // -------------------- Locations editor -------------------- function setLocationsStatus(msg) { if (locationsStatusEl) locationsStatusEl.textContent = msg || ""; } function loadLocationsFromConfig() { let cfg; try { cfg = parseConfigJsonOrThrow(); } catch (e) { setLocationsStatus(e.message || "Invalid JSON"); return; } const counties = cfg.ui?.labels?.county || {}; const countyLines = Object.keys(counties).map((k) => `${k} = ${counties[k]}`); if (countyListInput) countyListInput.value = countyLines.join("\n"); const muniLabels = cfg.ui?.labels?.muni || {}; const muniFees = cfg.municipalFees || {}; const muniLines = Object.keys(muniLabels).map((k) => { const fee = muniFees[k]; const feePart = Number.isFinite(fee) ? ` | ${fee}` : ""; return `${k} = ${muniLabels[k]}${feePart}`; }); if (muniListInput) muniListInput.value = muniLines.join("\n"); setLocationsStatus("Loaded"); } function parseKeyLabelFee(line) { const [left, feePart] = line.split("|").map(s => s.trim()); if (!left) return null; const [keyRaw, ...labelParts] = left.split("="); const key = (keyRaw || "").trim(); const label = labelParts.join("=").trim(); if (!key || !label) return null; const fee = feePart ? Number(feePart) : NaN; return { key, label, fee: Number.isFinite(fee) ? fee : null }; } function applyLocationsToConfig() { let cfg; try { cfg = parseConfigJsonOrThrow(); } catch (e) { setLocationsStatus(e.message || "Invalid JSON"); return; } const countyLines = (countyListInput?.value || "").split(/\n+/).map(l => l.trim()).filter(Boolean); const muniLines = (muniListInput?.value || "").split(/\n+/).map(l => l.trim()).filter(Boolean); const countyMap = {}; const countyOrder = []; countyLines.forEach((line) => { const [keyRaw, ...labelParts] = line.split("="); const key = (keyRaw || "").trim(); const label = labelParts.join("=").trim(); if (key && label) { countyMap[key] = label; countyOrder.push(key); } }); if (!Object.keys(countyMap).length) { setLocationsStatus("No counties parsed."); return; } const muniLabels = {}; const muniFees = {}; muniLines.forEach((line) => { const parsed = parseKeyLabelFee(line); if (!parsed) return; muniLabels[parsed.key] = parsed.label; if (parsed.fee !== null) muniFees[parsed.key] = parsed.fee; }); if (!Object.keys(muniLabels).length) { setLocationsStatus("No municipal entries parsed."); return; } cfg.ui = cfg.ui || {}; cfg.ui.labels = cfg.ui.labels || {}; cfg.ui.labels.county = countyMap; cfg.ui.countyOrder = countyOrder; cfg.ui.labels.muni = muniLabels; cfg.municipalFees = muniFees; configJsonEl.value = JSON.stringify(cfg, null, 2); setLocationsStatus("Applied"); renderQuickEditor(); } // -------------------- Broker fee visibility -------------------- function setBrokerStatus(msg) { if (brokerStatusEl) brokerStatusEl.textContent = msg || ""; } function loadBrokerSettingsFromConfig() { try { const cfg = parseConfigJsonOrThrow(); const show = cfg.ui?.showBrokerTransactionFee !== false; const payer = (cfg.ui?.defaults?.brokerTransactionFeePayer === "seller") ? "seller" : "buyer"; if (brokerFeeEnabledEl) brokerFeeEnabledEl.checked = show; if (brokerFeeDefaultPayerEl) brokerFeeDefaultPayerEl.value = payer; setBrokerStatus("Loaded"); } catch (e) { setBrokerStatus(e.message || "Invalid JSON"); } } function applyBrokerSettingsToConfig() { try { const cfg = parseConfigJsonOrThrow(); cfg.ui = cfg.ui || {}; cfg.ui.defaults = cfg.ui.defaults || {}; cfg.ui.showBrokerTransactionFee = brokerFeeEnabledEl ? !!brokerFeeEnabledEl.checked : true; cfg.ui.defaults.brokerTransactionFeePayer = (brokerFeeDefaultPayerEl?.value === "seller") ? "seller" : "buyer"; configJsonEl.value = JSON.stringify(cfg, null, 2); setBrokerStatus("Applied"); renderQuickEditor(); } catch (e) { setBrokerStatus(e.message || "Invalid JSON"); } } // -------------------- Quick line item editor -------------------- let quickWorkingConfig = null; function setQuickStatus(msg) { if (quickStatusEl) quickStatusEl.textContent = msg || ""; } function normalizePaidBy(p) { if (!p || typeof p !== "object") return { type: "buyer" }; if (p.type === "seller") return { type: "seller" }; if (p.type === "split") { const b = Number.isFinite(p.buyerPct) ? p.buyerPct : 50; const s = Number.isFinite(p.sellerPct) ? p.sellerPct : 50; return { type: "split", buyerPct: b, sellerPct: s }; } return { type: "buyer" }; } function writeQuickConfig() { if (!quickWorkingConfig) return; configJsonEl.value = JSON.stringify(quickWorkingConfig, null, 2); setQuickStatus("Updated JSON"); } function resolveAmountField(calc) { const t = calc?.type; if (t === "flat") return { field: "amount", label: "Amount" }; if (t === "perUnit") return { field: "unitAmount", label: "Unit" }; if (t === "eRecordingPerDocument") return { field: "perDocument", label: "Per doc" }; return null; } function renderQuickEditor() { if (!quickEditorEl) return; let cfg; try { cfg = parseConfigJsonOrThrow(); } catch (e) { quickEditorEl.innerHTML = ""; setQuickStatus(e.message || "Invalid JSON"); return; } quickWorkingConfig = cfg; const items = Array.isArray(cfg.lineItems) ? cfg.lineItems : []; quickEditorEl.innerHTML = ""; if (!items.length) { setQuickStatus("No lineItems"); return; } items.forEach((item, idx) => { const box = document.createElement("div"); box.className = "lineitem"; const head = document.createElement("div"); head.className = "lineitem-head"; const left = document.createElement("div"); const title = document.createElement("div"); title.className = "lineitem-title"; title.textContent = item.label || item.id || `Line ${idx+1}`; const idLine = document.createElement("div"); idLine.className = "lineitem-id mono"; idLine.textContent = item.id || ""; left.appendChild(title); left.appendChild(idLine); const right = document.createElement("div"); right.className = "pill"; right.innerHTML = ` Enabled`; const enabledChk = right.querySelector("input"); enabledChk.addEventListener("change", () => { item.enabled = !!enabledChk.checked; writeQuickConfig(); }); head.appendChild(left); head.appendChild(right); box.appendChild(head); // Payor const paid = normalizePaidBy(item.paidBy); const payRow = document.createElement("div"); payRow.className = "radioRow"; const makeRadio = (val, text) => { const wrap = document.createElement("label"); const r = document.createElement("input"); r.type = "radio"; r.name = `payor-${idx}`; r.value = val; r.checked = paid.type === val; wrap.appendChild(r); wrap.appendChild(document.createTextNode(text)); r.addEventListener("change", () => { if (val === "split") { // preserve existing split values if available const b = Number.isFinite(item.paidBy?.buyerPct) ? item.paidBy.buyerPct : 50; const s = Number.isFinite(item.paidBy?.sellerPct) ? item.paidBy.sellerPct : 50; item.paidBy = { type: "split", buyerPct: b, sellerPct: s }; splitWrap.classList.remove("hidden"); } else { item.paidBy = { type: val }; splitWrap.classList.add("hidden"); } writeQuickConfig(); }); return wrap; }; payRow.appendChild(makeRadio("buyer", "buyer")); payRow.appendChild(makeRadio("seller", "seller")); payRow.appendChild(makeRadio("split", "split")); box.appendChild(payRow); const splitWrap = document.createElement("div"); splitWrap.className = "miniGrid"; const buyerPct = document.createElement("div"); const sellerPct = document.createElement("div"); buyerPct.innerHTML = ``; sellerPct.innerHTML = ``; const bInp = buyerPct.querySelector("input"); const sInp = sellerPct.querySelector("input"); bInp.value = Number.isFinite(paid.buyerPct) ? String(paid.buyerPct) : "50"; sInp.value = Number.isFinite(paid.sellerPct) ? String(paid.sellerPct) : "50"; function setSplitFromInputs() { item.paidBy = { type: "split", buyerPct: Number(bInp.value || 0), sellerPct: Number(sInp.value || 0) }; // force split radio on const splitRadio = box.querySelector(`input[type="radio"][value="split"]`); if (splitRadio) splitRadio.checked = true; splitWrap.classList.remove("hidden"); writeQuickConfig(); } bInp.addEventListener("input", setSplitFromInputs); sInp.addEventListener("input", setSplitFromInputs); splitWrap.appendChild(buyerPct); splitWrap.appendChild(sellerPct); if (paid.type === "split") splitWrap.classList.remove("hidden"); else splitWrap.classList.add("hidden"); box.appendChild(splitWrap); // Amount control (when applicable) const amountInfo = resolveAmountField(item.calc || {}); if (amountInfo) { const amt = document.createElement("div"); amt.className = "field-group"; amt.innerHTML = ``; const inp = amt.querySelector("input"); inp.value = Number(item.calc?.[amountInfo.field] ?? 0); inp.addEventListener("input", () => { if (!item.calc) item.calc = {}; item.calc[amountInfo.field] = Number(inp.value || 0); writeQuickConfig(); }); box.appendChild(amt); } else { const hint = document.createElement("div"); hint.className = "hint"; hint.textContent = "Auto"; box.appendChild(hint); } quickEditorEl.appendChild(box); }); setQuickStatus(`Loaded ${items.length}`); } // -------------------- Dashboard helpers -------------------- function csvEscape(s) { const v = String(s ?? ""); if (/[",\n]/.test(v)) return `"${v.replace(/"/g, '""')}"`; return v; } function downloadCsv(filename, rows) { const csv = rows.map(r => r.map(csvEscape).join(",")).join("\n"); const blob = new Blob([csv], { type: "text/csv;charset=utf-8" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url); } function renderDashboard(licenses) { dashBody.innerHTML = ""; for (const row of licenses) { const tr = document.createElement("tr"); const td = (content) => { const c = document.createElement("td"); if (content instanceof Node) c.appendChild(content); else c.textContent = String(content ?? ""); return c; }; tr.appendChild(td(row.key || "")); tr.appendChild(td((row.status || "").toLowerCase() || "")); tr.appendChild(td(row.firmName || "")); tr.appendChild(td(row.firmPhone || "")); const mkInput = (field, value, placeholder="") => { const inp = document.createElement("input"); inp.className = "mono"; inp.setAttribute("data-field", field); inp.value = value || ""; if (placeholder) inp.placeholder = placeholder; return inp; }; tr.appendChild(td(mkInput("contactName", row.contactName || ""))); tr.appendChild(td(mkInput("contactPhone", row.contactPhone || ""))); tr.appendChild(td(mkInput("contactEmail", row.contactEmail || ""))); tr.appendChild(td(mkInput("validThrough", row.validThrough || "", "YYYY-MM-DD"))); const actionsCell = document.createElement("td"); const group = document.createElement("div"); group.className = "btn-group"; const saveBtn = document.createElement("button"); saveBtn.className = "btn btn-primary"; saveBtn.textContent = "Save"; const loadBtn = document.createElement("button"); loadBtn.className = "btn"; loadBtn.textContent = "Load"; const getVal = (field) => (tr.querySelector(`input[data-field="${field}"]`)?.value || "").trim(); saveBtn.addEventListener("click", async () => { try { const patch = { contact: { name: getVal("contactName"), phone: getVal("contactPhone"), email: getVal("contactEmail") }, validThrough: getVal("validThrough") }; const data = await adminFetch(`/admin/license`, { method: "POST", body: JSON.stringify({ key: row.key, patch }) }); showResponse(true, { saved: row.key, result: data }, "Dashboard save"); setStatusIndicator(true, "Authenticated"); } catch (e) { showResponse(false, e.message || String(e), "Dashboard save error"); setStatusIndicator(false, "Error"); } }); loadBtn.addEventListener("click", async () => { try { const data = await adminFetch(`/admin/license?k=${encodeURIComponent(row.key)}`, { method: "GET" }); fillUIFromRecord(data.key, data.record); showResponse(true, data, "Dashboard load"); setStatusIndicator(true, "Authenticated"); setActiveTab("license"); } catch (e) { showResponse(false, e.message || String(e), "Dashboard load error"); setStatusIndicator(false, "Error"); } }); group.appendChild(saveBtn); group.appendChild(loadBtn); actionsCell.appendChild(group); tr.appendChild(actionsCell); dashBody.appendChild(tr); } } async function loadDashboard(status = "active") { const s = String(status || "active").trim(); const data = await adminFetch(`/admin/licenses?status=${encodeURIComponent(s)}`, { method: "GET" }); renderDashboard(data.licenses || []); return data; } // -------------------- Events -------------------- $("saveTokenBtn").addEventListener("click", async () => { try { const t = tokenEl.value.trim(); if (!t) throw new Error("Token empty."); setToken(t); tokenEl.value = ""; setStatusIndicator(true, "Authenticated"); showResponse(true, "Token saved.", "Auth"); // Auto-refresh dashboard when token is saved try { const status = (dashStatusEl.value || "active").trim(); const d = await loadDashboard(status); showResponse(true, { ok: true, loaded: (d.licenses || []).length, status }, "Dashboard"); } catch {} } catch (e) { setStatusIndicator(false, "Not authenticated"); showResponse(false, e.message || String(e), "Auth error"); } }); $("clearTokenBtn").addEventListener("click", () => { clearToken(); setStatusIndicator(false, "Not authenticated"); showResponse(true, "Token cleared.", "Auth"); }); calcOriginEl.addEventListener("change", () => { const v = String(calcOriginEl.value || "").trim() || DEFAULT_CALC_ORIGIN; setCalcOrigin(v); calcOriginEl.value = v; updateEmbedBox(); showResponse(true, { calcOrigin: v }, "Origin updated"); }); genKeyBtn.addEventListener("click", () => { licenseKeyEl.value = generateKey(); updateEmbedBox(); }); licenseKeyEl.addEventListener("input", updateEmbedBox); addDomainBtn.addEventListener("click", () => addDomainRow("")); normalizeDomainsBtn.addEventListener("click", () => { const uniq = [...new Set(getDomainsFromUI().map(normalizeHostname).filter(Boolean))]; setDomainsUI(uniq); showResponse(true, { normalizedDomains: uniq }, "Domains normalized"); }); fetchBtn.addEventListener("click", async () => { try { const key = (licenseKeyEl.value || "").trim(); if (!key) throw new Error("Enter a license key to fetch."); const data = await adminFetch(`/admin/license?k=${encodeURIComponent(key)}`, { method: "GET" }); fillUIFromRecord(data.key, data.record); showResponse(true, data, "License fetch"); setStatusIndicator(true, "Authenticated"); } catch (e) { showResponse(false, e.message || String(e), "License fetch error"); setStatusIndicator(false, "Error"); } }); async function loadLeadStats(withList) { const key = (licenseKeyEl.value || "").trim(); if (!key) throw new Error("Enter a license key first."); const data = await adminFetch( `/admin/leads?k=${encodeURIComponent(key)}${withList ? "&list=1" : ""}`, { method: "GET" } ); const months = Object.entries(data.byMonth || {}).sort((a, b) => b[0].localeCompare(a[0])); const lines = [`Total: ${data.total}`]; if (months.length) { lines.push("", ...months.map(([m, n]) => ` ${m} ${n}`)); } else { lines.push("", " No leads captured yet."); } if (withList && Array.isArray(data.leads)) { lines.push("", "Most recent:"); for (const l of data.leads.slice(0, 25)) { // A failed delivery is the reason to look at this list at all, so flag it. const failed = l.delivery?.firm && l.delivery.firm !== "sent" ? " [NOT DELIVERED]" : ""; lines.push(` ${l.submittedAt} ${l.email} ${l.name || ""}${failed}`); } if (data.listTruncated) lines.push(` …and ${data.listTruncated} older not shown.`); } leadStatsEl.textContent = lines.join("\n"); leadStatsEl.style.whiteSpace = "pre"; return data; } refreshLeadsBtn.addEventListener("click", async () => { try { const data = await loadLeadStats(false); showResponse(true, { total: data.total, byMonth: data.byMonth }, "Lead counts"); setStatusIndicator(true, "Authenticated"); } catch (e) { leadStatsEl.textContent = "Could not load lead counts."; showResponse(false, e.message || String(e), "Lead counts error"); setStatusIndicator(false, "Error"); } }); listLeadsBtn.addEventListener("click", async () => { try { await loadLeadStats(true); setStatusIndicator(true, "Authenticated"); } catch (e) { leadStatsEl.textContent = "Could not load leads."; showResponse(false, e.message || String(e), "Lead list error"); setStatusIndicator(false, "Error"); } }); saveLicenseBtn.addEventListener("click", async () => { try { prettifyConfigJson(); const payload = getRecordFromUI(); if (!payload.record.allowedDomains.length) throw new Error("allowedDomains cannot be empty."); const data = await adminFetch(`/admin/license`, { method: "POST", body: JSON.stringify(payload) }); showResponse(true, data, "License save"); setStatusIndicator(true, "Authenticated"); } catch (e) { showResponse(false, e.message || String(e), "License save error"); setStatusIndicator(false, "Error"); } }); revokeBtn.addEventListener("click", async () => { try { const key = (licenseKeyEl.value || "").trim(); if (!key) throw new Error("Missing license key."); if (!confirm(`Revoke license ${key}? This sets status=inactive.`)) return; const data = await adminFetch(`/admin/revoke`, { method: "POST", body: JSON.stringify({ key }) }); statusEl.value = "inactive"; showResponse(true, data, "Revoke"); setStatusIndicator(true, "Authenticated"); } catch (e) { showResponse(false, e.message || String(e), "Revoke error"); setStatusIndicator(false, "Error"); } }); activateBtn.addEventListener("click", async () => { try { const key = (licenseKeyEl.value || "").trim(); if (!key) throw new Error("Missing license key."); const data = await adminFetch(`/admin/activate`, { method: "POST", body: JSON.stringify({ key }) }); statusEl.value = "active"; showResponse(true, data, "Activate"); setStatusIndicator(true, "Authenticated"); } catch (e) { showResponse(false, e.message || String(e), "Activate error"); setStatusIndicator(false, "Error"); } }); copyEmbedBtn.addEventListener("click", async () => { const text = (embedCodeEl.textContent || "").trim(); if (!text) { showResponse(false, "No embed code to copy.", "Embed"); return; } try { await navigator.clipboard.writeText(text); showResponse(true, "Embed code copied to clipboard.", "Embed"); return; } catch {} try { const ta = document.createElement("textarea"); ta.value = text; ta.setAttribute("readonly", ""); ta.style.position = "fixed"; ta.style.top = "-9999px"; ta.style.left = "-9999px"; document.body.appendChild(ta); ta.select(); ta.setSelectionRange(0, ta.value.length); const ok = document.execCommand("copy"); document.body.removeChild(ta); showResponse(ok, ok ? "Embed code copied to clipboard." : "Copy failed. Copy manually from the embed box.", "Embed"); } catch { showResponse(false, "Copy failed. Copy manually from the embed box.", "Embed"); } }); openCalcBtn.addEventListener("click", () => { const key = (licenseKeyEl.value || "").trim(); if (!key) { showResponse(false, "Generate or enter a key first.", "Open"); return; } const origin = getCalcOrigin().replace(/\/+$/,""); window.open(`${origin}/?k=${encodeURIComponent(key)}`, "_blank"); }); loadDefaultsBtn.addEventListener("click", () => { configJsonEl.value = JSON.stringify(DEFAULT_CONFIG, null, 2); showResponse(true, "Loaded defaults.", "Config"); loadLocationsFromConfig(); loadBrokerSettingsFromConfig(); renderQuickEditor(); }); prettifyJsonBtn.addEventListener("click", () => { try { prettifyConfigJson(); showResponse(true, "JSON formatted.", "Config"); } catch (e) { showResponse(false, e.message || String(e), "Config error"); } }); clearConfigBtn.addEventListener("click", () => { configJsonEl.value = ""; showResponse(true, "Config cleared.", "Config"); if (quickEditorEl) quickEditorEl.innerHTML = ""; setQuickStatus(""); setLocationsStatus(""); }); renderQuickBtn.addEventListener("click", () => renderQuickEditor()); loadLocationsBtn.addEventListener("click", () => loadLocationsFromConfig()); applyLocationsBtn.addEventListener("click", () => applyLocationsToConfig()); loadBrokerSettingsBtn.addEventListener("click", () => loadBrokerSettingsFromConfig()); applyBrokerSettingsBtn.addEventListener("click", () => applyBrokerSettingsToConfig()); loadDashboardBtn.addEventListener("click", async () => { try { const status = (dashStatusEl.value || "active").trim(); const data = await loadDashboard(status); showResponse(true, { ok: true, loaded: (data.licenses || []).length, status }, "Dashboard"); setStatusIndicator(true, "Authenticated"); } catch (e) { showResponse(false, e.message || String(e), "Dashboard error"); setStatusIndicator(false, "Error"); } }); exportCsvBtn.addEventListener("click", async () => { try { const status = (dashStatusEl.value || "active").trim(); const data = await adminFetch(`/admin/licenses?status=${encodeURIComponent(status)}`, { method: "GET" }); const licenses = data.licenses || []; const header = ["License Key","Status","Firm Name","Firm Phone","Contact Name","Contact Phone","Contact Email","Valid Through"]; const rows = [ header, ...licenses.map(r => [ r.key, r.status || "", r.firmName || "", r.firmPhone || "", r.contactName || "", r.contactPhone || "", r.contactEmail || "", r.validThrough || "" ]) ]; downloadCsv(`licenses-${status}-${new Date().toISOString().slice(0,10)}.csv`, rows); showResponse(true, `Exported ${licenses.length} rows to CSV (${status}).`, "Export"); setStatusIndicator(true, "Authenticated"); } catch (e) { showResponse(false, e.message || String(e), "Export error"); setStatusIndicator(false, "Error"); } }); // -------------------- Init -------------------- // Origin calcOriginEl.value = getCalcOrigin(); // Defaults setDomainsUI([]); statusEl.value = "active"; allowNoRefererEl.checked = false; leadsEnabledEl.checked = false; leadsNotifyEmailEl.value = ""; leadsFirmDisplayNameEl.value = ""; leadsReplyToOverrideEl.value = ""; leadsRetentionDaysEl.value = ""; leadsDisclaimerHtmlEl.value = ""; loadedRecord = null; loadedRecordKey = ""; // Default config loaded configJsonEl.value = JSON.stringify(DEFAULT_CONFIG, null, 2); loadLocationsFromConfig(); loadBrokerSettingsFromConfig(); renderQuickEditor(); // Embed initial updateEmbedBox(); // Auth status if (getToken().trim()) { setStatusIndicator(true, "Token present"); // Auto-load dashboard if token already present (async () => { try { const status = (dashStatusEl.value || "active").trim(); const data = await loadDashboard(status); showResponse(true, { ok: true, loaded: (data.licenses || []).length, status }, "Dashboard"); } catch (e) { showResponse(false, e.message || String(e), "Dashboard error"); setStatusIndicator(false, "Error"); } })(); } else { setStatusIndicator(false, "Not authenticated"); } })();