/* © 2026 K2B NETWORK SOLUTIONS LLC. All rights reserved. Proprietary and confidential. Unauthorized copying, distribution, or use of this file, via any medium, is strictly prohibited. Original work product. */ /* eslint-disable */ // JBREWS DMS — Integrations. // • IntegrationsHub (route "integrations", everyone): read-only API-integration // status from the server + the vendor-site launchers (ConnectedApps.jsx.txt). // • IntegrationsAdminPanel (Settings → Integrations, ADMIN ONLY): per-provider // configuration, write-only secrets (stored encrypted on the server, never // sent back to a browser), enable/disable, REAL connection test, last // successful test, last error, activity log. // A provider shows "Connected" only after the server's live test against that // provider succeeded. Stored credentials, the enable switch, or server mock // mode never count as connected. // The old e-sign messaging settings kept a SendGrid key in this browser — purge it. try { localStorage.removeItem("jbrews-msg-cfg"); } catch (e) {} const IntegrationsHub = ({ onClose, go }) => { const admin = !!(window.DMS && window.DMS.isAdmin && window.DMS.isAdmin()); return (
Two different things live here. API integrations are server-to-server connections (texting, email, RouteOne, CARFAX, vAuto, GM, and AI page reading for old reports) — an administrator configures them, and each one shows Connected only after a real test with the provider succeeds. Vendor sites below simply open the vendor's own website in a tab with your own login.
{window.IntegrationStatusStrip ? :
Integration status unavailable — ConnectedApps.jsx.txt is not loaded.
} {window.ConnectedAppsPanel ? :
Vendor site launchers unavailable — ConnectedApps.jsx.txt is not loaded.
}
go && go("routeone")}>⇪ RouteOne Module go && go("uploadtool")}>⇧ Import Legacy Data {admin && go && go("settings")}>Settings ▸ Integrations}
ESC-Close
); }; /* ─── Admin: one provider card ─────────────────────────────────────────── */ const intInp = { width: "100%", boxSizing: "border-box", padding: "6px 8px", border: "1px solid #C8CCD2", borderRadius: 4, fontSize: 12.5 }; // What a pasted key will look like after the SERVER cleans it (quotes, "Bearer ", header names, spaces and line breaks). // Used only for the hint under the key box — the key itself is never shown, stored or logged here. const intKeyHint = (raw) => { const t = String(raw || ""); if (!t.trim()) return null; const cleaned = t.replace(/^\s*(x-api-key|authorization)\s*:\s*/i, "").replace(/\s+/g, "").replace(/^["'`]+|["'`]+$/g, "").replace(/^bearer/i, "").replace(/^["'`]+|["'`]+$/g, ""); const notes = []; if (cleaned !== t.trim()) notes.push({ bad: false, t: "Spaces, line breaks, quotes and the word \"Bearer\" are removed for you." }); if (!/^sk-ant-/.test(cleaned)) notes.push({ bad: true, t: "This does not look like an Anthropic key - they start with sk-ant-. Copy it again from console.anthropic.com > API keys." }); else if (cleaned.length < 40) notes.push({ bad: true, t: "This key looks cut off (too short). Copy the whole key again." }); return notes; }; const IntProviderCard = ({ p, onChanged, onOpenMessaging, onShowLog, embedded, onOpenOldReports }) => { const api = window.k2bIntApi; const isAi = p.id === "anthropic"; const [cfg, setCfg] = React.useState(p.config || {}); const [sec, setSec] = React.useState({}); // typed secrets — never pre-filled const [busy, setBusy] = React.useState(""); const [msg, setMsg] = React.useState(null); const [showMap, setShowMap] = React.useState(false); const [confirmRemove, setConfirmRemove] = React.useState(null); // secret field name awaiting "Remove stored key" confirmation const aiv = isAi && window.useK2BAiStatus ? window.useK2BAiStatus() : null; React.useEffect(() => { setCfg(p.config || {}); }, [p.updatedAt]); const ftype = (f) => f.type || "text"; const same = (f, a, b) => ftype(f) === "checkbox" ? (a === true || a === "true") === (b === true || b === "true") : String(a == null ? "" : a) === String(b == null ? "" : b); const dirty = Object.keys(sec).some(k => sec[k]) || p.fields.some(f => !f.secret && ftype(f) !== "readonly" && !same(f, cfg[f.f], (p.config || {})[f.f])); if (window.useK2BUnsaved) window.useK2BUnsaved("integration-" + p.id, { label: "Integrations — " + p.label.split(" —")[0], isDirty: () => dirty, discard: () => { setSec({}); setCfg(p.config || {}); } }); // RC35 (brd) const fmt = (d) => d ? new Date(d).toLocaleString([], { month: "short", day: "numeric", year: "numeric", hour: "numeric", minute: "2-digit" }) : "never"; const shortName = p.label.split(" —")[0]; const refreshAi = () => { if (isAi && window.K2BAiStatus) window.K2BAiStatus.refresh(); }; const changed = (np) => { onChanged && onChanged(np); refreshAi(); }; const save = async () => { setBusy("save"); setMsg(null); const secrets = {}; Object.keys(sec).forEach(k => { if (sec[k] && sec[k].trim()) secrets[k] = sec[k].trim(); }); const config = {}; p.fields.forEach(f => { if (!f.secret && ftype(f) !== "readonly" && f.f in cfg) config[f.f] = cfg[f.f]; }); const r = await api("/admin/" + p.id, "POST", { config, secrets }); setBusy(""); if (!r.ok) { setMsg({ bad: true, t: r.error || "Save failed" }); return; } setSec({}); const miss = r.data.missing || []; const np = r.data.provider || p; const hint = np.secretHints && np.secretHints.apiKey ? " Key stored encrypted (" + np.secretHints.apiKey + ")." : ""; setMsg({ bad: false, t: miss.length ? "Saved. Still missing: " + miss.join(", ") + "." : isAi ? "Saved." + hint + " Next: press Test connection - AI reading is not usable until a test succeeds." : "Saved (secrets encrypted on the server). Run Test connection — it is not connected until a test succeeds." }); changed(np); }; const toggle = async (on, quiet) => { setBusy("enable"); const r = await api("/admin/" + p.id + "/enable", "POST", { enabled: on }); setBusy(""); if (!r.ok) { setMsg({ bad: true, t: r.error }); if (r.data && r.data.provider) changed(r.data.provider); return null; } changed(r.data.provider); return r.data.provider; }; const test = async () => { setBusy("test"); setMsg(null); const r = await api("/admin/" + p.id + "/test", "POST", {}); setBusy(""); if (!r.ok) { setMsg({ bad: true, t: r.error || "Test failed" }); return; } const d = r.data; let text = (d.connected ? "Connected — " : "Not connected — ") + d.detail; if (d.stale) text += " (The settings changed while the test ran - press Test connection again.)"; let np = d.provider; // AI Import has no other job than reading pages: a passing test with consent ticked switches it on, so "Connected" really means "usable". if (isAi && d.connected && np && !np.enabled && (np.config || {}).consent) { const after = await toggle(true, true); if (after) { np = after; text += " AI reading is now ON."; } else text += " It is still switched off - see the message above."; } setMsg({ bad: !d.connected, t: text }); window.k2bToast && window.k2bToast(shortName + (d.connected ? ": connection test passed." : ": not connected — see the card."), d.connected ? "ok" : "warn", 3600); changed(np); }; const removeSecret = async (field) => { setConfirmRemove(null); setBusy("remove"); setMsg(null); const r = await api("/admin/" + p.id + "/clear-secret", "POST", { field }); setBusy(""); if (!r.ok) { setMsg({ bad: true, t: r.error || "Could not remove the stored key" }); return; } setSec(s => { const n = { ...s }; delete n[field]; return n; }); setMsg({ bad: false, t: "The stored key was removed and " + shortName + " was switched off. Paste a new key and press Save to use it again." }); changed(r.data.provider); }; const lab = { fontSize: 11.5, fontWeight: 600, color: "#555" }; const helpSt = { fontSize: 11, color: "#777", lineHeight: 1.45, fontWeight: 400 }; const renderField = (f) => { const t = ftype(f); const help = f.help ? {f.help} : null; if (t === "readonly") { const v = cfg[f.f]; if (!v) return null; return
{f.label}: {/^\d{4}-\d{2}-\d{2}T/.test(String(v)) ? fmt(v) : String(v)}
; } if (t === "checkbox") { return ( ); } if (f.secret) { const stored = !!p.secretsSet[f.f]; const hintTxt = (p.secretHints || {})[f.f]; const kh = isAi && f.f === "apiKey" ? intKeyHint(sec[f.f]) : null; return (
{kh && kh.map((n, i) => {n.bad ? "⚠ " : "✓ "}{n.t})} {help} {f.removable && stored && (
Switches {shortName} off until a new key is saved.
)}
); } if (t === "select") { const cur = cfg[f.f] != null && cfg[f.f] !== "" ? cfg[f.f] : (f.default || ""); return ( ); } if (t === "number") { return ( ); } return ( ); }; return (
{p.label}
{window.K2BIntChip && }
{embedded ? null : <>Method: {p.auth}.
}{p.docs}
{p.fields.map(renderField)}
{dirty && You have unsaved changes - press Save first.} {p.id === "twilio" && onOpenMessaging && } {p.id === "email" && onOpenMessaging && } {isAi && onOpenOldReports && } {!embedded && } {!embedded && }
{msg &&
{msg.t}
} {isAi && aiv && aiv.limits && (
Used today: {aiv.limits.pagesToday} of {aiv.limits.maxPagesPerDay} pages {aiv.pricing ? · about ${aiv.pricing.estPerPageUsd.toFixed(2)} a page on {aiv.modelName || aiv.model} (${aiv.pricing.inputPerMTok} in / ${aiv.pricing.outputPerMTok} out per million tokens) : null}
)}
Last test{fmt(p.lastTestAt)} Last successful test / sync{fmt(p.lastOkAt)} {p.status === "error" || !p.lastError ? "Last error" : "Last test note"}{p.lastError || "—"} Settings changed{p.updatedAt ? fmt(p.updatedAt) + (p.updatedBy ? " by " + p.updatedBy : "") : "—"}
{p.enabled && p.status !== "connected" && (
Enabled, but nothing is sent or synced through {shortName} until a connection test succeeds.
)} {showMap && (
Prepared data mapping (activates with credentials + provider approval)
{(p.mapping || []).map(([a, b], i) => )}
{a}→{b}
)}
{confirmRemove && ( setConfirmRemove(null)} buttons={<> setConfirmRemove(null)}>Cancel }>
The stored key is deleted from the server and {shortName} is switched off. Nothing that was already imported changes. To use it again, paste a key and press Save, then Test connection.
)}
); }; /* ─── AI Import settings (window opened from Upload & Import > Old reports; same card as Settings > Integrations) ── */ const AiImportSettingsPanel = ({ onClose }) => { const v = window.useK2BAiStatus ? window.useK2BAiStatus() : null; const [prov, setProv] = React.useState(null); const [err, setErr] = React.useState(""); const [loading, setLoading] = React.useState(false); const admin = !!(v && v.admin); const load = React.useCallback(async () => { setLoading(true); const r = await window.k2bIntApi("/admin"); setLoading(false); if (r.ok) { setProv(((r.data && r.data.providers) || []).find(x => x.id === "anthropic") || null); setErr(r.data && (r.data.providers || []).some(x => x.id === "anthropic") ? "" : "This server does not have AI Import installed yet (no \"anthropic\" provider). Run the AI Import server update."); } else setErr(r.status === 403 ? "Administrator access is required." : (r.error || "Server unreachable")); }, []); React.useEffect(() => { if (admin) load(); }, [admin]); const lim = v && v.limits; return (
{!admin && window.K2BAiChip && } {v ? v.headline : ""}
{v && (admin ? v.step : v.next) &&
Next: {admin ? v.step : v.next}
} {!admin && lim &&
Pages read today: {lim.pagesToday} of {lim.maxPagesPerDay}{v.modelName ? " · model: " + v.modelName : ""}.
} {!admin && v && !v.loading && (
Only an administrator can change the key, the model or the consent. Ask an administrator to open Upload & Import > Old reports > AI Import settings (or Settings > Integrations (API) > AI Import).
)} {admin && err &&
{err}
} {admin && loading && !prov &&
Loading the settings…
} {admin && prov && setProv(np)} />}
); }; /* ─── Admin: activity log viewer ───────────────────────────────────────── */ const IntLogViewer = ({ provider, setProvider, providers, tick }) => { const [rows, setRows] = React.useState(null); const [err, setErr] = React.useState(""); React.useEffect(() => { let alive = true; (async () => { const r = await window.k2bIntApi("/admin/log?limit=200" + (provider ? "&provider=" + encodeURIComponent(provider) : "")); if (!alive) return; if (r.ok) { setRows(r.data.log || []); setErr(""); } else setErr(r.error); })(); return () => { alive = false; }; }, [provider, tick]); return (
Sync & activity log Failures are logged here — no pop-ups on other workstations.
{err &&
{err}
} {rows && !rows.length &&
No activity yet.
} {rows && rows.length > 0 && ( {rows.map(r => ( ))}
WhenProviderActionResultDetail
{new Date(r.ts).toLocaleString([], { month: "short", day: "numeric", hour: "numeric", minute: "2-digit", second: "2-digit" })} {r.provider}{r.action} {r.ok === false ? "fail" : r.ok ? "ok" : "—"} {r.detail}
)}
); }; /* ─── Admin panel (Settings → Integrations) ────────────────────────────── */ const IntegrationsAdminPanel = ({ go, onOpenMessaging }) => { const [data, setData] = React.useState(null); const [err, setErr] = React.useState(""); const [logProv, setLogProv] = React.useState(""); const [logTick, setLogTick] = React.useState(0); const load = React.useCallback(async () => { const r = await window.k2bIntApi("/admin"); if (r.ok) { setData(r.data); setErr(""); } else setErr(r.status === 403 ? "Administrator access is required." : (r.error || "Server unreachable")); }, []); React.useEffect(() => { load(); }, [load]); if (!window.k2bIntApi) return
ConnectedApps.jsx.txt (integration client) is not loaded.
; const onChanged = (np) => { setData(d => d ? { ...d, providers: d.providers.map(x => x.id === np.id ? np : x) } : d); setLogTick(t => t + 1); }; const provs = (data && data.providers) || []; const order = window.K2B_INT_ORDER || []; const sorted = provs.slice().sort((a, b) => order.indexOf(a.id) - order.indexOf(b.id)); const count = (f) => provs.filter(f).length; const connected = provs.filter(p => p.status === "connected"); return (
Integrations (API)
Administrator only. Credentials are stored encrypted on the DMS server and are never sent back to any browser — a stored secret shows as stored ✓. A provider shows Connected only after Test connection reaches the provider and succeeds. RouteOne, CARFAX, vAuto and GM require partner onboarding/approval from the vendor before any live test is possible; until then the best status is Ready — waiting on provider. AI Import (Claude) reads scanned old reports for Upload & Import › Old reports: paste the Anthropic key, tick the consent box, press Save, then Test connection — it turns itself on when the test passes.
{err &&
{err}
} {data && (
Genuinely connected: {connected.length ? connected.map(p => p.label.split(" —")[0]).join(", ") : "none"} Ready — credentials stored, waiting on provider/test: {count(p => p.status === "ready")} Error: {count(p => p.status === "error")} Mock: {count(p => p.status === "mock")} Not configured: {count(p => p.status === "not_configured")}
)} {data && data.mock && (
The server is in mock mode (INTEGRATIONS_MOCK=1 in the server .env). The existing RouteOne / CARFAX / vAuto / e-sign routes return simulated results; those providers test as Mock mode — not connected. Remove the flag on the server when real partner credentials are in place.
)} {sorted.map(p => { try { localStorage.setItem("jbrews-upload-mode", "oldreports"); } catch (e) {} go("uploadtool"); } : null} onShowLog={(id) => { setLogProv(id); setLogTick(t => t + 1); try { document.getElementById("int-log").scrollIntoView({ behavior: "smooth" }); } catch (e) {} }} />)} {data && }
); }; Object.assign(window, { IntegrationsHub, IntegrationsAdminPanel, AiImportSettingsPanel, IntProviderCard });