mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
fix(charts): cut days in the account's zone when a mount passes none
Several chart mounts (the medications page, the coach chat, the recovery section) pass no zone, and the chart then fell back to a fixed Europe/Berlin, so those charts cut and labelled days in Berlin for every user whatever their profile said. The chart now falls back to the signed-in account's zone, and only before that has loaded to the shared DEFAULT_TIMEZONE the server's own resolver ends on. /api/auth/me reports the zone the server resolves: the stored one, or the instance default when the stored one is unusable, so the client cuts days where the server does. Refs #1026
This commit is contained in:
1 parent
3170a622d1
commit
16ef75e729
4 files changed
+104
-6
No files matched your search
@@ -49,6 +49,10 @@ import { syncMfaEnrollCookie } from "@/lib/auth/mfa-enrollment";
|
||||
import { buildAvatarUrl } from "@/lib/avatar";
|
||||
import { decrypt } from "@/lib/crypto";
|
||||
import { prisma } from "@/lib/db";
|
||||
import {
|
||||
isValidTimezone,
|
||||
resolveServerDefaultTimezone,
|
||||
} from "@/lib/tz/resolver";
|
||||
import { isCycleEnabled } from "@/lib/cycle/gate";
|
||||
import {
|
||||
resolveModuleMap,
|
||||
@@ -238,7 +242,12 @@ export const GET = apiHandler(async () => {
|
||||
heightCm: user.heightCm,
|
||||
dateOfBirth: user.dateOfBirth,
|
||||
gender: user.gender,
|
||||
timezone: user.timezone,
|
||||
// The zone the server cuts this account's days in: the stored zone, or
|
||||
// the instance default when the stored one is unusable. Clients bucket
|
||||
// by this value, so it must be the resolved one, not the raw column.
|
||||
timezone: isValidTimezone(user.timezone)
|
||||
? user.timezone
|
||||
: await resolveServerDefaultTimezone(),
|
||||
onboardingCompletedAt: user.onboardingCompletedAt,
|
||||
onboardingTourCompleted: user.onboardingTourCompleted,
|
||||
// v1.39 (C1) — the needs-based setup flow, published additively:
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
|
||||
/**
|
||||
* A chart mount that passes no `userTimezone` must still cut days in the
|
||||
* signed-in user's zone. Several mounts (the medications page, the coach
|
||||
* chat, the recovery section) pass none, and the component used to fall
|
||||
* back to a fixed Europe/Berlin, so a user in Asia/Kolkata saw those charts
|
||||
* bucketed and labelled in Berlin days while every other chart used their
|
||||
* own. The zone now comes from the account, which the server resolves.
|
||||
*/
|
||||
|
||||
let lastKey: unknown[] | undefined;
|
||||
|
||||
vi.mock("@tanstack/react-query", () => ({
|
||||
keepPreviousData: (previous: unknown) => previous,
|
||||
useQuery: ({ queryKey }: { queryKey: unknown[] }) => {
|
||||
lastKey = queryKey;
|
||||
return { data: [], isLoading: false };
|
||||
},
|
||||
useQueryClient: () => ({
|
||||
cancelQueries: () => Promise.resolve(),
|
||||
getQueryData: () => undefined,
|
||||
setQueryData: () => undefined,
|
||||
invalidateQueries: () => Promise.resolve(),
|
||||
}),
|
||||
useMutation: () => ({ mutate: () => undefined, isPending: false }),
|
||||
}));
|
||||
|
||||
vi.mock("@/hooks/use-auth", () => ({
|
||||
useAuth: () => ({
|
||||
isAuthenticated: true,
|
||||
user: { id: "u1", timezone: "Asia/Kolkata" },
|
||||
isLoading: false,
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("<HealthChart> timezone", () => {
|
||||
it("uses the account's zone when the mount passes none", async () => {
|
||||
const { I18nProvider } = await import("@/lib/i18n/context");
|
||||
const { HealthChart } = await import("../health-chart");
|
||||
|
||||
renderToStaticMarkup(
|
||||
<I18nProvider initialLocale="en">
|
||||
<HealthChart types={["ACTIVITY_STEPS"]} title="Steps" unit="" />
|
||||
</I18nProvider>,
|
||||
);
|
||||
|
||||
expect(lastKey).toBeDefined();
|
||||
expect(lastKey).toContain("Asia/Kolkata");
|
||||
expect(lastKey).not.toContain("Europe/Berlin");
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { chartSeriesParams } from "@/components/charts/chart-series-request";
|
||||
import { DEFAULT_TIMEZONE } from "@/lib/tz/format";
|
||||
import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { useRecordCapabilities } from "@/hooks/use-record-capabilities";
|
||||
@@ -214,10 +215,11 @@ interface HealthChartProps {
|
||||
/**
|
||||
* v1.4.25 W7b — per-user display timezone. When passed (mount sites
|
||||
* thread `useAuth().user?.timezone`), x-axis tick labels and the
|
||||
* per-day bucket keys both render in the user's zone instead of the
|
||||
* legacy Europe/Berlin pin. Defaults to "Europe/Berlin" so older
|
||||
* callers that haven't yet adopted the prop keep their previous
|
||||
* behaviour bit-for-bit.
|
||||
* per-day bucket keys both render in the user's zone. When a mount
|
||||
* passes none, the chart uses the signed-in account's zone (which
|
||||
* `/api/auth/me` reports already resolved against the server default),
|
||||
* and only before that has loaded the shared `DEFAULT_TIMEZONE` the
|
||||
* server itself falls back to.
|
||||
*/
|
||||
userTimezone?: string;
|
||||
/**
|
||||
@@ -614,7 +616,7 @@ export function HealthChart({
|
||||
chartKey,
|
||||
annotations,
|
||||
verticalMarkers,
|
||||
userTimezone = "Europe/Berlin",
|
||||
userTimezone: userTimezoneProp,
|
||||
valueScale = 1,
|
||||
valueOffset = 0,
|
||||
onVisibleStats,
|
||||
@@ -625,6 +627,9 @@ export function HealthChart({
|
||||
showDataTable = false,
|
||||
}: HealthChartProps) {
|
||||
const { isAuthenticated, user } = useAuth();
|
||||
// A mount without the prop used to pin Europe/Berlin, so those charts
|
||||
// cut days in Berlin for every user whatever their profile said.
|
||||
const userTimezone = userTimezoneProp || user?.timezone || DEFAULT_TIMEZONE;
|
||||
const { t, locale } = useTranslations();
|
||||
const fmt = useFormatters();
|
||||
// Issue #490 — x-axis tick + tooltip date labels. Every timestamp this
|
||||
|
||||
Reference in new issue
Block a user