Skip to main content

Architecture

Request path in deployment​

wrangler.jsonc: main: worker/index.ts, assets from ./dist with not_found_handling: single-page-application, and run_worker_first: ["/rest/*"] so only API calls hit the Worker. The Worker strips any client-sent X-Cf-Access-Jwt, copies Cloudflare's Cf-Access-Jwt-Assertion into it, and returns 404 for anything that is not /rest/*.

API client (src/api/)​

  • client.ts: apiFetch and api.get/post/patch/delete helpers. Enforces trailing slashes, attaches the Bearer token, refreshes once on 401 then retries, throws ApiError carrying X-Request-ID, and shows an error toast for everything except 400 and 409 (whose bodies drive the UI).
  • errors.ts, query.ts (query-string helpers), download.ts (CSV on click), directUpload.ts (putToPresignedUrl with progress for R2).
  • Documented in src/api/README.md.

Authentication and capabilities (src/features/auth/)​

  • authStore.ts: access token in memory; refresh via the HttpOnly cookie; POST /rest/api/token/, /refresh/, /logout/, /rest/auth/google/; GET /rest/crm/me/ returns the caller's capabilities and scope.
  • access.ts defines the capability names (contacts.view, contacts.edit, contacts.bulk, notes, activity, map, groups.manage, geography.manage, campaigns, templates, events, store, resources, access.manage) and scopes (all, region, areas, self).
  • routeGate.tsx wraps each feature's routes; AppShell hides navigation the user cannot use. The backend applies the same rules, so the UI gating is a convenience, not the control.
  • AdminAccessCard on a contact (capability access.manage) grants capabilities and scope to the linked app user.

Concepts and the full contract: docs/CRM_FRONTEND_GUIDE.md §1–§3.

Server state​

TanStack Query 5 for every read; mutations invalidate the relevant query keys. Long-running backend jobs (GeographyAssignRun, AI template drafts, campaign sending) are polled with refetchInterval until they finish. The devtools are available in development.

Feature modules​

FeatureNotable pieces
contactsContactFilterBar, ContactsListPage, ContactDetailPage tabs in tabs/, BulkAssignDialog, LinkUserDialog, AdminAccessCard
mapPointsLayer, heat.ts, SalesLayer (geo-sales), GeographyOverlay, DrawTools (Geoman), FilterPanel, LayerControl, ShapePanel, MapAreaDialog; base layers in layers.ts
geography, shapesRegion/Area polygons and saved shapes
templatesDesignEditor (blocks), RichTextEditor (Tiptap), CodeMirror HTML, AiTemplateDialog, ImageLibraryDialog, VariablesMenu, SendTestDialog
campaigns, audienceWizard steps, AudienceBuilder, SendConfirmDialog, SendingProgress, ReportChart, RecipientsTable, devGuard.ts
eventsRecurrence, ticket tiers, registrations, check-in, Google connect, Meet attendance
storeOrders, Deposco-only orders, inventory, Charts.tsx (Recharts)
resourcesResourceForm, MediaCard, CoverCard, probeDuration.ts; upload flow in Cloudflare R2

Dev send guard​

features/campaigns/devGuard.ts: when VITE_API_BASE_URL is the dev backend, the send button is disabled and a banner shown unless every audience group is in VITE_DEV_SEND_GROUP_IDS. Remove only when the backend allow-list alone is judged sufficient.