Files
spa/.planning
julian 1ba0648cf3 feat: task 1.5 directus auth client + zustand auth store
Six files under src/auth/:
- client.ts: createDirectus<Schema>().with(authentication('cookie', ...))
  .with(rest(...)). Lazy singleton via initDirectusClient(url) +
  getDirectus() + useDirectus() (React-side helper). DirectusUser type;
  empty Schema (grows in Phase 2+).
- store.ts: Zustand store with discriminated AuthState
  (unknown / anonymous / authenticating / authenticated). Actions:
  initialize, login, logout, setUser. humanizeAuthError maps Directus
  error codes (INVALID_CREDENTIALS / INVALID_OTP / USER_SUSPENDED) to
  user-facing strings.
- guard.ts: useRequireAuth (redirect to /login on anonymous) and
  useRequireRole (bounce to / on role mismatch). Uses TanStack Router's
  useNavigate; effective once 1.7 wires the router.
- bootstrap.tsx: <AuthBootstrap> component runs initDirectusClient +
  initialize() once after the runtime config is loaded.
- index.ts: barrel re-exports.

main.tsx wraps App in <RuntimeConfigProvider><AuthBootstrap>...
App.tsx shows the auth status as a smoke indicator before 1.6/1.7.

Deviations:
1. Split getDirectus into initDirectusClient(url) + getDirectus()
   because the runtime config is a React context (per 1.4), not a
   Zustand store. <AuthBootstrap> bridges from React land to the non-
   React getter.
2. Added <AuthBootstrap> as a discrete component rather than putting
   the bootstrap effect in App.tsx — App.tsx gets replaced by the
   router in 1.7, but bootstrap needs to keep running.
2026-05-02 18:43:51 +02:00
..