Bỏ qua để đến nội dung

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).
  • Admin: apps/admin-web — routes in src/routes/app-router.tsx.
  • Vendor: apps/vendor-web — routes in src/routes/app-router.tsx; POS at /pos.
  • Shared: packages/shared — dashboard primitives (Button, tables, form fields, etc.); sources are consumed directly from src/ (no dist). Use npm run shared:watch from the repo root for tsc --noEmit --watch while editing shared types or components.
  • ECR Hub: packages/ecr-hub — ships compiled dist/ only (npm run build in that workspace, or npm run ecr-hub:watch from the repo root to recompile on every save). Backend, vendor-web, and vendor-desktop resolve @indochina/ecr-hub from that output.

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.

PathScreen / notes
/Marketing landing (sign-in entry only; no public register route)
/loginVendor sign-in (owner vs staff)
/forgot-password, /verify-otp, /reset-passwordPassword reset chain
PathGate
/posorder 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; drafttab=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.

AreaPaths (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 / roleslist + 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.

VariableAdminVendorPurpose
VITE_API_URLYesYesBackend origin for REST calls (no trailing slash). Declared in src/vite-env.d.ts for both apps.

Resolution (getApiBaseUrl()):

  1. If VITE_API_URL is set and non-empty → use it (trim trailing slashes).
  2. Else if developmenthttp://localhost:3000.
  3. Else → empty string → Axios baseURL becomes /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.

  • Production builds emit static assets under each app’s dist/. The root Dockerfile runs Vite builds with VITE_API_URL= empty, then copies artifacts into apps/backend/static/admin and **static/vendor** as part of npm run build:deploy. Details: Deployment and static SPAs.

App: apps/vendor-desktop — packages the cashier-only Vite build from apps/vendor-web (build:electron-cashier-renderervendor-desktop/dist/renderer).

Before the first pack, configure the API URL baked into the renderer:

Terminal window
cp apps/vendor-web/electron.env.example apps/vendor-web/.env.electron
# edit VITE_API_URL=https://your-api-host.example

From repo root:

ScriptOutput
npm run vendor:desktop:pack:macmacOS dmg + zip (arm64 + x64) under apps/vendor-desktop/release/
npm run vendor:desktop:pack:winWindows NSIS Setup.exe + zip (x64) — can be built on macOS or Windows (embedded sql.js, no native module)
npm run vendor:desktop:pack:win:msiWindows MSI + zip — run on Windows only (WiX; fails on macOS with Wine/32-bit error)
npm run vendor:desktop:packInstallers 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).

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).

  1. Backend listening on the API origin (default http://127.0.0.1:3000). GET /api/v1/health must return 200 before tests start (e2e/global-setup.ts waits up to 120s).
  2. MySQL + DB_ENABLED=true when you want parity with production auth (vendor rows in vendors / vendor_employees). Run docker compose under apps/backend, configure DB_* in .env, run migrations, then start Nest (e.g. npm run start:dev in apps/backend).
  3. Credentials: set PLAYWRIGHT_VENDOR_OWNER_EMAIL / PLAYWRIGHT_VENDOR_OWNER_PASSWORD to match a real owner in the database. Defaults (owner@nipos.local / 123123) match the in-memory seeds used when DB_ENABLED=false (no MySQL).
Terminal window
cd apps/vendor-web && npm run test:e2e:install

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).

Terminal window
npm run vendor:test:e2e

Or 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 window
# Terminal A — in-memory vendor owner owner@nipos.local / secret12
cd apps/backend && PORT=3001 DB_ENABLED=false THROTTLE_LIMIT=10000 npx nest start
# Terminal B
cd apps/vendor-web && PLAYWRIGHT_API_BASE_URL=http://127.0.0.1:3001 npm run test:e2e

turbo 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:

Terminal window
PLAYWRIGHT_API_BASE_URL=http://127.0.0.1:3001 npx turbo run test:e2e --filter=@indochina/vendor-web

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 visibleOwner tab, valid owner credentials → POST /api/v1/auth/vendor/login OK → URL /dashboard, main.main-layout visible
staff signs in and dashboard shell is visibleStaff 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 bannerLogin 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).

  • 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.cjs also sets screenshot: only-on-failure and video: retain-on-failure under test-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.

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):

Terminal window
npm run vendor:test:e2e:docs-screenshots

Or 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:

Terminal window
set PLAYWRIGHT_UPDATE_DOCS_SCREENSHOTS=1
cd apps/vendor-web && npx playwright test e2e/docs-screenshots.spec.ts

The 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:

Terminal window
npm run vendor:test:e2e:docs-cashier-screenshots

Required quality gate per screen (vendor-web)

Section titled “Required quality gate per screen (vendor-web)”

For vendor-facing screens, treat these as release requirements:

  1. Unit test set for the screen logic (component/hook/schema/service adapters in src/**).
  2. E2E test set for the user flow (apps/vendor-web/e2e/**).
  3. 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.

VariableDefaultPurpose
PLAYWRIGHT_API_BASE_URLhttp://127.0.0.1:3000Nest origin; passed to Vite as VITE_API_URL for the dev server under test
PLAYWRIGHT_VENDOR_WEB_PORT5187Vite --strictPort — default avoids colliding with a dev server on 5174
PLAYWRIGHT_VENDOR_OWNER_EMAILowner@nipos.localLogin spec — owner account
PLAYWRIGHT_VENDOR_OWNER_PASSWORD123123Login spec — owner password
PLAYWRIGHT_VENDOR_EMPLOYEE_EMAILemployee@nipos.localLogin spec — staff account
PLAYWRIGHT_VENDOR_EMPLOYEE_PASSWORD123123Login 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_*.