Version: 0.1.814 Last Updated: April 2026
┌──────────────────────────────────────────────────────┐
│ Browser (SPA) │
│ React 19 + TypeScript + Vite + Tailwind CSS │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │
│ │ Auth Layer │ │ Data Layer │ │ UI Layer │ │
│ │ AuthContext │ │ DataContext │ │ Components │ │
│ │ Firebase │ │ Services │ │ Hooks │ │
│ │ Auth SDK │ │ Firestore │ │ Recharts │ │
│ └─────────────┘ └──────────────┘ │ Leaflet │ │
│ │ Framer │ │
│ └─────────────┘ │
└──────────────────┬───────────────────────────────────┘
│ HTTPS
┌──────────────────▼───────────────────────────────────┐
│ Firebase Platform │
│ │
│ ┌────────────┐ ┌───────────┐ ┌──────────────────┐ │
│ │ Auth │ │ Firestore │ │ Storage │ │
│ │ Email/Pass │ │ RBAC Rules│ │ Storage Rules │ │
│ │ Phone Auth │ │ Indexes │ │ Hive-partition │ │
│ └────────────┘ └───────────┘ └──────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Cloud Functions (Node 22) │ │
│ │ reseed | getStorageStats | migrateSearchKeys │ │
│ └─────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
│
┌──────────────────▼───────────────────────────────────┐
│ External APIs │
│ Google Maps Directions API | Google Maps JS API │
│ OpenStreetMap tiles (Leaflet) │
└──────────────────────────────────────────────────────┘
| Layer | Technology | Version |
|---|---|---|
| UI Framework | React | 19.2.1 |
| Language | TypeScript | ~5.8.2 |
| Build Tool | Vite | 6.2.0 |
| Styling | Tailwind CSS | 3.4.17 |
| Animation | Framer Motion | 12.23.26 |
| Icons | Lucide React | 0.554.0 |
| Charts | Recharts | 3.5.0 |
| Maps (display) | Leaflet + react-leaflet | 1.9.4 / 5.0.0 |
| Maps (routing) | Google Maps JS API + Directions | via loader |
| i18n | i18next + react-i18next | 25.6.3 / 16.3.5 |
| PDF export | jsPDF + html2canvas | 4.0.0 / 1.4.1 |
| File export | jszip + file-saver | 3.10.1 / 2.0.5 |
| Date utilities | date-fns | 4.1.0 |
| Firebase Client SDK | firebase | 12.6.0 |
| AI (available) | @google/genai + Vercel AI SDK | 1.30.0 / 5.0.114 |
| Unit testing | Vitest + Testing Library | 4.0.15 / 16.3.0 |
| E2E testing | Cypress | 15.8.1 |
| Cloud Functions runtime | Firebase Functions v2 | Node 22 |
| Image processing (Functions) | Jimp | 1.6.0 |
index.html
└── index.tsx React root, i18n init, AuthProvider wrapper
└── App.tsx Main router — tab state, global listeners, role gates
├── AuthContext.tsx Firebase Auth state
├── DataContext.tsx Shared Firestore data (users, routes, etc.)
├── Sidebar.tsx Navigation with role-based visibility
├── AppHeader.tsx Top bar, user avatar, language toggle
└── [Module Components]
No external state management library. State is distributed across:
| Layer | Mechanism | Scope |
|---|---|---|
| Auth state | AuthContext (React Context) |
App-wide |
| Shared data | DataContext (React Context) |
App-wide |
| App logic | useAppState (custom hook) |
App.tsx |
| Route editor | Local component state | RouteEditor |
| Camera | useCameraManager (custom hook) |
CameraCapture |
| Optimization | useRouteOptimization (custom hook) |
RouteEditor |
components/
App/
AppHeader.tsx Top navigation bar
Sidebar.tsx Left navigation with role gates
UI/
LoadingSpinner.tsx Reusable spinner
RefreshButton.tsx Reusable refresh trigger
RouteEditor/
RouteEditorForm.tsx Route fields and destination list
RouteEditorHeader.tsx Save/cancel/optimize controls
RouteMapContainer.tsx Leaflet map with polyline
DestinationItem.tsx Individual stop with photo capture
reports/
VisitStatusReport.tsx
CustomerHistoryReport.tsx
EmployeePerformanceReport.tsx
CustomerDeliveryVolumeReport.tsx
FleetStatusReport.tsx
app-status/
AppHealth.tsx
LoginHistory.tsx
ReportIssues.tsx
[Top-level modules]
LogisticsDashboard.tsx
CalendarView.tsx
RouteEditor.tsx (orchestrator)
WarehouseView.tsx
Reporting.tsx (orchestrator)
SystemSettings.tsx
UserManagement.tsx
CustomerManagement.tsx
FleetManagement.tsx
ProductManagement.tsx
AppStatus.tsx (orchestrator)
No React Router. Tab-based navigation managed in App.tsx via activeTab state. The Sidebar emits tab change events; App.tsx renders the corresponding component. This is appropriate for an authenticated SPA with no deep-link requirements.
| Service | Usage |
|---|---|
| Authentication | Email/password login. Secondary app instance for creating users without ending admin session. |
| Firestore | Primary datastore. All collections have server-side RBAC rules. Real-time listeners for live dashboard updates. |
| Cloud Storage | Photo proof-of-delivery uploads. Hive-style folder partitioning. |
| Cloud Functions | Admin-only or Manager-only operations too privileged/expensive for the client. |
| Hosting | SPA hosting from dist/ with CSP headers and SPA rewrite rule. |
Each entity has its own service file. Services are plain objects or exported functions — no class instances except where secondary Firebase apps are needed.
| File | Responsibility |
|---|---|
firebase.ts |
SDK init; exports auth, db, storage, functions |
RouteService.ts |
Route CRUD, atomic ID generation, paginated queries, date-range queries |
customerService.ts |
Customer CRUD, aggregation count, paginated + prefix search |
userService.ts |
User CRUD, aggregation count, paginated + prefix search |
productService.ts |
Product CRUD, aggregation count, paginated + prefix search |
vehicleService.ts |
Vehicle + maintenance CRUD with optional date filters |
audit.ts |
Login event logging; multi-provider IP detection with 3s timeout |
adminService.ts |
Secondary Firebase app for user creation without session interruption |
BackupService.ts |
Full Firestore → JSON backup/restore via Storage (450-doc batch chunks) |
GoogleMapsService.ts |
Directions API integration; max 25 waypoints; returns order + polyline + distance |
clientSeedingService.ts |
Seeding constants and city/industry config shared with Cloud Functions |
imageUtils.ts |
Client-side image compression and Base64 conversion |
All functions are HTTPS Callable (v2). Auth is verified server-side in every function.
| Function | Auth Required | Description |
|---|---|---|
reseed |
ADMINISTRATOR | Generate full demo dataset (users, customers, products, routes, maintenance) for a given city/industry/scale |
getStorageStats |
MANAGER or ADMINISTRATOR | Enumerate Firebase Storage buckets and Firestore collection sizes |
migrateSearchKeys |
ADMINISTRATOR | Backfill lowercase searchKey field on users and customers for case-insensitive search |
Request
│
├─► Frontend role gates (Sidebar visibility, button guards)
│ └─ Defense-in-depth only. Can be bypassed. Not authoritative.
│
└─► Firebase Security Rules (Firestore + Storage)
└─ Authoritative. Cannot be bypassed from client.
│
└─► Cloud Functions: verify auth + role in Firestore
└─ Authoritative for server-side operations.
// Helper functions used throughout rules
function isAuthenticated() → request.auth != null
function getUserRole() → firestore.get(users/{uid}).data.role
function isApproved() → role in [MANAGER, EMPLOYEE, VIEWER, ADMINISTRATOR]
function isManager() → role in [MANAGER, ADMINISTRATOR]
// Privilege escalation prevention
// Users cannot update their own role field
allow update: if !request.resource.data.diff(resource.data).affectedKeys().hasAny(['role'])- Login logs: Allow unauthenticated creation (to capture failed login attempts)
- App status docs: Schema-validated — max 20 fields, type constraints
- config/settings: Only Managers can write warehouse/camera config
- Employees: Can update routes but cannot change
assignedToordueDate
proofs/** → create: any authenticated user (field ops upload their own proofs)
read/update/delete: MANAGER or ADMINISTRATOR only
backups/** → read/write: MANAGER or ADMINISTRATOR only
default → deny all
All list views use cursor-based pagination (not offset). Pattern:
const { items, lastVisible } = await CustomerService.getPaginated(pageSize, cursor);
// Next page:
const { items: nextPage } = await CustomerService.getPaginated(pageSize, lastVisible);Advantage: stable under concurrent writes, no skipped/duplicated items.
Firestore lacks full-text search. GoNow uses a searchKey field (stored as name.toLowerCase()) with range queries:
query.where('searchKey', '>=', term)
.where('searchKey', '<=', term + '\uf8ff')Applied to: users, customers, products.
IDs are human-readable and daily-scoped. Generated via Firestore transaction:
Format: yyyyMMdd.sequence
Example: 20260415.3 (3rd route on April 15, 2026)
Transaction on config/counters:
if lastDate == today → increment currentCount
else → reset currentCount to 1, set lastDate = today
Document counts use Firebase Aggregation API (getCountFromServer) — a single read regardless of collection size. Used in CustomerManagement, UserManagement, and ProductManagement for pagination metadata.
Creating a user via Firebase Admin SDK from the client would sign out the current session. GoNow initializes a secondary Firebase app instance (adminService.ts) for user creation, keeping the manager's active session intact.
npm run dev # Vite dev server (port 3000, HMR)Requires .env file with Firebase credentials and Google Maps API key.
Automated pipeline — run with npm run deploy:
- Increment version in
package.jsonand i18n files - Build Cloud Functions: TypeScript →
functions/lib/ - Build frontend: Vite production build →
dist/ - Create annotated git tag
v{version} - Deploy to Firebase: Firestore rules + Storage rules + Hosting + Cloud Functions
Vite splits vendor bundles to optimize caching:
| Chunk | Contents |
|---|---|
vendor-react |
React, ReactDOM |
vendor-firebase |
Firebase SDK modules |
vendor-ui |
Lucide, Framer Motion, Recharts |
vendor-maps |
Leaflet, react-leaflet, Google Maps loader |
vendor-pdf |
jsPDF, html2canvas, jszip, file-saver |
vendor-utils |
date-fns, i18next |
| Type | Tool | Location |
|---|---|---|
| Unit / Component | Vitest + Testing Library | tests/ |
| E2E | Cypress | cypress/e2e/ |
| Coverage | @vitest/coverage-v8 | Run npx vitest run --coverage |
npm test # Unit tests (Vitest)
npm run test:e2e # E2E tests (Cypress headless)
npm run test:e2e:headed # Cypress with browser visible| File | Coverage Area |
|---|---|
Auth.test.tsx |
Login flows, phone auth, profile completion |
RouteService.test.ts |
Route ID generation, daily counters |
CustomerManagement.test.tsx |
Customer CRUD flows |
CameraCapture.test.tsx |
Camera permissions, image capture |
imageUtils.test.ts |
Image compression, Base64 conversion |
AppIntegration.test.tsx |
App-level rendering |
| Variable | Required | Description |
|---|---|---|
VITE_FIREBASE_API_KEY |
Yes | Firebase web API key |
VITE_FIREBASE_AUTH_DOMAIN |
Yes | Firebase auth domain |
VITE_FIREBASE_PROJECT_ID |
Yes | Firebase project ID |
VITE_FIREBASE_STORAGE_BUCKET |
Yes | Firebase storage bucket |
VITE_FIREBASE_MESSAGING_SENDER_ID |
Yes | Firebase messaging sender ID |
VITE_FIREBASE_APP_ID |
Yes | Firebase app ID |
VITE_GOOGLE_MAPS_API_KEY |
No | Enables Google Directions API for route optimization; falls back to local 2-Opt heuristic if absent |
SEED_USER_PASSWORD |
Dev only | Password for seeded demo users |
TEST_USER_PASSWORD |
Dev only | Password for Cypress test user |
See .env.example for the full template.