feat: task 1.6 login page

src/ui/pages/login.tsx — LoginPage component:
- Centred card on muted background.
- shadcn Form + FormField with react-hook-form + zodResolver.
  FormMessage renders field-level zod errors automatically.
- Email + password with proper autocomplete attrs for password managers.
  autoFocus on email.
- Submit calls useAuthStore.getState().login(); errors render in a
  destructive Alert above the form.
- In-flight via the auth store's 'authenticating' status (cross-tab safe).
- onAuthenticated callback fires on store transition to authenticated;
  1.7 wires this to a router redirect.

src/App.tsx branches on auth status:
- unknown / authenticating -> "Signing you in..." placeholder
  (avoids flashing the login page on hard refresh while the boot probe
  runs)
- anonymous -> <LoginPage />
- authenticated -> home placeholder (1.7 replaces with router shell)

Deviation: skipped src/routes/login.tsx — requires the router plugin
to have generated routeTree.gen.ts, which is 1.7's territory. The page
works standalone via App.tsx's status branch.

Search-param redirect (?redirect=...) also deferred to 1.7 — no router
yet to expose useSearch. onAuthenticated callback is the seam.
This commit is contained in:
2026-05-02 18:02:43 +02:00
parent a65ad428e6
commit ef18222edf
4 changed files with 162 additions and 5 deletions
+24 -1
View File
@@ -160,4 +160,27 @@ useEffect(() => {
## Done
(Filled in when the task lands.)
`src/ui/pages/login.tsx``LoginPage` component:
- Centred card on muted background, max-width sm.
- shadcn `Form` + `FormField` primitives with react-hook-form + zodResolver. `FormMessage` renders field-level zod errors automatically.
- Email + password inputs with `autoComplete="username"` / `current-password"` so password managers populate correctly. `autoFocus` on email.
- Submit handler calls `useAuthStore.getState().login()`; on `{ ok: false }` populates a top-of-card destructive `Alert` with the humanised error.
- In-flight indication via the auth store's `'authenticating'` status (cross-tab safe — no separate local state needed).
- `onAuthenticated` callback prop fires when the store transitions to `'authenticated'` (covers both successful login and concurrent-tab login). The actual redirect lands in 1.7 alongside the router.
`src/App.tsx` updated to branch on auth status:
- `'unknown'` / `'authenticating'` → "Signing you in…" placeholder (avoids flashing the login page on hard refresh while the boot probe runs).
- `'anonymous'``<LoginPage />`.
- `'authenticated'` → home placeholder (1.7 replaces this branch with the TanStack Router shell).
**Deviation from spec:**
The spec called for `src/routes/login.tsx` (TanStack Router file-based route). Skipped that here — the route file requires the router plugin to have generated `routeTree.gen.ts`, which is 1.7's territory. For 1.6 the page works standalone via App.tsx's status branch; 1.7 will carve out `routes/login.tsx` and rewire App.tsx to render the router instead.
Search-param redirect (`?redirect=...`) also deferred to 1.7 — there's no router yet to expose `useSearch`. The `onAuthenticated` callback is the seam that 1.7 will use to navigate to the redirect target.
**Smoke check:** `pnpm typecheck`, `pnpm lint`, `pnpm format:check`, `pnpm build` all green. Bundle: 330KB / 102KB gzipped (up from 261KB / 80KB; react-hook-form + radix-ui from the shadcn `Form` primitive are now in the runtime path). Browser-side login against a stage Directus is the operational gate — pending until 1.10 deploys the SPA there.
Landed in `PENDING_SHA`.