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:
- dev —
vite.config.tsproxies/api,/admin,/staticand/mediatoVITE_API_PROXY_TARGET(http://backend:8000in 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:
- Mobile number ->
POST /api/auth/start/ - The response's
nextdecides what appears under it:password-> password field ->POST /api/auth/login/password/otp-> 6-digit code, with a resend cooldown ->POST /api/auth/verify/
verify/answers withnext:dashboard(token stored, on to the portal) orregister(the returned ticket goes to/registerin 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