# Architecture ## What the app is SAR Link Portal — a customer + admin portal for an ISP. Users register (verified against the national identity registry), add network devices, and pay for device subscriptions either by bank transfer (MIB) or from a prepaid wallet they top up. Admins verify users, manage devices/payments/topups, and credit wallets. ## Stack (current) - **Next.js 15** (App Router, React 19), TypeScript. - **Tailwind CSS v4** (CSS-first, no config file) + **shadcn/ui** (`new-york`, neutral) + Radix + **lucide** icons. - **Jotai** (global UI state) + **React Query** (mounted, not yet used). - **react-hook-form** + **zod** (forms/validation), **nuqs** (URL query state), **sonner** (toasts), **next-themes**, **motion** (animation). - **Auth:** mid-migration — old **next-auth** (JWT session) → new **localStorage token** (`lib/auth-store.ts`) + axios **api-client** (`lib/api-client.ts`). ## Folder map ``` app/ (auth)/auth/{signin,signup,verify-otp,verify-otp-registration}/ # public auth pages (dashboard)/{devices,payments,top-ups,wallet,users,...}/ # authed app (user + admin) api/{auth/[...nextauth],check-devices}/ # frontend API routes (being removed) layout.tsx page.tsx globals.css auth.ts actions/ # server actions: auth-actions, payment, user-actions, (omada, ninja = dead) queries/ # server data fetchers: authentication, devices, islands, users, wallet components/ # feature components + components/ui (shadcn primitives) lib/ # auth-store, api-client, atoms, backend-types, schemas, person, utils utils/ # tryCatch, axios-client (dead), axiosInstance (dead) providers/ # QueryProvider, theme, (AuthProvider = next-auth, being removed) hooks/ middleware.ts(removed) docs/ # this documentation deploy/ # (superseded — real prod is .build/prod/, see below) ``` ## Current architecture (BFF — what's being replaced) ``` Browser ──(RSC + Server Actions)──▶ Next.js server ──(fetch + Token)──▶ Django API ``` The Next.js server sits between the browser and Django: - Server components + server actions read the token via `getServerSession` and call Django server-side over `SARLINK_API_BASE_URL`. - Public origin `/api/*` = Next.js's own routes (NextAuth); Django's `/api` is reached only internally. - Deployed as a Node container behind nginx (`.build/prod/`). ## Target architecture (static + nginx, direct-to-API) ``` Browser ──(static HTML/JS from nginx) ├─ API_URL calls (/api, /callback, /auth/*) ─▶ nginx ─▶ Django └─ everything else ─▶ nginx serves the static build ``` Goals set by the project owner: - **No Node/Bun in production** — ship a static build served by nginx. - **Configurable API base:** `API_URL=http://localhost:8000` in dev, `API_URL=https://portal.sarlink.net/api` (nginx → backend) in prod. - Browser calls the Django API **directly** (same-origin in prod via nginx; cross-origin in dev with CORS). Two ways to get there (under discussion): 1. **Next.js static export** (`output: "export"`) — keeps Next; fights the framework (no server actions, `searchParams` needs Suspense, dynamic routes need query-params, middleware gone). Foundation already built: `lib/auth-store.ts`, `lib/api-client.ts`, `components/auth/route-guard.tsx`, config flip, dev `rewrites()` proxy. See `../STATIC_MIGRATION_PLAN.md`. 2. **Vite + React SPA (recommended)** — natural fit for a static client app with a configurable `API_URL` (`import.meta.env.VITE_API_URL`). A **port, not a rewrite**: all components, shadcn, react-query, jotai, zod transfer 1:1; only the shell (routing, `next/link`, `next/font`, `next/image`, layouts) changes. TanStack Router or React Router for routing. Either way the **auth/data plumbing already written is reusable**: token in localStorage, `Authorization: Token` interceptor, client route guard, React Query for reads/mutations. ## Auth model (target) 1. Login: `POST {API}/callback/auth/ { token: pin }` → `{ token, user, expiry }`. 2. Store token + user in localStorage (`setAuth`). 3. Attach `Authorization: Token ` on every request (axios interceptor). 4. `RouteGuard` gates authed pages; `401` → `clearAuth()` + redirect to signin. 5. Admin gating from `user.is_admin` / `user.user_permissions`. ## Production deployment (where nginx lives) The real prod stack is **`.build/prod/`** (repo root), not `frontend/deploy/`: - `compose.yml` — postgres + backend (Django/gunicorn) + frontend + **nginx** (single entrypoint, `:8080→80`). - `nginx.conf` — reverse proxy. **Currently built for the BFF model** (`/` → Next Node server; `/api` NOT proxied to Django). For the static migration this must change to: serve the static build + proxy `/api`, `/callback`, `/auth/{login,logout,mobile}` to Django; the separate Node `frontend` service goes away. - `frontend.Dockerfile` — currently `output:"standalone"` + `node server.js`; becomes a static build folded into the nginx image. See [api-endpoints.md](./api-endpoints.md) for exact paths and the `/auth/*` frontend-vs-backend collision that the nginx config must handle. ## Known issues / cleanup - **Mid-migration auth:** dashboard pages/actions/queries still use `getServerSession`; they must move to the client token store (or be removed in the SPA port). - **Stale wallet balance:** header balance comes from the login `userAtom` snapshot; not refreshed after topups/payments until re-login. - **Dead code:** Omada, Invoice Ninja, `/auth/login/`, `/islands/`, `/inventory/`, `backendMobileLogin`, `app/api/check-devices`, `utils/axios-client.ts`, `utils/axiosInstance.ts`. - **person-verify** is called directly from the frontend; planned to route through the backend.