Files
daily_console_web/README.md
2026-08-24 20:35:18 +05:30

102 lines
4.6 KiB
Markdown

# Nearle Console
The new Nearle Daily retail operations console. Built against the existing Fiesta
backend — the API is a fixed constraint, not something this repo changes.
## Running it
```bash
npm install
npm run dev # http://localhost:3100
npm run typecheck # tsc --noEmit
npm run build # typecheck + production build
```
> **Port 3100, not 3000.** The old console (`daily_merchant_web`) runs its dev
> server on 3000. `strictPort` is on, so if 3100 is taken this fails loudly
> rather than silently moving — which is the failure that makes you think your
> changes did not land when you are actually looking at a different app.
In development Vite proxies `/fiesta` → `https://fiesta.nearle.app`. For a
deployed build set `VITE_API_BASE` (see `.env.example`).
## Stack
| Layer | Choice |
|---|---|
| Build | Vite 8 |
| Language | **TypeScript 7**, strict, `noUncheckedIndexedAccess`. No `.js`/`.jsx` anywhere. |
| UI | React 19 + **@astryxdesign/core** |
| Theme | `src/theme/nearle.ts`, compiled to `nearle.css` |
| Routing | React Router 7, split per page |
| Server state | TanStack Query 5 |
| Icons | lucide-react |
| Spreadsheets | `xlsx`, loaded on demand |
## What's built
Only the **Nearle Admin** workspace so far:
- `/nearle/stores` — every tenant, branch counts, per-tenant performance
- `/nearle/stores/:tenantId` — one tenant's branches, orders and revenue
- `/nearle/onboard/tenant` — provision a merchant group
- `/nearle/onboard/branch` — commission an outlet with its delivery thresholds
- `/nearle/catalogue` — the global catalogue, plus both product-import paths
`/admin/*` (Store Admin) and `/store/*` (Store Manager) render a named
placeholder rather than a 404, so those roles land somewhere that explains
itself.
## Things about the backend that shape this code
These are not bugs in this repo. They are the API's behaviour, and each one is
worked around deliberately.
1. **No web authentication.** The login endpoints return the user record and no
token; only `/v1/pos/*` has middleware. So the session *is* that record, held
in `sessionStorage`. `src/auth/session.ts` is the only file that changes when
tokens arrive.
2. **Every list call needs a scoping id or it 400s.** The IDOR pass added
controller-level guards. Query hooks are therefore `enabled`-gated on the id
and the id is part of the query key.
3. **Roles are derived, not asserted.** `issuperadmin` is checked before
`roleid`, because the flag is server-derived and a roleid is not. Roleids 7
and 8 are till roles and never reach an admin workspace.
4. **`POST /products/create` takes one product and returns no id.** The
controller passes the struct to the service by value, so GORM writes the
generated id into a copy that is then discarded. The sheet importer therefore
creates, then re-queries by SKU to resolve ids, then batches the location and
stock writes. See `importSheetProducts` in `src/api/products.ts`.
5. **The two import paths are not symmetric.** Catalogue import is one
idempotent batch call. Sheet import is N creates with no dedupe on SKU, so
re-uploading a file duplicates its products — the UI says so before the
button.
6. **The global catalogue has a price *range*, not a price**, and no mapping to
a tenant's own categories. Category, subcategory, retail price, cost and tax
are collected before an import can be enabled.
7. **POS endpoints take one required `locationid`.** There is no tenant-wide
counter-sales call, so a multi-branch POS view fans out per branch.
## The 30-second cadence
Online orders land when an order is placed; counter sales reach the console on a
30-second refetch; anything still sitting on an offline till has not arrived at
all. So **any figure blending the two is eventually consistent**, and the
`<Freshness>` component exists to say when a number was last true and how many
bills are still stranded. Polling is applied per query in `src/queries/hooks.ts`
rather than globally — a provisioning form has no business re-polling.
## Design
The visual system is ported from KROW (which is built on Astryx too, so the
tokens are role-for-role comparable), with Nearle purple `#662582` as the accent.
The ambient canvas in `index.css` — a fixed, viewport-wide horizontal gradient at
`z-index: -1` — is the one piece of KROW that carries most of the character.
Radius, type scale and control heights are matched to KROW in
`src/theme/nearle.ts`, with the reasoning kept in comments.
**Open:** the dark-mode accent `#B57FD0` was chosen for 5.41:1 contrast on
Astryx's dark surface and still needs brand-owner sign-off before dark mode
ships.