Web dashboards
The nexis-omni-web repository holds the online dashboards: a pnpm + Turborepo workspace of two apps plus shared packages. The offline POS is a separate Flutter app and is not in this repo.
Two apps, two planes
Section titled “Two apps, two planes”| App | Plane | Who uses it |
|---|---|---|
apps/hq-dashboard |
TENANT | a business’s staff (Owner / Manager / Cashier) |
apps/platform-admin |
ADMIN | the platform operator |
The two never mix planes. The tenant app sends a Tenant-ID header on every request (including login) and reaches the API under /api; the admin app never sends Tenant-ID and reaches the API under /admin. See Auth planes & tenancy.
Shared packages
Section titled “Shared packages”@workspace/api-client- the hey-api typed client, generated from the backend’s OpenAPI, plus the Big.js money schema.@workspace/auth- the two-plane client and single-flight refresh-on-401.@workspace/ui,@workspace/tokens- the component library and design tokens.@workspace/i18n- Paraglide messages (English, Sinhala, Tamil).@workspace/config- shared config, including the runtime environment schema.
In this section
Section titled “In this section”- Stack & app layout - the React SPA monorepo stack and which of the two apps a screen belongs in.
- Calling the API - the two planes, the refresh cookie, money-as-string, error codes, and the generated client.
Canonical deep docs (in the repo)
Section titled “Canonical deep docs (in the repo)”nexis-omni-web/docs/backend-integration.md- the full API contract from the client’s side: auth, 2FA, the refresh cookie, theTenant-IDheader, money-as-string, error shapes, and the endpoint inventory. Read it before writing code that calls the API.nexis-omni-web/docs/adr/0001-frontend-stack.md- the stack decision.nexis-omni-web/docs/reference/apps.md- which app a given screen belongs in.
What to know up front
Section titled “What to know up front”- Gate off the server, mirror it in the UI. Authorization comes from
GET /me/permissionsandGET /capabilities; the UI hides what the user cannot do, but the server is the real gate. Do not over-gate read-only screens that the server leaves open. - Money is a string. Decode it through the money schema; never
Number()it. - Environment is validated at runtime in the browser.
VITE_API_BASE_URLmust be a full URL (z.url()), inlined at build time. A wrong or empty value builds a bundle that fails on load - which is why the deployment Dockerfiles validate it at build time.