Frontend applications
Nội dung này hiện chưa có sẵn bằng ngôn ngữ của bạn.
- React 19, React Router 7, Tailwind, TanStack Query, Zustand, i18next (user-visible strings in
src/i18n.ts).
Layout
Section titled “Layout”- Admin:
apps/admin-web— routes insrc/routes/app-router.tsx. - Vendor:
apps/vendor-web— routes insrc/routes/app-router.tsx; POS at/pos. - Shared:
packages/shared— dashboard primitives (Button, tables, form fields, etc.); sources are consumed directly fromsrc/(nodist). Usenpm run shared:watchfrom the repo root fortsc --noEmit --watchwhile editing shared types or components. - ECR Hub:
packages/ecr-hub— ships compileddist/only (npm run buildin that workspace, ornpm run ecr-hub:watchfrom the repo root to recompile on every save). Backend, vendor-web, and vendor-desktop resolve@indochina/ecr-hubfrom that output.
Vendor web — route map
Section titled “Vendor web — route map”Source of truth: apps/vendor-web/src/routes/app-router.tsx. ProtectedRoute without requiredModule only checks authentication; many nested routes add requiredModule, requireModuleWrite, or requiredModulesAny for RBAC.
Public
Section titled “Public”| Path | Screen / notes |
|---|---|
/ | Marketing landing (sign-in entry only; no public register route) |
/login | Vendor sign-in (owner vs staff) |
/forgot-password, /verify-otp, /reset-password | Password reset chain |
POS (authenticated)
Section titled “POS (authenticated)”| Path | Gate |
|---|---|
/pos | order module + write |
/point-of-sale/* | order module (read for workspace shell; write required for …/sale) — includes sale, /point-of-sale/items, orders, report (order or finance read), settings hub and hardware (…/settings/terminal, …/printer, …/cash-drawer, bridge labs on Electron). Cashier preset seeds settings + finance read on the backend; on the UI, routes under /point-of-sale also treat missing settings/finance as satisfied when the user has order (dashboard /settings still requires the settings module). |
POS sale — named drafts, server drafts tab, and edit order: On the sale screen, saving a named draft while online submits a pending order via the same place-order API with submit_as: "draft" (and still snapshots the tab in local storage). Server draft orders are listed on the same sale route with ?posTab=drafts (top bar Draft orders next to order tabs, or side nav; no duplicate tab strip inside the sale column). Edit order from that list or from the POS orders / return list sets /point-of-sale/sale?resumeOrder=<orderId> (and clears posTab); the sale page fetches that order and refills the active tab (store switches to the order’s store when needed). /point-of-sale/draft-orders redirects to …/sale?posTab=drafts. Opening a named draft that was linked to a server order refetches from the API when online.
POS orders list: The list is always scoped to order_type=pos. Status pills use ?listStatus=all|completed|cancelled|draft (completed is sent to the API as tab=paid; draft → tab=draft (server drafts with place_submit_as=draft excluding rows whose order_status is already cancelled/failed); legacy ?listStatus=paid still resolves to completed). The sale screen footer links to the same presets (all, completed, cancelled, draft). On All (and other mixed tabs), server draft rows are detected from place_submit_as in the list payload so Resume + Delete draft match the sale-screen Draft orders panel; paid rows keep print + row menu.
Dashboard (layout /dashboard)
Section titled “Dashboard (layout /dashboard)”| Area | Paths (representative) |
|---|---|
| Home | /dashboard |
| Orders | /dashboard/orders, …/orders/create, …/orders/:id, deliveries, shifts (+ templates, lanes, :shiftId) |
| Products | /dashboard/products, create/edit/detail, categories, units, addons (placeholder), attributes, tags, gallery, suppliers |
| Marketing | /dashboard/marketing/banners |
| Inventory | …/inventory/warehouses, stocks, lots, adjustments (+ detail / new where applicable) |
| Finance | …/finance/transactions, expenses, refunds, receivables, report (+ detail routes); legacy …/finance/cash-book redirects to Transactions |
| Customers | /dashboard/customers, …/:id |
| Stores / managers / employees / roles | list + create/edit/detail patterns |
| Finances (legacy hub) | /dashboard/finances |
| Discounts / notifications | /dashboard/discounts, /dashboard/notifications |
| Settings | /dashboard/settings/* — general, notifications, team, devices, shift-templates, checkout-lanes, PAX/CodePay labs, POS bridge agent, etc. |
| Profile | /dashboard/profile |
| Terminals | /dashboard/terminals, …/terminals/:deviceId |
| System (placeholders) | …/system/translations, business-settings, currencies, withdrawals, delivery-men |
Physical card terminals: the POS bridge WebSocket (/api/v1/pos-bridge/…), activate flow, and optional PUBLIC_API_BASE_URL are documented in POS card terminals & ECR Hub (PAX focus), POS card terminal — CodePay (ECR Hub), and Environment configuration. Vendor exposes bridge lab pages under Settings (listener + payment log, CodePay WS lab) and the POS bridge agent screen for vendor-desktop.
Environment variables (Vite)
Section titled “Environment variables (Vite)”| Variable | Admin | Vendor | Purpose |
|---|---|---|---|
VITE_API_URL | Yes | Yes | Backend origin for REST calls (no trailing slash). Declared in src/vite-env.d.ts for both apps. |
Resolution (getApiBaseUrl()):
- If
VITE_API_URLis set and non-empty → use it (trim trailing slashes). - Else if development →
http://localhost:3000. - Else → empty string → Axios
baseURLbecomes/api/v1(relative to the page origin). Use this when the UI and API are served from the same host (e.g. Docker image or reverse proxy).
There are no other VITE_* flags in the repo today; search import.meta.env when adding feature flags or analytics.
Build and deploy
Section titled “Build and deploy”- Production builds emit static assets under each app’s
dist/. The rootDockerfileruns Vite builds withVITE_API_URL=empty, then copies artifacts intoapps/backend/static/adminand **static/vendor**as part ofnpm run build:deploy. Details: Deployment and static SPAs.
Vendor desktop (Electron cashier)
Section titled “Vendor desktop (Electron cashier)”App: apps/vendor-desktop — packages the cashier-only Vite build from apps/vendor-web (build:electron-cashier-renderer → vendor-desktop/dist/renderer).
Before the first pack, configure the API URL baked into the renderer:
cp apps/vendor-web/electron.env.example apps/vendor-web/.env.electron# edit VITE_API_URL=https://your-api-host.exampleFrom repo root:
| Script | Output |
|---|---|
npm run vendor:desktop:pack:mac | macOS dmg + zip (arm64 + x64) under apps/vendor-desktop/release/ |
npm run vendor:desktop:pack:win | Windows NSIS Setup.exe + zip (x64) — can be built on macOS or Windows (embedded sql.js, no native module) |
npm run vendor:desktop:pack:win:msi | Windows MSI + zip — run on Windows only (WiX; fails on macOS with Wine/32-bit error) |
npm run vendor:desktop:pack | Installers for the current OS (same folder) |
Pack runs ecr-hub:build, validates .env.electron, builds the renderer + compiles main/preload, then electron-builder. Icons: apps/vendor-desktop/assets/mac/icon.icns, assets/win/icon.ico.
Platform notes: Build macOS artifacts on a Mac. pack:win (NSIS + zip) can run on macOS or Windows because POS offline storage uses embedded sql.js (WebAssembly), not native sqlite3. MSI (pack:win:msi) still requires Windows (WiX).
Vendor web — Playwright (browser E2E)
Section titled “Vendor web — Playwright (browser E2E)”End-to-end tests live in apps/vendor-web/e2e/ and use Playwright. They drive the real vendor-web dev server and call a running NestJS API (same as a developer machine).
Prerequisites
Section titled “Prerequisites”- Backend listening on the API origin (default
http://127.0.0.1:3000).GET /api/v1/healthmust return200before tests start (e2e/global-setup.tswaits up to 120s). - MySQL +
DB_ENABLED=truewhen you want parity with production auth (vendor rows invendors/vendor_employees). Rundocker composeunderapps/backend, configureDB_*in.env, run migrations, then start Nest (e.g.npm run start:devinapps/backend). - Credentials: set
PLAYWRIGHT_VENDOR_OWNER_EMAIL/PLAYWRIGHT_VENDOR_OWNER_PASSWORDto match a real owner in the database. Defaults (owner@nipos.local/123123) match the in-memory seeds used whenDB_ENABLED=false(no MySQL).
One-time browser install
Section titled “One-time browser install”cd apps/vendor-web && npm run test:e2e:installRun (from repo root)
Section titled “Run (from repo root)”With MySQL up and Nest on port 3000, set PLAYWRIGHT_VENDOR_OWNER_EMAIL / PLAYWRIGHT_VENDOR_OWNER_PASSWORD to a real owner row (defaults only work against in-memory seeds).
npm run vendor:test:e2eOr from apps/vendor-web: npm run test:e2e. Optional: npm run test:e2e:ui or npm run test:e2e:headed.
Quick smoke (no MySQL): start a second API on 3001 with in-memory vendor seeds, then point Playwright at it:
# Terminal A — in-memory vendor owner owner@nipos.local / secret12cd apps/backend && PORT=3001 DB_ENABLED=false THROTTLE_LIMIT=10000 npx nest start
# Terminal Bcd apps/vendor-web && PLAYWRIGHT_API_BASE_URL=http://127.0.0.1:3001 npm run test:e2eturbo run test:e2e: Turborepo strict env handling can otherwise hide variables from the Playwright process. The repo declares passThroughEnv for the test:e2e task in turbo.json for the PLAYWRIGHT_* keys below, so this works:
PLAYWRIGHT_API_BASE_URL=http://127.0.0.1:3001 npx turbo run test:e2e --filter=@indochina/vendor-webLogin E2E scenarios (e2e/login.spec.ts)
Section titled “Login E2E scenarios (e2e/login.spec.ts)”Names below match test("…") in code (see Sign-in and account (Vendor) for role/layout + full test index).
test("…") | What it checks |
|---|---|
owner signs in and dashboard shell is visible | Owner tab, valid owner credentials → POST /api/v1/auth/vendor/login OK → URL /dashboard, main.main-layout visible |
staff signs in and dashboard shell is visible | Staff tab, valid employee credentials → same post-login assertions |
validation: invalid email (client) | Zod — “Enter a valid email address.”; form > [role='alert'] count 0; stay on /login |
validation: empty password (client) | Zod — “This field is required.”; no API banner; stay on /login |
API error: wrong password shows banner | Login POST not OK → form > [role='alert'] visible (copy matches Unauthorized / sign in / credentials) → /login |
Credentials default to owner@nipos.local / 123123 and employee@nipos.local / 123123 (same as AUTH_VENDOR_* in-memory seeds when DB_ENABLED=false). Override with PLAYWRIGHT_VENDOR_* env vars (see table below).
Screenshots & traces
Section titled “Screenshots & traces”- Per-test attachments: each login scenario calls
attachFullPagePng(e2e/helpers/attach-screenshot.ts) so the HTML report always includes a full-page PNG for that run (pass or fail). - On failure only (global):
playwright.config.cjsalso setsscreenshot: only-on-failureandvideo: retain-on-failureundertest-results/(gitignored). trace:on-first-retry— open with Playwright Trace Viewer when CI retries.
Open the last HTML report: cd apps/vendor-web && npx playwright show-report.
Docs screenshots (Starlight)
Section titled “Docs screenshots (Starlight)”PNG files for the docs site are written under apps/docs/public/screenshots/vendor/ (served as /screenshots/vendor/…). A dedicated spec e2e/docs-screenshots.spec.ts captures the vendor sign-in page for Sign-in and account (Vendor).
Run from repo root (requires the same API prerequisites as other vendor E2E tests):
npm run vendor:test:e2e:docs-screenshotsOr from apps/vendor-web: npm run test:e2e:docs-screenshots (sets PLAYWRIGHT_UPDATE_DOCS_SCREENSHOTS=1 for that run).
On Windows shells that do not support VAR=value prefixing, set the variable first, then run Playwright on that file only:
set PLAYWRIGHT_UPDATE_DOCS_SCREENSHOTS=1cd apps/vendor-web && npx playwright test e2e/docs-screenshots.spec.tsThe spec is skipped unless PLAYWRIGHT_UPDATE_DOCS_SCREENSHOTS=1, so normal npm run test:e2e runs do not overwrite doc images.
Cashier / POS workspace screenshots for Cashier help center land in apps/docs/public/screenshots/cashier/ via e2e/docs-cashier-screenshots.spec.ts:
npm run vendor:test:e2e:docs-cashier-screenshotsRequired quality gate per screen (vendor-web)
Section titled “Required quality gate per screen (vendor-web)”For vendor-facing screens, treat these as release requirements:
- Unit test set for the screen logic (component/hook/schema/service adapters in
src/**). - E2E test set for the user flow (
apps/vendor-web/e2e/**). - Documentation screenshot captured by Playwright and stored under
apps/docs/public/screenshots/vendor/.
The evolving screen-by-screen checklist (implemented vs backlog) is maintained in Vendor testing and screenshots index.
Environment variables (Playwright)
Section titled “Environment variables (Playwright)”| Variable | Default | Purpose |
|---|---|---|
PLAYWRIGHT_API_BASE_URL | http://127.0.0.1:3000 | Nest origin; passed to Vite as VITE_API_URL for the dev server under test |
PLAYWRIGHT_VENDOR_WEB_PORT | 5187 | Vite --strictPort — default avoids colliding with a dev server on 5174 |
PLAYWRIGHT_VENDOR_OWNER_EMAIL | owner@nipos.local | Login spec — owner account |
PLAYWRIGHT_VENDOR_OWNER_PASSWORD | 123123 | Login spec — owner password |
PLAYWRIGHT_VENDOR_EMPLOYEE_EMAIL | employee@nipos.local | Login spec — staff account |
PLAYWRIGHT_VENDOR_EMPLOYEE_PASSWORD | 123123 | Login spec — staff password |
PLAYWRIGHT_UPDATE_DOCS_SCREENSHOTS | (unset) | Set to 1 to run docs-screenshots.spec.ts and refresh PNGs under apps/docs/public/screenshots/vendor/ |
See also Environment configuration for DB_*, DB_ENABLED, and AUTH_VENDOR_*.