);
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 (
{ 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.
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 && (