Web & Admin Frontends
Both apps/web and apps/admin are React 18 + Vite + Tailwind apps that share a design system and the same conventions. The web app is the farmer/trader trading app; admin is the operations panel.
apps/web — the trading app
apps/web/src/
├── main.tsx Sentry init, QueryClient, Router, i18n bootstrap
├── App.tsx 100+ routes with legacy redirects
├── i18n.ts react-i18next init (en + hi)
├── locales/ en.json, hi.json (~500 keys, ~10 namespaces)
├── lib/ api client wiring, queryClient, feature flags, gps provider
├── routes/ Route definitions / guards
├── pages/ 60+ page components (Auth, Buy, Sell, Products, Deals, Farmer, Trader, Reports…)
├── components/
│ ├── ui/ Reusable primitives (Button, Input, Select, Card, Badge, BottomSheet, Toast…)
│ ├── layout/ AppLayout, Sidebar, BottomNav, PageHeader, PageTitle, BackButton, BannerSearch
│ ├── leads/ Lead boards, cards, inline offer/bid actions, contact share
│ ├── products/ Product form, taxonomy picker, metrics form, photos
│ ├── farms/ Farm maps (Leaflet), photos, crop pills
│ ├── inquiries/ Thread list, composer, thread view
│ ├── dashboard/ Quota strip, inquiry ticker, sparklines
│ ├── tiles/ Tile + TileMenu for hub pages
│ ├── search/ Search rail / results
│ └── forms/ Shared form building blocks
├── hooks/ Custom hooks (useQuota, useDeals, useReports, useTaxonomy, messaging stream…)
├── stores/ Zustand auth store
└── types/ Local types (prefer @cropto/types)Routing polarity (critical)
Routes are organised by intent, not resource type:
/buy= "I want to buy" → shows other users' sell leads → I make offers./sell= "I want to sell" → shows other users' buy leads → I make bids.
/buy and /sell are 4-tile hub landing pages; the discover boards live at /buy/browse and /sell/browse. Canonical permalinks /leads/sell/:id and /leads/buy/:id are the stable URL form for SMS deep-links (they auto-route to the owner or counterparty view).
Page-chrome rules (enforced across the app)
<PageTitle>on every list/board page (title + subtitle + count + CTA).<PageHeader>with a back-button on every detail/create/edit page.- True root pages (Dashboard,
/farms,/products,/notifications,/settings) have no back button; tile-menu spoke pages get a back-to-hub link viauseHubBackLink(). - Pages render content directly under the root —
AppLayoutalready provides page padding, max-width and centering; re-adding them double-pads.
State & forms
- Server state → React Query via
packages/hooks(query keys centralised inpackages/hooks/src/query-keys.ts). - API calls → the typed
packages/api-client(Axios). Never rawfetch. - Forms →
react-hook-form+zodResolver, inline errors,scrollToFirstError. - Number/price display → the shared
utils/format.tshelpers (formatPrice,formatPricePerKg,formatWeight,formatNumber) — Indian grouping, no raw floats.
apps/admin — the operations panel
apps/admin/src/
├── App.tsx Routes + ProtectedRoute (role ∈ {admin, sub-admin})
├── api/adminApi.ts Typed fetchers — all admin calls go through here (never axios directly)
├── stores/authStore Keeps BOTH access + refresh tokens; single-flight refresh interceptor
├── components/ UI primitives ported from web + admin-specific Badge, StatCard, DataTable
├── pages/ Auth, Dashboard, Users, Farmers, Deals, Grievances, Lookups, Taxonomy, System
└── locales/ en.json (i18next kept so hi.json can drop in)Admin pages (all <PageTitle>/<PageHeader> compliant, inline confirmations, no popups):
| Route | Purpose |
|---|---|
/dashboard | V2 platform stats |
/users, /users/:id | KYC patch, membership card, sub-admin grant, search-history (audit-gated) |
/farmers | Farmer directory with filters |
/deals | Deals dashboard — status + fulfilment filters, overdue/backfilled badges |
/grievances, /grievances/:id | Grievance queue + SLA-timed adjudication (also open to sub-admins) |
/lookups | Tabbed CRUD for affiliations / varieties / crops |
/taxonomy, /taxonomy/suggestions | Two-pane tree manager (rename, GST bracket, metric fields) + suggestion approval |
/system | Retention purge (typed-"PURGE" confirm), order backfill, Bull Board link |
The admin auth is hardened: it keeps both tokens, runs a single-flight refresh on TOKEN_EXPIRED, classifies terminal-vs-transient refresh failures (a 5xx never logs the admin out), and gates ProtectedRoute on role ∈ {admin, sub-admin} — a signed-in farmer gets a full-page NotAuthorized state, not a 403-ing shell.
Shared design system
Both apps use the same Tailwind theme (primary green #1B6B3A), the same component conventions (44px min tap targets, mobile-first, empty states, skeleton loaders), and the NativeWind-safe class subset so components are Phase-2 portable.
