mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 15:02:04 +00:00
Rename every routed Insights sub-page slug from German to English (`/insights/blutdruck` -> `/insights/blood-pressure`, `gewicht` -> `weight`, `medikamente` -> `medications`, and the full v1.7.0 metric tail). UI copy stays localised through `t()` keys; only the internal slugs change. The slug registry in `src/lib/insights/sub-page-metric.ts` is the single source of truth, so the tab strip, target summary, and daily-briefing routing targets follow it. `next.config.ts` `redirects()` emits a permanent redirect from every legacy German slug to its English target, including a `:path*` variant so nested routes survive. Bookmarks and cached PWA navigation keep resolving.
119 lines
4.0 KiB
TypeScript
119 lines
4.0 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
createContext,
|
|
useCallback,
|
|
useContext,
|
|
useMemo,
|
|
useState,
|
|
type ReactNode,
|
|
} from "react";
|
|
|
|
import type { CoachScopeSource } from "@/lib/ai/coach/types";
|
|
|
|
/**
|
|
* v1.4.27 R3d MB4 — Coach launch context.
|
|
*
|
|
* Until v1.4.27 the Coach drawer was mounted inline on
|
|
* `/insights/page.tsx` body, which meant navigating to a routed
|
|
* sub-page (`/insights/blood-pressure`, etc.) unmounted the drawer and
|
|
* removed every entry point to the Coach. The MA3 audit + Decision F
|
|
* promoted the drawer up to `/insights/layout.tsx` so every routed
|
|
* Insights surface can launch it.
|
|
*
|
|
* Shape:
|
|
* - `<CoachLaunchProvider>` owns the drawer's open / prefill state
|
|
* and renders the children beneath the launch surface.
|
|
* - `useCoachLaunch()` returns `{ open, askCoach, setOpen, prefill }`
|
|
* so any descendant can open the drawer with an optional prefill
|
|
* string (the hero-strip suggested-prompt chips feed prefills;
|
|
* the sticky `<CoachLaunchButton>` opens without one).
|
|
*
|
|
* The provider is intentionally tiny — the actual drawer mount sits
|
|
* next to it in the layout, reading the same state via the hook.
|
|
*/
|
|
export interface CoachLaunchScope {
|
|
/**
|
|
* Optional metric the user is looking at when they open the Coach.
|
|
* Reserved for v1.4.28 so the drawer can pre-narrow the source rail
|
|
* to the active metric; ignored in v1.4.27.
|
|
*
|
|
* v1.4.28 R3c (BK-MED-2 / BK-F-M4) — narrowed from `string` to the
|
|
* `CoachScopeSource` union so call sites cannot drift to a free-form
|
|
* label that the sources rail then silently ignores. Adding a new
|
|
* Apple-Health source to the rail (the union already covers the
|
|
* v1.4.23 additions) automatically widens the allowed values here.
|
|
*/
|
|
metric?: CoachScopeSource;
|
|
}
|
|
|
|
interface CoachLaunchValue {
|
|
/** Whether the Coach drawer is currently open. */
|
|
open: boolean;
|
|
/** Current prefill string (or null when the next open should start blank). */
|
|
prefill: string | null;
|
|
/** Open the drawer with an optional prefill + scope hint. */
|
|
askCoach: (prefill?: string | null, scope?: CoachLaunchScope) => void;
|
|
/**
|
|
* Direct setter for the open state — the drawer's `onOpenChange`
|
|
* consumes it on close. Kept (rather than collapsed into a
|
|
* `closeCoach()` helper) because `<LayoutCoachMount>` forwards the
|
|
* raw setter to the Sheet's controlled-state contract, which expects
|
|
* a boolean callback. v1.4.28 R3c (BK-F-M4) audit: exactly one
|
|
* consumer, no drift.
|
|
*/
|
|
setOpen: (next: boolean) => void;
|
|
}
|
|
|
|
const CoachLaunchContext = createContext<CoachLaunchValue | null>(null);
|
|
|
|
export interface CoachLaunchProviderProps {
|
|
children: ReactNode;
|
|
}
|
|
|
|
export function CoachLaunchProvider({ children }: CoachLaunchProviderProps) {
|
|
const [open, setOpen] = useState<boolean>(false);
|
|
const [prefill, setPrefill] = useState<string | null>(null);
|
|
|
|
const askCoach = useCallback(
|
|
(nextPrefill?: string | null, scope?: CoachLaunchScope) => {
|
|
// `scope` is currently reserved for v1.4.28 (metric-narrow on the
|
|
// sources rail). Accept the parameter today so the call sites
|
|
// don't need to change shape when the rail starts honouring it.
|
|
void scope;
|
|
setPrefill(nextPrefill ?? null);
|
|
setOpen(true);
|
|
},
|
|
[],
|
|
);
|
|
|
|
const handleSetOpen = useCallback((next: boolean) => {
|
|
setOpen(next);
|
|
if (!next) {
|
|
// Drop the prefill on close so the next open starts blank.
|
|
setPrefill(null);
|
|
}
|
|
}, []);
|
|
|
|
const value = useMemo<CoachLaunchValue>(
|
|
() => ({ open, prefill, askCoach, setOpen: handleSetOpen }),
|
|
[open, prefill, askCoach, handleSetOpen],
|
|
);
|
|
|
|
return (
|
|
<CoachLaunchContext.Provider value={value}>
|
|
{children}
|
|
</CoachLaunchContext.Provider>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Read the Coach launch context. Returns `null` when called outside a
|
|
* `<CoachLaunchProvider>` so consumer components can degrade gracefully
|
|
* (e.g. the hero strip's "Ask the coach" action stays disabled until
|
|
* the provider mounts).
|
|
*/
|
|
export function useCoachLaunch(): CoachLaunchValue | null {
|
|
return useContext(CoachLaunchContext);
|
|
}
|