Files
2026-09-22 00:50:30 +05:00

3.4 KiB

frontend

Vite + React + TypeScript SPA for the SAR Link portal. No Node at runtime: production is a static bundle served by nginx.

Build Vite 8
UI React 19, Tailwind CSS v4
Routing React Router 7 (SPA, BrowserRouter)
Auth knox token in localStorage, AuthProvider in src/lib/auth.tsx

Layout

src/lib/api.ts            fetch wrapper, typed endpoints, ApiError
src/lib/auth.tsx          AuthProvider (session bootstrap, sign in/out)
src/lib/auth-context.ts   AuthContext + useAuth
src/lib/date.ts           local-time YYYY-MM-DD formatting
src/components/           ui primitives, Select, DateField, AppLayout, RequireAuth
src/pages/                Login (two-step), Register (form), Dashboard, NotFound

Run it

From the repo root (starts the API and database too):

docker compose up --build

Or on the host, against a backend on localhost:8000:

npm install
npm run dev

Then http://localhost:5173.

Talking to the API

The app always calls a relative /api/.... Nothing hardcodes a backend URL:

  • devvite.config.ts proxies /api, /admin, /static and /media to VITE_API_PROXY_TARGET (http://backend:8000 in compose).
  • prod — nginx serves dist/ and proxies the same prefixes to gunicorn.

So there is no CORS in production, and no API origin to configure at build time.

Sign-in and registration

src/pages/Login.tsx is one form whose second step the API chooses:

  1. Mobile number -> POST /api/auth/start/
  2. The response's next decides what appears under it:
    • password -> password field -> POST /api/auth/login/password/
    • otp -> 6-digit code, with a resend cooldown -> POST /api/auth/verify/
  3. verify/ answers with next: dashboard (token stored, on to the portal) or register (the returned ticket goes to /register in router state).

The code step looks the same whether or not the number has an account - the UI has no idea until the code is confirmed, which is the point.

src/pages/Register.tsx requires that ticket (no ticket -> back to /login), shows the verified number read-only, and collects name, ID card/passport/work permit number, date of birth (DateField, a react-day-picker popover with month and year dropdowns), atoll + island (from GET /api/locations/atolls/, island list filtered by the chosen atoll) and the two agreement checkboxes linking to sarlink.net/terms and /policy. Submitting shows a "pending approval" panel - it does not sign the applicant in.

A pending or rejected account that signs in later sees a status banner on the dashboard instead of services.

On reload AuthProvider calls GET /api/auth/me/ to turn the stored token back into a user, and clears it if the API rejects it.

Adding a dependency

node_modules lives in a Docker volume that outlives image rebuilds, so a new entry in package.json isn't in the container until it's installed there. The dev service runs npm install on every start, so:

npm install <pkg>                        # updates package.json + lock on the host
docker compose restart frontend          # installs it in the container

If it still can't resolve the import, the volume is stale — recreate it with docker compose up -d -V frontend.

Scripts

npm run dev       # dev server on :5173
npm run build     # tsc -b && vite build -> dist/
npm run preview   # serve dist/ locally
npm run lint      # oxlint