40227-vm/frontend/docs/zone-checkin-integration.md
2026-06-12 10:56:13 +02:00

2.8 KiB

Daily Zone Check-in Integration

Purpose

Campus staff log a daily self-regulation "Emotional Zone" (blue/green/yellow/red). The same state drives three surfaces: the dashboard check-in card, the /zones-of-regulation page (reminder banner + card), and a notification-dropdown nudge when an eligible user has not checked in today.

Backend contract

/api/zone_checkins (requires ZONE_CHECKIN — the four campus staff roles). The client never computes the date; "today" is the campus-local date computed server-side from campuses.timezone.

  • GET /today{ date, zone, isCheckedInToday }
  • POST / { data: { zone } } → record today's zone (upsert)
  • DELETE /today → clear today's zone
  • GET /?from=&to= → history { rows: [{ date, zone }], count }

Frontend Structure

  • API/types: shared/api/zoneCheckins.ts, shared/types/zoneCheckins.ts
  • Business: business/zone-checkin/hooks.ts (useTodayZoneCheckIn, useZoneCheckInHistory), business/zone-checkin/selectors.ts (canZoneCheckIn, shouldNudgeZoneCheckIn)
  • Components: components/zone-checkin/ZoneCheckInCard.tsx (shared card), ZoneCheckInReminder.tsx (banner), ZoneCheckInSection.tsx (page section)

Behavior

  • Eligibility/nudge gating is role-based (canZoneCheckIn — the four campus staff roles), mirroring the backend grant. The dashboard card and the zones-page section render only for eligible roles; the nudge (red "Not checked in" badge, reminder banner, and notification) shows when an eligible user hasn't checked in today.
  • Dashboard: the card is wired through useDashboardPage (which exposes showZoneCheckIn + needsZoneCheckIn) with an optimistic shell value for snappy selection.
  • Zones page: ZoneCheckInSection is self-contained (useTodayZoneCheckIn) and renders above the regulation content.
  • Notifications: business/top-bar derives a single unread notification from shouldNudgeZoneCheckIn (buildTopBarNotifications) — there is no backend notifications store. The notification carries an href (APP_ROUTE_PATHS.zones); clicking it navigates to /zones-of-regulation (a react-router Link) and closes the dropdown.
  • The useTodayZoneCheckIn error surfaces only save/clear failures; the today-load query can 403 for an ineligible caller and must not render as an error in the widget. React Query dedupes the /today fetch across all three surfaces.

Tests

  • business/zone-checkin/selectors.test.ts (eligibility + nudge), business/top-bar/selectors.test.ts (notification builder + zones href).
  • Seeded e2e: frontend/tests/e2e/zone-checkins.seeded.e2e.ts (record / read-back / clear today, invalid-zone rejection, external-role lockout).

Verification

  • npm run typecheck, npm run lint, npm run test pass.