/* eslint-disable */ /* © 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. */ // K2B DMS — RC37 (lane tpl37): "Set up your dealership" — the FIRST-RUN setup of a brand-new DMS. // // window.K2BFirstRunGate wraps the sign-in screen (app.jsx). It asks the server once (GET /api/setup/status): // • the server says the setup is OPEN (its database has no store and no login yet: a new dealer's server) -> this screen; // • anything else (a dealership that is set up, an older server, no answer) -> the normal sign-in, exactly as before. // The screen: the setup code (the installer has it), the dealership (name, legal name, address, phone, dealer license #, logo, // two colors), the store type + the chart of accounts (GM dealer chart / starter used-car + collision / import later), the tax // rates, the first RO number, and YOU (the first admin login = the owner: login, name, password, cell phone for sign-in codes; // a text-message code when the server already has its text-message service). One button saves it all in ONE step on the // server (POST /api/setup) — then "Sign in". Works on a laptop and on a phone (one column, nothing wider than the screen). // Nothing typed here is kept in the browser. (function () { const C = { navy: "#0e1430", gold: "#ffd24a", line: "#c8cfdb", bg: "#f4f6fa", ink: "#16202e", soft: "#5a6577", red: "#a02234", redBg: "#fbe9ec", green: "#1b5e20", greenBg: "#e8f3ea" }; const api = async (method, p, body) => { const h = Object.assign({ "Content-Type": "application/json" }, (window.K2BStore && window.K2BStore.stamp) ? window.K2BStore.stamp({}) : {}); delete h["X-K2B-Store"]; try { const r = await fetch(location.origin + "/api/setup" + p, { method, headers: h, body: body === undefined ? undefined : JSON.stringify(body), cache: "no-store" }); let j = null; try { j = await r.json(); } catch (e) { j = null; } return { status: r.status, body: j || {} }; } catch (e) { return { status: 0, body: { ok: false, error: "The server did not answer. Check the connection and try again." } }; } }; const fileB64 = (file) => new Promise((res, rej) => { const fr = new FileReader(); fr.onload = () => res(String(fr.result || "")); fr.onerror = () => rej(new Error("read")); fr.readAsDataURL(file); }); const Field = ({ label, hint, err, children, id }) => ( ); const inp = { width: "100%", boxSizing: "border-box", padding: "10px 11px", border: "1px solid " + C.line, borderRadius: 4, fontSize: 15, fontFamily: "var(--font-ui, system-ui)", background: "#fff", color: C.ink, minHeight: 42 }; const Sect = ({ n, title, children }) => (
{n}{title}
{children}
); const row2 = { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(140px, 1fr))", columnGap: 12 }; const KIND_LABEL = { "franchise": "New-car franchise (GM dealer)", "used": "Used-car lot", "collision": "Collision / body shop", "used+collision": "Used-car lot + collision" }; const CHART_FOR = { "franchise": "gm", "used": "starter", "collision": "starter", "used+collision": "starter" }; function FirstRunSetup({ status, onDone }) { const [f, setF] = React.useState({ setupCode: "", dba: "", legalName: "", addr1: "", city: "", state: "", zip: "", phone: "", licenseNo: "", web: "", primary: "#0e1430", accent: "#ffd24a", kind: "used+collision", chart: "starter", chartTouched: false, servicePct: "", partsPct: "", taxOk: false, roStart: "1001", adminId: "", adminName: "", pw: "", pw2: "", cell: "", phoneCode: "", }); const [logo, setLogo] = React.useState(null); // { mime, dataBase64, name, preview } const [errs, setErrs] = React.useState({}); const [msg, setMsg] = React.useState(""); const [busy, setBusy] = React.useState(false); const [sms, setSms] = React.useState({ sent: false, note: "" }); const set = (k) => (e) => { const v = e && e.target ? (e.target.type === "checkbox" ? e.target.checked : e.target.value) : e; setF((o) => Object.assign({}, o, { [k]: v })); }; const setKind = (e) => { const k = e.target.value; setF((o) => Object.assign({}, o, { kind: k, chart: o.chartTouched ? o.chart : CHART_FOR[k] })); }; const setChart = (c) => setF((o) => Object.assign({}, o, { chart: c, chartTouched: true })); const pickLogo = async (e) => { const file = e.target.files && e.target.files[0]; if (!file) { setLogo(null); return; } if (file.size > 1048576) { setErrs((o) => Object.assign({}, o, { logo: "That logo is larger than 1 MB. Pick a smaller picture (PNG, JPG or SVG)." })); setLogo(null); return; } try { const d = await fileB64(file); setLogo({ mime: file.type || "image/png", dataBase64: d, name: file.name, preview: d }); setErrs((o) => Object.assign({}, o, { logo: "" })); } catch (x) { setErrs((o) => Object.assign({}, o, { logo: "That file could not be read." })); } }; const fieldMap = { "store.dba": "dba", "store.legalName": "legalName", "store.addr1": "addr1", "store.street": "addr1", "store.city": "city", "store.state": "state", "store.zip": "zip", "store.phone": "phone", "store.licenseNo": "licenseNo", "store.licenseLine": "licenseNo", "store.colors.primary": "primary", "store.colors.accent": "accent", "store.roStart": "roStart", "store.tax": "servicePct", "store.web": "web", "chart": "chart", "admin.id": "adminId", "admin.name": "adminName", "admin.password": "pw", "admin.phone": "cell", "logo": "logo", "store.kind": "kind" }; const sendCode = async () => { setSms({ sent: false, note: "" }); const r = await api("POST", "/phone-code", { setupCode: f.setupCode, phone: f.cell }); if (r.status === 200) setSms({ sent: true, note: "A code was texted to " + (r.body.sentTo || "your phone") + ". Type it below." }); else setSms({ sent: false, note: r.body.error || "The code could not be sent." }); }; const submit = async () => { if (busy) return; const e = {}; if (!f.setupCode.trim()) e.setupCode = "Type the setup code from the person who installed the DMS."; if (f.pw !== f.pw2) e.pw2 = "The two passwords are not the same."; if (!f.taxOk) e.taxOk = "Tick the box once the two tax rates are right."; setErrs(e); setMsg(""); if (Object.keys(e).length) { setMsg("Some details need a look (marked in red)."); return; } setBusy(true); const body = { setupCode: f.setupCode, chart: f.chart, store: { kind: f.kind, roStart: f.roStart, tax: { servicePct: f.servicePct, partsPct: f.partsPct, confirmed: !!f.taxOk }, branding: { dba: f.dba, legalName: f.legalName, addr1: f.addr1, street: f.addr1, city: f.city, state: f.state, zip: f.zip, phone: f.phone, licenseNo: f.licenseNo, web: f.web, colors: { primary: f.primary, accent: f.accent } } }, logo: logo ? { mime: logo.mime, dataBase64: logo.dataBase64 } : null, admin: { id: f.adminId, name: f.adminName, password: f.pw, phone: f.cell, phoneCode: f.phoneCode }, }; const r = await api("POST", "", body); setBusy(false); if (r.status === 200 && r.body.ok) { onDone(r.body); return; } if (r.status === 426) { setMsg(r.body.error || "This page is older than the server. Reload the page (Ctrl+F5)."); return; } const fe = {}; for (const [k, v] of Object.entries(r.body.fields || {})) fe[fieldMap[k] || k] = v; if (r.body.code === "bad_setup_code" || r.body.code === "no_setup_code") fe.setupCode = r.body.error; if (r.body.code === "bad_phone_code") fe.phoneCode = r.body.error; if (r.body.code === "tax_not_confirmed") fe.taxOk = r.body.error; setErrs(fe); setMsg(r.status === 410 ? (r.body.error || "This DMS is already set up.") : (r.body.error || "The setup was not saved.")); if (r.status === 410) setTimeout(() => onDone(null), 1500); }; const E = (k) => errs[k] || ""; const charts = (status && status.charts) || []; const kinds = (status && status.kinds) || Object.keys(KIND_LABEL); return (
K2B { e.target.style.display = "none"; }} />
Set up your dealership
K2B DMS · first-run setup (one time only)
This DMS is brand new: it has no store and no login yet. Fill this in once — it makes your store, its chart of accounts and your own login (you become the owner). Everything can be changed later in Owner panel > Stores.
set("state")(e.target.value.toUpperCase())} autoComplete="off" />
{logo ? : null}{f.dba || "Your store name"}
Chart of accounts
{charts.map((c) => ( ))} {E("chart") &&
{E("chart")}
}
Every balance starts at 0. Your accountant can add, rename or turn off accounts later (Accounting > Chart of accounts), and opening balances come in through Upload tool > templates.
{E("taxOk") &&
{E("taxOk")}
}
Sign-in codes, after the text-message service is set up on the server."} err={E("cell")}> {status && status.sms && (
{sms.note &&
{sms.note}
} {sms.sent && }
)}
{msg &&
{msg}
}
© 2026 K2B NETWORK SOLUTIONS LLC · K2B DMS
); } function FirstRunDone({ result, onSignIn }) { return (
✓ {result.store && result.store.dba} is set up
Store number {result.store && result.store.id} · chart of accounts: {result.chart && result.chart.accounts} accounts
Your login: {result.admin && result.admin.id} ({result.admin && result.admin.name}) — you are the owner
Sign-in phone: {result.phone && result.phone.masked} {result.phone && result.phone.confirmed ? "(confirmed)" : "(to confirm later)"}
    {(result.next || []).map((t, i) =>
  1. {t}
  2. )}
{Array.isArray(result.backupCodes) && result.backupCodes.length > 0 && (
Your 10 backup codes — write them down now
Each works once, when your phone is not with you. They are shown only now. Keep them somewhere safe (not on this computer).
{result.backupCodes.map((c) =>
{c}
)}
)}
); } /** wraps the sign-in screen: shows the setup ONLY when the server says it is open */ function K2BFirstRunGate({ children }) { const [st, setSt] = React.useState({ phase: "check", status: null, result: null }); React.useEffect(() => { let gone = false; (async () => { const r = await api("GET", "/status"); if (gone) return; if (r.status === 200 && r.body && r.body.open === true) setSt({ phase: "form", status: r.body, result: null }); else setSt({ phase: "signin", status: null, result: null }); })(); return () => { gone = true; }; }, []); if (st.phase === "form") return setSt(res ? { phase: "done", status: st.status, result: res } : { phase: "signin", status: null, result: null })} />; if (st.phase === "done") return setSt({ phase: "signin", status: null, result: null })} />; return children; // "check" shows the sign-in at once (a set-up dealership never waits for this question) } window.K2BFirstRunGate = K2BFirstRunGate; })();