Admin CRM overview
emaw-crm-app is the staff web app, branded EMAW Admin. It replaces Planning Center for contacts and events and Mailchimp for email, and adds the map, store operations and resource publishing. It is a client only; the API is the Django backend, documented for this app in docs/CRM_FRONTEND_GUIDE.md.
| Package name | emaw-admin |
| Stack | React 19, Vite 8, TypeScript 6, React Router 8 (data router, lazy route modules), TanStack Query 5, Tailwind 4 with in-repo UI primitives, Leaflet + react-leaflet + Geoman + leaflet.heat, Recharts, Tiptap (rich text), CodeMirror (HTML), Vitest + Testing Library |
| Hosting | Cloudflare Workers (static assets + /rest/* proxy), not Pages |
| URLs | dev https://admin-dev.everymanawarrior.cc, prod https://admin.everymanawarrior.cc, both behind Cloudflare Access |
| Sign-in | Cloudflare Access (Google Workspace) and a backend account in the Django group CRM Staff or with granted capabilities |
| Own docs | README.md, src/api/README.md |
| Tests | Vitest, 64 test files |
Routes
Route table in src/routes/router.tsx and src/routes/paths.ts; each feature declares its routes in src/features/<area>/routes.tsx, gated by a capability through src/features/auth/routeGate.tsx. Everything except /login and /google/callback renders inside Root (RequireAuth + AppShell).
| Path | Capability | Page |
|---|---|---|
/login | public | Staff sign-in (password or Google) |
/google/callback | public | Landing for the Google OAuth popup (connect Google for Meet) |
/ | any | Dashboard; scoped users get links into their filtered list and map |
/contacts, /contacts/new, /contacts/:id | contacts.view / contacts.edit | List with filters, bulk assign, CSV export; create; detail with Profile, Groups & Tags, Notes, Activity, Registrations, Orders, Campaign history, Location tabs |
/groups | groups.manage | Groups, Tags, Roles, Note categories |
/regions | geography.manage | Region and Area tree, polygon editor, assignment |
/map | map | Points, heat map, sales layer, geography overlay, drawing tools, filters |
/shapes | campaigns | Saved map shapes used as audiences |
/templates, /templates/new, /templates/:id | templates | Email templates: block designer, rich text or HTML, AI draft, image library, preview, test send |
/campaigns, /campaigns/:id, /campaigns/:id/report | campaigns | List; four-step wizard (Email & event, Audience, Prepare & test, Send/schedule); delivery report |
/events, /events/new, /events/:id | events | Events; detail tabs Details, Tickets, Registrations, Attendees (check-in), Meet attendance |
/store, /store/orders, /store/orders/:id, /store/orders/other/:id, /store/inventory | store | Dashboard with charts; web orders (resubmit/refresh to Deposco, CSV); Deposco-only orders; inventory with per-SKU history |
/resources, /resources/new, /resources/:id | resources | Website resources: media upload to R2, cover image, publish/unpublish |
* | 404 |
The sidebar (src/components/layout/AppShell.tsx) hides entries the user lacks the capability for. There is no donations UI yet.
Layout
src/
api/ typed fetch wrapper (client.ts), errors, query helpers, CSV download, directUpload (presigned PUT)
components/
layout/ AppShell, environment banner
ui/ buttons, inputs, dialogs, toasts, tables, badges
features/ one folder per area: auth, home, contacts, groups, geography, shapes, notes,
activity, map, audience, templates, campaigns, events, store, resources
each with api.ts, types, screens, routes.tsx, *.test.tsx
routes/ route table, router, placeholder and 404 pages
lib/ small utilities
config.ts the ONLY place env vars are read
test/ Vitest setup and fixtures
worker/index.ts the /rest proxy Worker
scripts/deploy.mjs
wrangler.jsonc
public/_headers