# Home Automation — Frontend React SPA for the home-automation backend. Built with Vite + React 18 + TypeScript. Scaffolded in M2-T06; feature pages filled in by T07–T10. ## Stack | Layer | Library | Version | |---|---|---| | Build | Vite | 6.x | | UI framework | React | 18.x | | Language | TypeScript | 5.x | | Component library | Mantine | 7.x | | Data fetching | TanStack Query | 5.x | | Routing | react-router-dom | 6.x | | API client codegen | openapi-typescript | 7.x | | API client runtime | openapi-fetch | 0.17.x | | Testing | Vitest + @testing-library/react | 4.x / 14.x | ## npm Scripts | Command | What it does | |---|---| | `npm run dev` | Start Vite dev server (with backend proxy — see below) | | `npm run build` | `tsc -b && vite build` — type-check then build to `dist/` | | `npm run preview` | Serve the built `dist/` locally | | `npm run lint` | ESLint (flat config, React + TypeScript rules) | | `npm run typecheck` | `tsc --noEmit` — type-check without emitting files | | `npm run test` | Vitest (run once, no watch) | | `npm run codegen` | Regenerate `src/api/schema.d.ts` from `../openapi/openapi.json` | All frontend gates must pass before any task is considered done: ```bash npm run codegen npm run lint npm run typecheck npm run test npm run build # must produce dist/ ``` ## Directory Structure ``` frontend/ ├── index.html Vite entry HTML ├── vite.config.ts Vite + Vitest config; dev proxy ├── tsconfig.json References tsconfig.app.json + tsconfig.node.json ├── tsconfig.app.json App source TS config (strict, react-jsx) ├── tsconfig.node.json Vite config TS config ├── eslint.config.js Flat ESLint config (React + TypeScript rules) ├── package.json Dependencies + npm scripts ├── package-lock.json Lockfile (committed; CI uses npm ci) └── src/ ├── main.tsx Entry point; mounts into #root ├── App.tsx Provider stack + route tree (MantineProvider → QueryClient → Router → SessionProvider) ├── vite-env.d.ts /// for CSS imports ├── test-setup.ts Vitest global setup (@testing-library/jest-dom) ├── api/ │ ├── schema.d.ts AUTO-GENERATED from openapi/openapi.json (committed) │ ├── client.ts openapi-fetch client + CSRF/cookie/401 middleware │ └── csrf.ts Module-level CSRF token holder (setCsrfToken / getCsrfToken) ├── auth/ │ ├── SessionProvider.tsx TanStack Query against GET /api/session; exposes useSession() │ └── ProtectedRoute.tsx Redirects to /login when unauthenticated └── pages/ ├── LoginPage.tsx Placeholder → T07 builds the real form ├── HomePage.tsx Placeholder → T09 builds the map/heatmap view └── ConfigPage.tsx Placeholder → T08 builds the config editor ``` ## Dev Proxy (local development) `npm run dev` starts Vite on port 5173. The Vite config proxies API/auth paths to the FastAPI backend running on port 8000: | Proxied path | Backend URL | |---|---| | `/api/*` | `http://localhost:8000` | | `/login` | `http://localhost:8000` | | `/logout` | `http://localhost:8000` | | `/static/*` | `http://localhost:8000` | | `/docs` | `http://localhost:8000` | | `/openapi.json` | `http://localhost:8000` | To develop locally: 1. Start the backend: `uvicorn app.main:app --reload --host 0.0.0.0 --port 8000` 2. Start the frontend: `cd frontend && npm run dev` 3. Open `http://localhost:5173` — the app proxies all API calls to the backend. Since the dev server proxies the session cookie path, auth flows work exactly as they would in the deployed (same-origin) setup. ## Adding a New Page + Typed Query This is the pattern every task T07–T10 follows to wire up a real page: ### 1. Run codegen (if the OpenAPI contract changed) ```bash npm run codegen ``` The generated `src/api/schema.d.ts` is committed to the repo. CI enforces that the file is in sync with `openapi/openapi.json` via: ```bash npm run codegen && git diff --exit-code frontend/src/api/schema.d.ts ``` ### 2. Import the typed client ```typescript // src/pages/SomePage.tsx import apiClient from '../api/client' ``` ### 3. Write a typed TanStack Query ```typescript import { useQuery } from '@tanstack/react-query' import apiClient from '../api/client' function usePooRecords(limit = 100) { return useQuery({ queryKey: ['poo', { limit }], queryFn: async () => { const res = await apiClient.GET('/api/poo', { params: { query: { limit } } }) // res.data is typed as PooResponse | undefined // On non-2xx the middleware throws ApiError; TanStack Query catches it. return res.data }, }) } ``` The `params.query` and `params.path` objects are fully typed from `schema.d.ts`. TypeScript will error if you pass unknown query params or mistype a path param. ### 4. Write a typed mutation (write request) ```typescript import { useMutation, useQueryClient } from '@tanstack/react-query' import apiClient from '../api/client' function useDeletePoo() { const qc = useQueryClient() return useMutation({ mutationFn: (timestamp: string) => apiClient.DELETE('/api/poo/{timestamp}', { params: { path: { timestamp } }, }), onSuccess: () => qc.invalidateQueries({ queryKey: ['poo'] }), }) } ``` The middleware (`src/api/client.ts`) automatically injects the `X-CSRF-Token` header on all non-GET/HEAD requests (sourced from `getCsrfToken()`). You do not need to handle CSRF manually in page code. ### 5. Add the route in App.tsx ```typescript // App.tsx import { SomePage } from './pages/SomePage' // Inside : } /> // or, if protected: } > } /> ``` ## OpenAPI codegen + CI sync rule `src/api/schema.d.ts` is committed to the repository (not gitignored). **Rule**: whenever `openapi/openapi.json` changes (any backend task that modifies a route or schema), CI must run: ```bash cd frontend && npm run codegen git diff --exit-code frontend/src/api/schema.d.ts ``` If the file has changed but the new version was not committed, CI fails. To update manually after a backend change: ```bash cd frontend npm run codegen git add src/api/schema.d.ts git commit -m "M2-Txx: update generated OpenAPI types" ``` ## Production Build The production build (`npm run build`) writes static files to `frontend/dist/`. In the deployed setup (M2-T11 onwards), FastAPI serves `dist/` as a static directory and falls back to `dist/index.html` for all non-`/api` paths, enabling client-side routing with deep links. The multi-stage Dockerfile (M2-T12) builds the frontend in a Node container and copies only `dist/` into the Python image — the production image does not contain Node or npm.