BGBidGauge Documentation

1 · Sign-in and initial data load

Browser / SPA App() component Firebase Auth email/password + claims Cloud Firestore companies/{'{'}companyId{'}'}/* 1 signInWithEmailAndPassword(email, password) 2 user + ID token (claims: companyId, MFA remember state) 3 If TOTP is enrolled or company email-MFA is required and not remembered for this company (see Fig. 6) — challenge before proceeding further 4 onSnapshot(doc(companies/{'{'}id{'}'})) — companyPrefs + mode flags 5 onSnapshot(query(projectSummaries), chunked by stage, ≤30 values/chunk) 6 onSnapshot(collection(editPresence)) — live "who's editing" feed 7 parses window.location for a deep link (Fig. shown in Feature Inventory) 8 (only if a project/proposal is deep-linked) full projects/{'{'}id{'}'} doc + its storageFolders 9 Item Library and full project collection load lazily, only when the active page actually needs them (see Data Model)

Fig. 4 — Data now loads per-collection and on-demand, rather than one document for the whole company.

2 · Proposal edit → PDF export → storage save

Browser / SPA ProposalEditor Cloud Firestore projects/{'{'}id{'}'} + projectSummaries/{'{'}id{'}'} Firebase Storage companies/{'{'}id{'}'}/project-storage/… 1 edits groups / line items (local state) 2 setDoc(projects/{'{'}id{'}'}) + setDoc(projectSummaries/{'{'}id{'}'}) via Promise.all 3 user clicks "Export PDF" 4 buildProposalPdf() via pdf-lib, in-browser 5 uploadBytesResumable(.../proposal-exports/{'{'}proposalId{'}'}/...) 6 download URL + storage path 7 patch proposal.lastExportedPdf, save both docs again 8 Browser triggers a local file download of the generated PDF to the user — one stored copy is kept per proposal/change order

Fig. 5 — Same shape as before, but writes now target the project's own document plus its summary mirror instead of one shared company document.

3 · Live tax rate lookup

Browser / SPA Estimate page lookupTaxRate() Cloud Function Firestore taxRateCache ZipTax api.zip-tax.com 1 httpsCallable("lookupTaxRate")({'{'}zip, ...{'}'}) 2 get taxRateCache/{'{'}country_region_zip{'}'} alt: cache hit & < 30 days old → return cached rate else: cache miss or stale GET /request/v60?postalcode=… (X-API-KEY) 3 tax rate response 4 set taxRateCache/{'{'}key{'}'} (30-day TTL) 5 rate applied to the estimate's tax fields

Fig. 6 — Same shape as before; the provider behind the cache changed from Avalara AvaTax to ZipTax.

4 · Payment application billing cycle

PaymentApplicationsPage client-side, in-memory SOV / Pricing engine src/lib/paymentApplications.js Cloud Firestore projects/{'{'}id{'}'} 1 buildSovBreakdown(project) — phases, factors, tax rows 2 getPaymentApplicationSovRows() + prior-period totals 3 user enters this-period % / stored materials 4 calculatePaymentApplicationTotals(), retainage summary 5 save draft → setDoc(projects/{'{'}id{'}'}), patches paymentApplications[] 6 export → buildPaymentApplicationPdf() (AIA-style layout) 7 Continuation sheet, certification block, and retainage figures are drawn to mirror the standard AIA G702/G703 pay app format

Fig. 7 — The core math (steps 1, 2, 4) now lives in a small, unit-tested module instead of inline in App.jsx.

5 · BOM export

Browser / SPA Dashboard / Proposal buildBomWorkbookXlsx() hand-rolled XLSX + ZIP + CRC32 Local file Blob download 1 user clicks "Export BOM" 2 filters Material-type line items across selected proposals 3 builds .xlsx bytes entirely in-browser 4 triggers download, named for the project 5 App prompts the user to update BOM color-dot status back on the project

Fig. 8 — Unchanged. Still no xlsx/exceljs dependency; see Technical Risk Notes.

6 · Email login verification (MFA)

Browser / SPA login screen sendEmailLoginVerificationCode / verifyEmailLoginVerificationCode Firestore + Auth emailLoginVerificationCodes, claims 1 not remembered for this company → challenge 2 sendEmailLoginVerificationCode({'{'}companyId{'}'}) 3 store SHA-256(salt:code), 10-min expiry — email sent via Gmail API 4 user enters code + "remember this browser" 5 verifyEmailLoginVerificationCode({'{'}companyId, code, rememberDays{'}'}) on success (≤5 attempts, else lockout): setCustomUserClaims: bidgaugeEmailMfaByCompany[id] = now + min(rememberDays,120)d 6 force getIdToken(true) → new claim picked up, login proceeds

Fig. 9 — "Remembered device" is really a per-company, per-account expiry stamped into the Auth token, not a browser cookie.

7 · Database backup & restore

Company Settings Backup & Recovery panel createManualDatabaseSnapshot / restoreDatabaseBackup Firestore + Storage databaseBackups meta + gzip file 1 admin clicks "Create manual snapshot" 2 read projects, projectSummaries, itemLibrary, company doc, users 3 gzip JSON → database-backups/{'{'}companyId{'}'}/manual.json.gz 4 metadata: size, counts, createdAt — panel refreshes its list — later, restoring a snapshot — 5 admin types "RESTORE", confirms 6 download+gunzip, delete then rewrite projects/projectSummaries/itemLibrary 7 restoredType, restoredLabel, counts

Fig. 10 — Both steps require Company Admin. Project Storage files, Auth passwords, and cache/edit-presence records are never part of this flow either direction — see Cloud Functions Reference.

Previous← Data Model