mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
The clock format followed the UI language with no way out — an English interface forced AM/PM everywhere. A new profile preference (automatic, 24-hour, 12-hour) feeds every formatter through the shared locale context, persists on the user row and rides the profile endpoints. The insight pill list in settings gains the same eye toggle the overview tiles have, so ordering and visibility live in one place; the page-side 'manage detail pages' block folds into a link there. Includes the locale bundles for both features and the regenerated API contract.
72 lines
2.5 KiB
TypeScript
72 lines
2.5 KiB
TypeScript
/**
|
|
* Client-side mirror of the per-user hour-cycle preference.
|
|
*
|
|
* The server-authoritative value lives on the user row (`users.time_format`,
|
|
* surfaced by `/api/auth/me` and editable via the profile PATCH). The mirror
|
|
* follows the `healthlog-locale` pattern: `fetchMe` writes the resolved value
|
|
* into localStorage so that
|
|
*
|
|
* 1. `useFormatters()` can read it through `useSyncExternalStore` without
|
|
* requiring a QueryClient in the tree, and
|
|
* 2. the legacy helpers in `src/lib/format.ts` (plain functions, no React
|
|
* context) render the same hour cycle as hook-based call sites.
|
|
*
|
|
* SSR always resolves AUTO (`window` is undefined); the same caveat as the
|
|
* locale formatters applies — current call sites render their formatted
|
|
* strings post-fetch, so there is no hydration-mismatch path today.
|
|
*/
|
|
|
|
import type { TimeFormatPreference } from "./format-locale";
|
|
|
|
const STORAGE_KEY = "healthlog-time-format";
|
|
const CHANGE_EVENT = "healthlog:time-format-change";
|
|
|
|
export const TIME_FORMAT_PREFERENCES = ["AUTO", "H12", "H24"] as const;
|
|
|
|
export function isTimeFormatPreference(
|
|
value: unknown,
|
|
): value is TimeFormatPreference {
|
|
return (
|
|
typeof value === "string" &&
|
|
(TIME_FORMAT_PREFERENCES as readonly string[]).includes(value)
|
|
);
|
|
}
|
|
|
|
/** Best-effort read of the mirrored preference. AUTO on SSR / no mirror. */
|
|
export function readStoredTimeFormat(): TimeFormatPreference {
|
|
if (typeof window === "undefined") return "AUTO";
|
|
try {
|
|
const stored = window.localStorage?.getItem(STORAGE_KEY);
|
|
return isTimeFormatPreference(stored) ? stored : "AUTO";
|
|
} catch {
|
|
return "AUTO";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Persist the mirror and notify same-tab subscribers. Cross-tab updates ride
|
|
* the browser's native `storage` event.
|
|
*/
|
|
export function storeTimeFormat(value: TimeFormatPreference): void {
|
|
if (typeof window === "undefined") return;
|
|
try {
|
|
const previous = window.localStorage?.getItem(STORAGE_KEY);
|
|
if (previous === value) return;
|
|
window.localStorage?.setItem(STORAGE_KEY, value);
|
|
} catch {
|
|
return;
|
|
}
|
|
window.dispatchEvent(new Event(CHANGE_EVENT));
|
|
}
|
|
|
|
/** `useSyncExternalStore`-shaped subscription for the mirror. */
|
|
export function subscribeTimeFormat(onChange: () => void): () => void {
|
|
if (typeof window === "undefined") return () => {};
|
|
window.addEventListener(CHANGE_EVENT, onChange);
|
|
window.addEventListener("storage", onChange);
|
|
return () => {
|
|
window.removeEventListener(CHANGE_EVENT, onChange);
|
|
window.removeEventListener("storage", onChange);
|
|
};
|
|
}
|