10 / WORK / CASE STUDY
Field Sales On-site Activation PWA
A mobile-first PWA for field reps in a beauty-industry B2B channel — scan a business card, activate the client account on the spot, manage CRM and track commissions. Built from scratch in six days; deployed.
- TYPE
- FRONTEND
- SCOPE
- Internal · deployed
- STACK
- REACT · VITE · MUI · REACT-QUERY · ZUSTAND · PWA
- LINKS
- Internal project (private)
01 / PROBLEM
Field reps used to copy a client’s details onto paper and open the account later from a back-office system — the client could not order on the spot, and the rep’s commissions, pending reviews and CRM lived in yet other tools. This PWA folds “photograph the business card → create and activate the account on-site → the client orders immediately” into one installable, offline-tolerant mobile flow, with commissions, CRM and account review in the same console — built for field reps in a beauty-industry B2B channel.
02 / CONSTRAINTS
- Company-internal; the codebase is private. Deployed on an internal domain.
- Field connectivity is uncontrolled: every action — above all account submission — needs local persistence and automatic resend on failure; a rep must never re-type a form.
- Business-card images are customer PII: OCR runs entirely on-device, never shipping the image to a third-party service — also the only option with no cloud-OCR budget.
- The frontend consumes APIs only — no business rules live client-side; commissions and permissions are decided by the backend.
03 / ARCHITECTURE
A React 18 + Vite 6 SPA: MUI 5 components, TanStack React Query 5 for server-state caching, and a single Zustand store holding only the login identity — 17 routed screens and 6 API modules with 50 call sites against 4 platform backend services (auth, commissions, CRM, stores), ~8,200 lines in all. State design is deliberately minimal: server data lives in React Query; client state is the identity, nothing else.
Cross-cutting: an Axios interceptor attaches tokens and handles 401 refresh (concurrent requests queue behind a single refresh); the Service Worker covers app-shell offline only (cache-first statics, APIs never cached); the on-site activation page has three layers of weak-network resilience — live local drafts, an offline queue on failed submits, auto-resend on reconnect; OCR runs fully in-browser on tesseract.js. Deployment is a static nginx site with same-origin API reverse proxying.
04 / RESPONSIBILITIES
Independent development: empty repo to deployment in six days (2026/07/10–15), every commit mine — requirements, architecture, implementation and deployment. The backend APIs are existing platform services outside this project’s scope.
05 / CHALLENGES → SOLUTIONS
CHALLENGE
The store page initially hit an existing endpoint built for another frontend: missing the rep’s internal attribution ID, it did not error — it silently fell back to filtering by user_id. The screen looked fine; the list was wrong.
SOLUTION
Switched to a sales-specific endpoint where the backend resolves the rep’s own attribution ID before filtering, and moved login to a sales-only gate (consumer accounts get 403) in the same commit. The fix was 2 files and 14 lines — against the hardest kind of bug, the one that looks perfectly healthy.
CHALLENGE
When an order was returned the backend clawed back its commission, but all three commission screens kept showing the gross figure — the number a rep saw never matched what would actually be paid.
SOLUTION
Moved all three pages to net-of-clawback in one pass, annotating partial clawbacks and striking through fully clawed-back records instead of deleting them — a rep can see where the money went, not just watch it vanish.
CHALLENGE
On-site activation is the core flow, yet connectivity is whatever the client’s shop happens to have; the business card is PII that cannot go to a cloud OCR — and there was no budget to buy one anyway.
SOLUTION
Resilience comes in three layers: live local drafts, an offline queue when the connection itself fails, auto-resend on reconnect. OCR runs on-device with tesseract.js, prefilling only still-empty fields and never overriding manual input, with the rep confirming before submit — a pure-frontend solution needing no backend work and no budget.
06 / SYSTEM FACTS
17
routed screens
50
API call sites
~190KB
vendor bundle (gzip)
6
days, zero to deployed
07 / LESSONS
Getting data back is not the same as getting the right data — a silent fallback is more dangerous than a loud error. A shared endpoint that swaps its filtering logic at the edge, without erroring, is indistinguishable from a working one. I now ask how an existing endpoint degrades before consuming it, instead of trusting a 200.
Weak-network resilience is a design-time decision — retrofitting drafts, queues and resend into a finished form state machine is far harder than building them in. The honest gap: this project has no automated tests (offline paths were verified by hand); the first item of further maintenance is covering the queue and resend logic.
NEXT
Shareholder AGM E-Voting System