MSaving

Web app changes

Conventions for screens, strings, money and data fetching in msaving/web.

Layout

src/
  config.ts          build-time settings, all optional (VITE_API_URL defaults to the same origin)
  auth/              session token store, useSession, signOut, new-account forms
  lib/api.ts         fetch wrapper (bearer token, ApiError with the code) and API types
  lib/queries.ts     TanStack Query hooks and keys
  lib/money.ts       format and parse amounts in minor units
  components/        Shell (sidebar, book switcher, admin link), AccountForm, AmountField, pickers, dialogs
  i18n/              vi.json (default), en.json, strings.test.ts
  pages/             one file per screen; App.tsx routes (setup, sign-in, join, reset, the app)

Stack: React, Vite, TypeScript, TanStack Query, React Router, i18next, Material UI (default theme, follows the device’s light/dark), Vitest, oxlint. Forms: plain inputs and FormData (no form library decided).

Rules

  • Same origin: the server serves this app, so calls go to /v1 on the same address and the API refuses other origins. In development, Vite’s proxy keeps it that way. Links (invites, resets) are built from window.location.origin.
  • Strings: every visible string is a key in both vi.json and en.json. Vietnamese first. The test fails if keys differ. Never translate what the user typed.
  • Error codes from the API are translated here (components/errors.ts and the string files).
  • Money: integers from the API; format with lib/money.ts (Intl.NumberFormat). Never float math. In Vietnamese . groups thousands, so typing “1.250” for VND means 1250.
  • Concurrency: send the version you loaded; on 409 reload and tell the user. Make a transaction’s id when the user clicks Save and reuse it on retry.
  • Desktop first: sidebar from the md breakpoint, tables for lists. Phones still get a working layout.
  • Nothing in localStorage but the session token and the language choice. No analytics or tracking.
  • Fast refresh: pages/ and components/ export components only; shared functions go in auth/ or lib/.

Gotchas

  • i18next’s <Trans> drops tags named like HTML void elements (<link>, <br>, <img>): use names like <policy>.
  • Queries that need a session pass enabled; the cache is cleared on sign-out.
  • The CSP comes from the server (backend/app/api/main.py): scripts only from itself, data: images (QR codes). A new external origin is a security decision first.

Checks

npm run lint && npm test && npm run build

Edit this page · Report a problem