/* 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. */ // JBREWS DMS — Sign-On screen. // Sign-on is validated by the SERVER (DMS.login / DMS.loginMfa). Nothing on // this workstation holds a password or an MFA code. If the server cannot be // reached the user may choose "Work Offline Anyway", which opens a RESTRICTED // local session (role "Offline": no admin, no deal creation, nothing shared). const JB_AUTH_KEY = "jbrews-auth-config-v1"; // Workstation-only preferences (no credentials): the Dealer Id pre-filled on // this screen and an optional workstation name shown in presence. // RC35 (brd): "dealer" is no longer asked - the server picks the store; the stored value only names an RC34 local copy (moved once) const JB_DEFAULT_AUTH = { dealer: "", station: "" }; window.jbLoadAuth = function() { try { const raw = localStorage.getItem(JB_AUTH_KEY); if (raw) { const p = JSON.parse(raw) || {}; // Only the workstation preferences are honoured; any credential fields // written by an older build are ignored and scrubbed on the next save. return { ...JB_DEFAULT_AUTH, dealer: p.dealer || JB_DEFAULT_AUTH.dealer, station: p.station || "" }; } } catch (e) {} return { ...JB_DEFAULT_AUTH }; }; window.jbSaveAuth = function(a) { const safe = { dealer: (a && a.dealer) || JB_DEFAULT_AUTH.dealer, station: (a && a.station) || "" }; try { localStorage.setItem(JB_AUTH_KEY, JSON.stringify(safe)); } catch (e) {} }; // Scrub any credential fields an older build left in this key. try { const raw = localStorage.getItem(JB_AUTH_KEY); if (raw && /"(password|mfaCode|user)"/.test(raw)) window.jbSaveAuth(window.jbLoadAuth()); } catch (e) {} // Station-level brute-force brake: after JB_SIGNON_MAX_FAILS refused sign-ins // this workstation waits 60 s (the server has its own limits; this one just // slows a keyboard). Nothing about the user or password is stored. const JB_SIGNON_COOL_KEY = "jb_signon_cooldown_until"; const JB_SIGNON_FAILS_KEY = "jb_signon_fails"; const JB_SIGNON_MAX_FAILS = 5; const JB_SIGNON_COOL_MS = 60 * 1000; const jbSignOnCoolUntil = () => { try { return Number(localStorage.getItem(JB_SIGNON_COOL_KEY)) || 0; } catch (e) { return 0; } }; const jbSignOnFails = () => { try { return Number(localStorage.getItem(JB_SIGNON_FAILS_KEY)) || 0; } catch (e) { return 0; } }; const jbSignOnAudit = (action, detail) => { try { if (window.DMS && window.DMS.logAudit) window.DMS.logAudit(action, detail || ""); } catch (e) {} }; const SignOn = ({ onLogin }) => { const [auth] = React.useState(() => window.jbLoadAuth()); const [step, setStep] = React.useState("creds"); // "creds" | "mfa" const dealer = auth.dealer; // RC35 (brd): kept as stored (never shown, never changed) const storeNow = () => (window.K2BStore && (window.K2BStore.id() || window.K2BStore.pageId())) || ""; // RC35 (brd): before sign-in the card shows the LAST store used on this PC (its cached branding) - never a list of stores - else K2B const SB = window.K2BStore && window.K2BStore.known() ? window.K2BStore.branding() : null; const soPrimary = SB ? SB.colors.primary : "#0E1430", soAccent = SB ? SB.colors.accent : "#FFD24A"; const soLight = (() => { const h = String(soPrimary).replace("#", ""); const c = [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16) || 0).map((v) => Math.round(v + (255 - v) * 0.09)); return "#" + c.map((v) => v.toString(16).padStart(2, "0")).join(""); })(); const soLogo = SB ? (window.K2BStore.logoUrl("large") || window.K2BStore.monogramUrl(200)) : "../../assets/k2b-logo-mark.svg"; const [user, setUser] = React.useState(""); const [pass, setPass] = React.useState(""); const [mfa, setMfa] = React.useState(""); const [mfaToken, setMfaToken] = React.useState(null); // RC22 (own): the owner's sign-in codes — which ways this login has, the chosen one, the text-message state, set-up at sign-in const [mfaInfo, setMfaInfo] = React.useState(null); // { methods, phone, enrollTotp, message, smsReady } const [mfaMethod, setMfaMethod] = React.useState("totp"); const [smsNote, setSmsNote] = React.useState(""); const [resendAt, setResendAt] = React.useState(0); const [enroll, setEnroll] = React.useState(null); // { qrPng, secret } — the authenticator being set up at sign-in const [showPw, setShowPw] = React.useState(false); const [err, setErr] = React.useState(""); const [busy, setBusy] = React.useState(false); const [modal, setModal] = React.useState(null); const [caps, setCaps] = React.useState(false); // Cooldown after repeated refused sign-ins (per station, localStorage). const [coolUntil, setCoolUntil] = React.useState(jbSignOnCoolUntil); const [now, setNow] = React.useState(Date.now()); React.useEffect(() => { if (!coolUntil) return; const t = setInterval(() => setNow(Date.now()), 500); return () => clearInterval(t); }, [coolUntil]); React.useEffect(() => { if (!resendAt) return; const t = setInterval(() => setNow(Date.now()), 500); return () => clearInterval(t); }, [resendAt]); // RC22 (own): the "Send again in Ns" countdown const coolLeft = coolUntil > now ? Math.ceil((coolUntil - now) / 1000) : 0; React.useEffect(() => { if (coolUntil && !coolLeft) { setCoolUntil(0); setErr(""); try { localStorage.removeItem(JB_SIGNON_COOL_KEY); localStorage.removeItem(JB_SIGNON_FAILS_KEY); } catch (e) {} } }, [coolLeft, coolUntil]); const noteFailure = (who, why) => { jbSignOnAudit("Failed Sign-On", String(who || "").trim() + (why ? " · " + why : "")); const n = jbSignOnFails() + 1; try { localStorage.setItem(JB_SIGNON_FAILS_KEY, String(n)); } catch (e) {} if (n >= JB_SIGNON_MAX_FAILS) { const until = Date.now() + JB_SIGNON_COOL_MS; try { localStorage.setItem(JB_SIGNON_COOL_KEY, String(until)); } catch (e) {} setCoolUntil(until); } }; const noteSuccess = (who, how) => { try { localStorage.removeItem(JB_SIGNON_FAILS_KEY); localStorage.removeItem(JB_SIGNON_COOL_KEY); } catch (e) {} setCoolUntil(0); jbSignOnAudit("Sign On", String(who || "").trim() + (how ? " · " + how : "")); }; const capsKey = (e) => { try { setCaps(!!(e.getModifierState && e.getModifierState("CapsLock"))); } catch (x) {} }; // Server-unreachable state: { detail } → show the explicit yellow choice // (Work Offline Anyway / Retry). There is NO automatic silent fallback. const [offlineOffer, setOfflineOffer] = React.useState(null); // Session-expired notice (stored token was already dead at page load). const [connNote, setConnNote] = React.useState(() => (window.DMS && window.DMS.connState && window.DMS.connState() === "auth-expired") ? "Your session expired — please sign in again." : ""); React.useEffect(() => { const onConn = () => { const cs = window.DMS && window.DMS.connState ? window.DMS.connState() : ""; setConnNote(cs === "auth-expired" ? "Your session expired — please sign in again." : ""); }; window.addEventListener("k2b-conn", onConn); return () => window.removeEventListener("k2b-conn", onConn); }, []); const submitCreds = async () => { if (busy) return; if (jbSignOnCoolUntil() > Date.now()) { setCoolUntil(jbSignOnCoolUntil()); setErr("Too many failed sign-ins on this station — wait for the countdown."); return; } setErr(""); setBusy(true); setOfflineOffer(null); try { // ALWAYS try the server first. if (window.DMS && typeof window.DMS.login === "function") { const r = await window.DMS.login(user, pass); if (r && r.ok && r.mfaRequired) { setMfaToken(r.mfaToken); setStep("mfa"); setMfa(""); setSmsNote(""); setEnroll(null); const ms = Array.isArray(r.methods) ? r.methods : ["totp"]; setMfaInfo({ methods: ms, phone: r.phone || null, enrollTotp: !!r.enrollTotp, message: r.message || "", smsReady: r.smsReady !== false }); const first = ms.indexOf("sms") !== -1 ? "sms" : ms.indexOf("totp") !== -1 ? "totp" : (ms[0] || "totp"); setMfaMethod(r.enrollTotp ? "totp" : first); if (r.enrollTotp && window.DMS && window.DMS.mfaEnrollAtSignIn) { const e = await window.DMS.mfaEnrollAtSignIn(r.mfaToken); if (e && e.ok) setEnroll({ qrPng: e.qrPng, secret: e.secret }); else setErr((e && e.msg) || "Could not start the authenticator set-up."); } else if (first === "sms" && !r.enrollTotp) { await sendSms(r.mfaToken); } else if (r.message) { setErr(r.message); } return; } if (r && r.ok) { setConnNote(""); if (window.DMS && typeof window.DMS.setOfflineByChoice === "function") { try { window.DMS.setOfflineByChoice(false); } catch (e) {} } noteSuccess(r.user || user, "server"); onLogin({ dealer: storeNow(), user: r.user || user, role: r.role || "" }); return; } if (r && r.serverReached) { // The server ANSWERED and said no. Show its exact words on screen. // Never quietly fall back to local sign-on — that is how stations // end up working on trapped, unshared data. noteFailure(user, "refused by server"); setErr(r.msg || "Sign-on refused by the server. Check your credentials."); return; } // Server genuinely unreachable (network error / no server) → offer an // EXPLICIT choice instead of silently going local. setOfflineOffer({ detail: (r && r.msg) || "no response" }); return; } // DMS core not loaded at all — same explicit choice. setOfflineOffer({ detail: "DMS core not loaded on this page" }); } catch (e) { setErr(e.message || "Sign-on failed."); } finally { setBusy(false); } }; // Explicit offline session — ONLY reachable from the yellow "Server // unreachable" notice via the [Work Offline Anyway] button. // Audit C-02c / C-03b: this NEVER grants Admin and validates no password. // The typed User Name becomes a restricted local session (role "Offline"): // work on this PC only, no deal creation, nothing shared. The core is told // the session is offline-by-choice so a stale token can't quietly reconnect // and push as a previous identity. const workOffline = () => { setErr(""); setOfflineOffer(null); const id = String(user || "").trim(); if (!id) { setErr("Enter your User Name to work offline on this workstation."); return; } if (window.DMS && typeof window.DMS.setOfflineByChoice === "function") { try { window.DMS.setOfflineByChoice(true); } catch (e) {} } onLogin({ dealer: storeNow(), user: id, role: "Offline", offline: true }); jbSignOnAudit("Sign On", id + " · offline session (no password verified)"); }; // RC22 (own): a sign-in code by text message (Twilio Verify through the server; the code never touches this station's storage) const sendSms = async (tok) => { const t = tok || mfaToken; if (!t || !window.DMS || !window.DMS.mfaSendSms) return; setErr(""); setSmsNote("Sending a code…"); const r = await window.DMS.mfaSendSms(t); if (r && r.ok) { setSmsNote("Code sent to " + (r.sentTo || "your phone") + ". It can take a minute."); setResendAt(Date.now() + 1000 * (r.resendInSec || 30)); } else { setSmsNote(""); setErr((r && r.msg) || "The code could not be sent."); if (r && r.retryInSec) setResendAt(Date.now() + 1000 * r.retryInSec); } }; const submitMfa = async () => { if (busy) return; setErr(""); setBusy(true); try { if (enroll && mfaToken && window.DMS && window.DMS.mfaConfirmAtSignIn) { const r = await window.DMS.mfaConfirmAtSignIn(mfaToken, mfa.trim()); if (r && r.ok) { noteSuccess(r.user || user, "server + new authenticator"); onLogin({ dealer: storeNow(), user: r.user || user, role: r.role || "" }); return; } setErr((r && r.msg) || "That code did not match. Try the current one."); return; } if (mfaToken && window.DMS && typeof window.DMS.loginMfa === "function") { const r = await window.DMS.loginMfa(mfaToken, mfa.trim(), mfaInfo ? mfaMethod : undefined); if (r && r.ok) { noteSuccess(r.user || user, "server + MFA"); onLogin({ dealer: storeNow(), user: r.user || user, role: r.role || "" }); return; } if (r && r.serverReached) noteFailure(user, "MFA code rejected"); setErr((r && r.msg) || "Incorrect verification code. Try again."); return; } // No server MFA token → there is nothing to verify against locally. setErr("Verification requires the server. Go back and sign in again."); } catch (e) { setErr(e.message || "MFA verification failed."); } finally { setBusy(false); } }; const onKey = (e) => { if (modal) return; if (e.key === "Enter") { e.preventDefault(); step === "creds" ? submitCreds() : submitMfa(); } else if (e.key === "F1") { e.preventDefault(); setModal("help"); } else if (e.key === "F9") { e.preventDefault(); step === "creds" ? submitCreds() : submitMfa(); } else if (e.key === "Escape") { if (step === "mfa") setStep("creds"); } }; React.useEffect(() => { window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }); return (
window.k2bToast && window.k2bToast("Cannot minimize sign-on window.", "info")} onMax={() => {}} onClose={() => setModal("leave")} />
{/* Centered login card */}
{/* Branded header */}
{SB
Dealer Management System · Secure Access
{step === "creds" && (
Sign In
Enter your dealer credentials. Multi-factor authentication may be required.
{connNote && (
⏰ {connNote}
)} User Name Password
{caps && (
⇪ Caps Lock is on
)} {(err || coolLeft > 0) && (
⚠ {coolLeft > 0 ? `Too many failed sign-ins on this station — try again in ${coolLeft}s.` : err}
)} {offlineOffer && (
⚠ Server unreachable ({offlineOffer.detail}). You can open a restricted offline session on this workstation only: no deals can be started, no admin screens, and nothing will be shared until the server is back.
)}
)} {step === "mfa" && (
Two-Factor Verification
{enroll ? <>The owner turned on sign-in codes for your login. Scan this with your authenticator app (Google / Microsoft Authenticator), then type the 6-digit code it shows. : mfaMethod === "sms" ? <>We text a 6-digit code to your phone{mfaInfo && mfaInfo.phone ? <> ({mfaInfo.phone}) : null}. Type it below. : mfaMethod === "backup" ? <>Type one of your 10 backup codes (each works once). : mfaMethod === "pass" ? <>Type the one-time sign-in pass the owner gave you (it works once, within 15 minutes). : <>Open your authenticator app and enter the 6-digit code from the DMS entry in your authenticator app.}
{enroll && enroll.qrPng &&
Authenticator set-up code
or type the key: {enroll.secret}
} {!enroll && mfaInfo && mfaInfo.methods && mfaInfo.methods.length > 1 && (
{mfaInfo.methods.map(m => ( ))}
)} {!enroll && mfaMethod === "sms" && (
{smsNote && {smsNote}}
)} {mfaMethod === "backup" ? "Backup Code" : mfaMethod === "pass" ? "One-Time Pass" : "Verification Code"} setMfa(mfaMethod === "backup" || mfaMethod === "pass" ? e.target.value.replace(/[^A-Za-z0-9-]/g, "").slice(0, 20) : e.target.value.replace(/[^0-9]/g, "").slice(0, 6))} style={{ width: "100%", padding: "12px 14px", border: "1px solid #c0c8d4", outline: 0, fontFamily: "'Courier Prime', monospace", fontSize: 24, letterSpacing: 8, textAlign: "center", color: "#0b1424", background: "#FFFDE7", }} /> {err && (
⚠ {err}
)}
Didn't get a code, or lost your phone? Ask the owner — he can give you a one-time sign-in pass. (Nobody else can change sign-in codes.)
)} {/* Footer */}
Version 1.0.0 · K2B NETWORK SOLUTIONS LLC {SB ? "Built for " + SB.dba : ""}
{modal === "help" && ( setModal(null)}>
Signing In
• Enter Dealer Id, User Name (your employee #), and Password.
• If your account has MFA, you will be prompted for a 6-digit code from your authenticator.
• Press Enter or F9 to continue at each step.
Need an account or a password reset?
Ask your dealership admin — accounts live in Settings → Users (Server) — or call K2B NETWORK SOLUTIONS LLC support.
Server unreachable?
You will be offered a restricted offline session for this workstation. Work done offline is not shared until the server is back and an admin runs Sync This Workstation.
)} {modal === "forgot" && ( setModal(null)} buttons={ setModal(null)}>OK}> Passwords are reset by your dealership administrator in Settings → Users (Server) → Reset Password. Contact the admin for immediate access. )} {modal === "leave" && ( setModal(null)} buttons={<> setModal(null)}>No { setModal(null); // Browsers only let script-opened windows close themselves. try { window.close(); } catch (e) {} window.k2bToast && window.k2bToast("You can close this browser window now.", "info"); }}>Yes — Exit }> Are you sure you want to leave the DMS? Any unsaved work will be lost. )}
); }; /* Sign-on form atoms */ const SOLabel = ({ children }) => (
{children}
); const SOInput = ({ value, onChange, type = "text", placeholder, autoFocus, style, name, autoComplete, onKeyDown, onKeyUp }) => ( onChange(e.target.value)} placeholder={placeholder} autoFocus={autoFocus} name={name} autoComplete={autoComplete} onKeyDown={onKeyDown} onKeyUp={onKeyUp} style={{ width: "100%", padding: "9px 12px", boxSizing: "border-box", border: "1px solid #c0c8d4", outline: 0, fontFamily: "var(--font-ui)", fontSize: 14, color: "#0b1424", background: "#FFF", ...style, }} onFocus={(e) => e.target.style.borderColor = "#1B66A8"} onBlur={(e) => e.target.style.borderColor = "#c0c8d4"} /> ); const primaryBtn = { flex: 1, padding: "10px 14px", border: 0, cursor: "pointer", background: "linear-gradient(180deg, #1c2942 0%, #0b1424 100%)", color: "#f4d27a", fontFamily: "var(--font-ui)", fontSize: 13, fontWeight: 700, letterSpacing: 1, textTransform: "uppercase", }; const ghostBtn = { padding: "10px 14px", border: "1px solid #c0c8d4", cursor: "pointer", background: "#FFF", color: "#0b1424", fontFamily: "var(--font-ui)", fontSize: 13, fontWeight: 500, }; Object.assign(window, { SignOn });