mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 22:32:04 +00:00
feat(dashboard): promote a Today hero above the tile strip
Surface the day's read at the top of the dashboard: a Today hero that renders the daily digest — the health score with an honest provisional/final and sleep-pending face, the briefing lead with a read-the-full-briefing affordance, and the worth-a-look rail of priority cards. The dense tile grid and chart rows stay exactly where they are, one scroll below, so muscle memory survives and no new nav destination is added. The hero reads one cached digest cell (GET /api/daily/digest) through the centralised query-key factory and unwraps the envelope; it recomputes nothing and never warms a provider on mount. It is gated on the insights module and self-gates its empty state: a genuinely empty account renders nothing rather than an alarming card, while an account with a score but nothing notable gets a calm all-clear line in the rail's place. A shimmer skeleton holds the footprint while the digest loads. Priority-card actions wire to their existing destinations by construction (log dose, reconnect a sync, view check-ups). New copy lands in all six locales.
This commit is contained in:
1 parent
ba6c0990bf
commit
168c1c85d7
14 files changed
+581
No files matched your search
@@ -0,0 +1,50 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* S2 — client cell for the unified daily digest (`GET /api/daily/digest`).
|
||||
*
|
||||
* The Today hero reads the ALREADY-CACHED `DailyDigest` DTO S1 assembled
|
||||
* server-side. This hook is a plain GET of that cached route — it never
|
||||
* warms a provider, never triggers a fresh AI call (the whole daily-value
|
||||
* system's warm-on-mount ban extends here); the route composes from the
|
||||
* nightly-cached briefing + dashboard snapshot ingredients.
|
||||
*
|
||||
* Gating: `enabled` is caller-supplied so the page can fold in the
|
||||
* `insights` module flag (the digest is the AI-narrative daily layer;
|
||||
* the route returns 403 `module.disabled` when insights is off) and the
|
||||
* auth state. Freshness mirrors the dashboard snapshot cadence — a warm
|
||||
* return-to-dashboard within the `staleTime` is a free cache hit, and a
|
||||
* background sync surfaces on focus/poll — so the two above-the-fold
|
||||
* cells refresh in lockstep without a second request storm.
|
||||
*
|
||||
* The read unwraps the `{ data, error }` envelope through `apiGet`
|
||||
* (`(await res.json()).data`), and the queryKey is the centralised
|
||||
* factory entry `queryKeys.dailyDigest()`.
|
||||
*/
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { apiGet } from "@/lib/api/api-fetch";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import { DASHBOARD_REFETCH_INTERVAL_MS } from "@/lib/queries/refetch-interval";
|
||||
import { retryOnceOnTransientError } from "@/lib/queries/retry-transient";
|
||||
import type { DailyDigest } from "@/lib/daily/digest";
|
||||
|
||||
export function useDailyDigest(enabled = true) {
|
||||
return useQuery({
|
||||
queryKey: queryKeys.dailyDigest(),
|
||||
queryFn: () => apiGet<DailyDigest>("/api/daily/digest"),
|
||||
enabled,
|
||||
// Match the dashboard snapshot's freshness so the hero and the tile
|
||||
// strip below it share one refresh cadence: a warm remount inside the
|
||||
// minute is free; a background sync surfaces on focus; an open tab
|
||||
// polls in the foreground only.
|
||||
staleTime: 60_000,
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: true,
|
||||
refetchInterval: DASHBOARD_REFETCH_INTERVAL_MS,
|
||||
refetchIntervalInBackground: false,
|
||||
// One retry on transient network / 5xx (never 401/403) so a single
|
||||
// blip doesn't collapse the hero to its empty degrade.
|
||||
retry: retryOnceOnTransientError,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Query keys — the unified daily-value system (S2 Today surface).
|
||||
* Part of the centralized factory; aggregated in `./index.ts`.
|
||||
*
|
||||
* The Today hero reads ONE cell against `GET /api/daily/digest` (the
|
||||
* `DailyDigest` DTO S1 shipped). A single flat key: the digest is a
|
||||
* per-user daily read with no discriminator, so `["daily","digest"]`
|
||||
* is the whole surface. Kept in the factory so a future daily-data
|
||||
* mutation can invalidate it by prefix without a bare-array literal
|
||||
* (the `healthlog/queryKey-factory` rule bans those).
|
||||
*/
|
||||
export const dailyKeys = {
|
||||
dailyDigest: () => ["daily", "digest"] as const,
|
||||
};
|
||||
@@ -21,6 +21,7 @@ import { coachKeys } from "./coach";
|
||||
import { familyHistoryKeys } from "./family-history";
|
||||
import { customMetrics } from "./custom-metrics";
|
||||
import { cycleKeys } from "./cycle";
|
||||
import { dailyKeys } from "./daily";
|
||||
import { dashboardKeys } from "./dashboard";
|
||||
import { environmentKeys } from "./environment";
|
||||
import { documentKeys } from "./documents";
|
||||
@@ -41,6 +42,7 @@ export const queryKeys = {
|
||||
...authKeys,
|
||||
...measurementKeys,
|
||||
...moodKeys,
|
||||
...dailyKeys,
|
||||
...dashboardKeys,
|
||||
...insightsKeys,
|
||||
...medicationKeys,
|
||||
|
||||
Reference in new issue
Block a user