Skip to content

jbwui — JBW360 Web Portal (React)

Module path: st-integration/jbwui/ · package jbw-client v0.1.0 · Create React App

React portal for JBW staff and dealers, backed by Cognito and jbw-sam.

The web front-end for the JB Warranties ServiceTitan integration (“JB360”, hosted at jbw360.jbwarranties.com). It is an internal/partner portal — login only, no public signup. Two roles via Cognito groups:

  • jbw-admin — JB Warranties staff: dashboards, dealer onboarding, user management, admin tools.
  • jbw-dealer — HVAC dealers/contractors: their own dashboard, agreement sync, claims.

Core flows: Cognito auth (incl. forced new-password, forgot/reset, idle timeout), a Terms-of-Use acceptance gate between sign-in and any navigation, a 4–5 step dealer onboarding wizard (ServiceTitan or Workiz), agreement sync/matching/approval against ServiceTitan data, claims management (with embedded JB Warranties iframes), and admin/dealer KPI dashboards.

ConcernChoice
FrameworkReact 18.3 (CRA / react-scripts 5), JavaScript (no TypeScript)
UIMUI v5 + MUI X (DataGrid, Charts, DatePickers), Emotion, styled-components
StateReact Context (src/AuthContext.jsx) + local state + localStorage; no Redux
FormsFormik + Yup
Routingreact-router-dom v6 (createBrowserRouter in src/App.js), guard in src/RouteGuard.jsx
AuthAWS Cognito via amazon-cognito-identity-js (src/services/jbwAuthServices.js)
HTTPaxios shared instance with interceptors (src/services/configs/axiosConfig.js)
Misccrypto-js (AES), lodash, date-fns/dayjs/moment, export-from-json, mammoth (client-side DOCX→HTML for the Terms panel)

Added by the July 2026 release. It is not a route — it is an in-page state swap on the sign-in page, which is why the route tables below are unaffected. It sits in front of every login.

Flow. After Cognito auth succeeds, a useEffect on src/pages/SignIn.js:277-300 runs checkTermsAndNavigate():

  1. getUserDetails(user.email)GET user?email= → the tblDealerRegistration row
  2. If isTermsAccepted === 1handleUserNavigation() (the normal admin/dealer branch)
  3. Otherwise → setShowTermsDialog(true), and the login form is replaced in place by <TermsOfUsePanel> inside the same Grid/Paper (SignIn.js:329-333)

The panel (src/components/TermsOfUsePanel.jsx) fetches the .docx named by envConfig.TermsUrl (:25), converts it with mammoth.convertToHtml (:30), and injects it via dangerouslySetInnerHTML (:105). Accept stays disabled until the user scrolls to the bottom (:41-47, gate at :130).

  • AcceptupdateUserTrems(loginUser.id, 1)PUT user/updatetermshandleUserNavigation()SignIn.js:248-266
  • DeclinesignOut(); the user is bounced

Consent is persisted per dealer-registration row, not per Cognito user. A second entry point — a “Download Terms of Use” Fab — is on every authenticated page via src/components/TopBar.jsx:134-143,323-334.

Backing Lambdas (both untemplated, added the same release):

CallLambdaSQL
GET user?email=jbw-sam/src/jbwgetactivedealerregistrationdetails/SELECT * FROM tblDealerRegistration WHERE registeredEmail = ? AND isactive = 1404s if no row
PUT user/updatetermsjbw-sam/src/jbwsetusertermsagreed/UPDATE tblDealerRegistration SET isTermsAccepted = ?, termsAcceptedOn = NOW() WHERE id = ?

Public auth routes live under RootLayout; all business flows sit under /home behind Cognito (RouteGuard).

Public (under RootLayout):

RoutePagePurpose
/, /signin, /signin/:idpages/SignIn.jsCognito login; :id carries encrypted email+code for invited users. Post-login: admins → /home/overview, dealers → onboarding check → dashboard or wizard
/signuppages/Register.jsDealer/user registration (verify-then-register)
/forgotpasswordpages/VerifyUser.jsInitiate forgot-password
/resetpassword/:idpages/ResetPassword.jsReset with code
/changepassword/:idpages/ChangePassword.jsForced new-password challenge

Authenticated (/home, HomeLayout = TopBar + Sidebar):

RoutePagePurpose
/home, /home/overviewpages/Overview.jsAdmin dashboard: KPI cards, latest onboardings, Admin Tools console
/home/dealerdetails[/:id]pages/DealerDetails.jsxDealer dashboard: info, KPIs, tenants, agreements/claims/invoices tabs
/home/onboardinghomepages/Onboarding.jsAll dealers grid + “Onboard New Dealer” + per-dealer sync
/home/agreementssynchomepages/AgreementsSyncHome.jsxAgreement sync: Map Location / Pending Approval / Approved tabs, Sync Data button
/home/userslistpages/UsersList.jsUser management
/home/transactionlogpages/TransactionLogs.jsxTransmission/sync logs (from tblTransmissionDetails)
/home/jbwclaimspages/JBWarrantiesFrame.jsxEmbedded JB Warranties claim360.aspx iframe
/home/todohome[/:id]pages/ToDoHome.jsxEmail-notification inbox (sidebar link currently commented out)
/home/settingsStub (route renders an MUI icon; pages/settings.js exists but is unused)
/home/onboarding/*pages/onboarding/Wizard: ClientName → ClientLocation → ClientPlatform → (ST: ClientCustom → ClientId) / (Workiz: ClientAccountId) → CLientConnect
  • Cognito ID token (JWT) stored in localStorage["token"]; role read from cognito:groups[0] (any group ≠ jbw-dealer is treated as admin).
  • Axios request interceptor attaches Authorization: Bearer <token> and drives a global loader via api-loading events.
  • Response interceptor: on 401, refreshes the Cognito session once (queueing concurrent requests) and retries; on failure fires session-expired → re-auth dialog.
  • AuthContext proactively refreshes tokens when <5 min to expiry (60 s check loop) and enforces a 30-minute idle timeout (src/hooks/useIdleTimer.js).

Everything business-related goes to one place — the jbw-sam gateway; the only non-gateway traffic is deep links and embedded JB Warranties iframes.

All API calls go through the shared axios instance with baseURL = REACT_APP_AWS_GATEWAY_BASE_URL (the jbw-sam API Gateway). Service modules live in src/services/ — the complete endpoint-per-module list is in the code reference.

External (non-gateway) integrations:

  • ServiceTitan deep links: REACT_APP_ST_URL; Workiz job links: REACT_APP_WZ_URL.
  • JB Warranties member portal iframes (hardcoded jbandassociates.biz URLs): claims console (claim360.aspx), claim dialog (jb360claim.aspx, with postMessage origin check), agreement PDF (customeragpdf.aspx), payment link.
  • Release-notes PDF from REACT_APP_RELEASE_NOTE_URL.

Quirk: several callers branch on APP_ENV === "Development" to read res.data.body vs res.data (API Gateway proxy wrapping differs between stages).

CRA REACT_APP_* vars, baked into the bundle at build time. Files: .env (default = Production values), prod.env, stage.env; selected via env-cmd in npm scripts.

VariablePurpose
REACT_APP_MYVARNAMEEnv label (“Production”/“Staging”) — drives staging banner + dev response unwrapping
REACT_APP_USER_POOL_ID / REACT_APP_CLIENT_IDCognito user pool / app client
REACT_APP_AWS_GATEWAY_BASE_URLBackend API Gateway base URL
REACT_APP_ST_URL / REACT_APP_WZ_URLServiceTitan / Workiz deep-link bases
REACT_APP_RELEASE_NOTE_URL / REACT_APP_RELEASE_NOTE_MONTHRelease-notes PDF + label
REACT_APP_ISSTAGINGStaging flag (YES/NO)
REACT_APP_TERMS_DOC_URLSite-root path to the Terms-of-Use .docx fetched by the sign-in consent panel and the TopBar download. Identical value in all three env files (line 10 each); surfaced as envConfig.TermsUrl (src/services/configs/envConfig.js:6)

Prod points at go.servicetitan.com and the prod API Gateway stage; stage points at integration.servicetitan.com and the dev stage.

Local dev — ⚠ the default .env holds production values:

npm install
npm start # dev server :3000 — Production config!

Staging dev — the safe option for day-to-day work:

npm run start:stage # staging config (stage.env), :3006

Production build:

npm run build # default .env
npm run build:prod # explicit prod.env bundle

Staging build:

npm run build:stage # stage.env bundle

Deployment note: there is no deploy config in the repo — the CRA build/ output is synced to the S3 bucket and served via the CloudFront distribution defined in jbw-sam/template.yaml (manual upload + invalidation). npm scripts use Windows set PORT= syntax, so they assume a Windows workstation.