# 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 <App> into #root
    ├── App.tsx                 Provider stack + route tree (MantineProvider → QueryClient → Router → SessionProvider)
    ├── vite-env.d.ts           /// <reference types="vite/client" /> 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 <Routes>:
<Route path="/some-path" element={<SomePage />} />
// or, if protected:
<Route
  element={
    <ProtectedRoute>
      <AppLayout />
    </ProtectedRoute>
  }
>
  <Route path="/some-path" element={<SomePage />} />
</Route>
```

## 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.
