mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 14:40:52 +00:00
chore(release): v1.28.44 — loose ends: broken link, API contract, module guards
From the campaign's dead-code + code-health audits (grep-verified; the custom-metrics query-key finding was verified-false and excluded): - sleep-overview empty-state CTA linked to /settings/devices, not a valid settings slug (dynamicParams=false → 404). Repointed to /settings/integrations (where every device OAuth callback already lands). - six live, iOS-consumed routes were absent from docs/api/openapi.yaml — auth/check-user, insights/cards, integrations/healthkit (GET+PATCH), measurement-categories, medications/intake/bulk, mood-entries/bulk — registered in the Zod OpenAPI registry mirroring each handler's real shape. - mood/labs/medications module pages lacked the redirect-when-off self-guard their siblings have; added, mirroring cycle/achievements exactly. No migration. Verifier REJECTED the custom-metrics bare-key finding (they are invalidateQueries prefix filters, not colliding useQuery keys).
This commit is contained in:
1 parent
8b6024f0d7
commit
cd0fbddd1d
17 files changed
+1433
-16
No files matched your search
+13
-3
@@ -49,6 +49,13 @@ export default function LabsPage() {
|
||||
const [scanOpen, setScanOpen] = useState(false);
|
||||
const ocrCapability = useOcrCapability(isAuthenticated);
|
||||
|
||||
// Gated on the resolved `modules.labs` flag from `GET /api/auth/me` (the
|
||||
// per-user toggle AND the operator server-wide kill-switch). Default-on: an
|
||||
// absent key reads as enabled, so a direct URL hit only bounces on an
|
||||
// explicit `false`. Every `/api/labs/*` route also enforces the gate
|
||||
// server-side, so this is a UX redirect, not the security boundary.
|
||||
const enabled = user?.modules?.labs !== false;
|
||||
|
||||
const refreshVisible = useCallback(
|
||||
() => queryClient.invalidateQueries({ type: "active" }),
|
||||
[queryClient],
|
||||
@@ -59,12 +66,15 @@ export default function LabsPage() {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading && !isAuthenticated) {
|
||||
if (isLoading) return;
|
||||
if (!isAuthenticated) {
|
||||
router.push("/auth/login");
|
||||
} else if (!enabled) {
|
||||
router.push("/");
|
||||
}
|
||||
}, [isLoading, isAuthenticated, router]);
|
||||
}, [isLoading, isAuthenticated, enabled, router]);
|
||||
|
||||
if (!mounted || isLoading) {
|
||||
if (!mounted || isLoading || (isAuthenticated && !enabled)) {
|
||||
return (
|
||||
<div className="flex h-64 items-center justify-center">
|
||||
<Loader2 className="text-primary h-8 w-8 animate-spin motion-reduce:animate-none" />
|
||||
|
||||
@@ -153,6 +153,12 @@ function MedicationCardSkeleton() {
|
||||
export default function MedicationsPage() {
|
||||
const { user, isAuthenticated, isLoading: authLoading } = useAuth();
|
||||
const { t } = useTranslations();
|
||||
// v1.18.1 (D3) — medications is an opt-out module. When the account has it
|
||||
// turned off the whole page disappears (the nav entry is hidden by the same
|
||||
// gate) and the list query never fires (the API would 403 anyway).
|
||||
// Default-on: an absent key reads as enabled, so the page only hides on an
|
||||
// explicit `false`.
|
||||
const medicationsEnabled = user?.modules?.medications !== false;
|
||||
const router = useRouter();
|
||||
const queryClient = useQueryClient();
|
||||
const searchParams = useSearchParams();
|
||||
@@ -195,7 +201,7 @@ export default function MedicationsPage() {
|
||||
queryFn: async () => {
|
||||
return apiGet<Medication[]>("/api/medications");
|
||||
},
|
||||
enabled: isAuthenticated,
|
||||
enabled: isAuthenticated && medicationsEnabled,
|
||||
// v1.16.12 (#316) — refetch on every mount, even inside the global
|
||||
// staleTime window. App Router unmounts this page on navigation away
|
||||
// and remounts it on return; without this, a return within 5 min
|
||||
@@ -277,6 +283,13 @@ export default function MedicationsPage() {
|
||||
);
|
||||
}
|
||||
|
||||
// Module off ⇒ the surface disappears entirely. The nav entry is hidden by
|
||||
// the same gate, so a direct hit renders nothing rather than an orphaned
|
||||
// shell.
|
||||
if (isAuthenticated && !medicationsEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
|
||||
+14
-4
@@ -19,7 +19,7 @@ import { useEffect } from "react";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
|
||||
export default function MoodPage() {
|
||||
const { isAuthenticated, isLoading } = useAuth();
|
||||
const { user, isAuthenticated, isLoading } = useAuth();
|
||||
const mounted = useMounted();
|
||||
const router = useRouter();
|
||||
const queryClient = useQueryClient();
|
||||
@@ -30,6 +30,13 @@ export default function MoodPage() {
|
||||
const [footerEl, setFooterEl] = useState<HTMLDivElement | null>(null);
|
||||
const { t } = useTranslations();
|
||||
|
||||
// Gated on the resolved `modules.mood` flag from `GET /api/auth/me` (the
|
||||
// per-user toggle AND the operator server-wide kill-switch). Default-on: an
|
||||
// absent key reads as enabled, so a direct URL hit only bounces on an
|
||||
// explicit `false`. Every `/api/mood-entries/*` route also enforces the gate
|
||||
// server-side, so this is a UX redirect, not the security boundary.
|
||||
const enabled = user?.modules?.mood !== false;
|
||||
|
||||
// v1.16.4 — PWA pull-to-refresh: a top-anchored touch pull refetches
|
||||
// whatever this page currently has mounted (`type: "active"` scopes the
|
||||
// invalidation to visible queries). Suspended while the add-sheet is
|
||||
@@ -44,17 +51,20 @@ export default function MoodPage() {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading && !isAuthenticated) {
|
||||
if (isLoading) return;
|
||||
if (!isAuthenticated) {
|
||||
router.push("/auth/login");
|
||||
} else if (!enabled) {
|
||||
router.push("/");
|
||||
}
|
||||
}, [isLoading, isAuthenticated, router]);
|
||||
}, [isLoading, isAuthenticated, enabled, router]);
|
||||
|
||||
// `!mounted` keeps the hydration render identical to the SSR HTML: the
|
||||
// auth query is fired by the early-hydrating shell and can settle before
|
||||
// this page boundary hydrates, so `isLoading` alone flipped the branch
|
||||
// and React logged hydration error #418 — same fix as the measurements
|
||||
// page; see `useMounted`.
|
||||
if (!mounted || isLoading) {
|
||||
if (!mounted || isLoading || (isAuthenticated && !enabled)) {
|
||||
return <PageAuthGate label={t("common.loading")} />;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user