mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 22:32:04 +00:00
fix(profile): read and store the date of birth as a calendar day
The iOS app sends the date of birth as the instant its date picker returned, and the profile writers stored that instant as sent. Since the report cover and the share page read the date part of the stored value, an entry made just after midnight in Berlin printed the day before, and a Californian entry made in the evening printed the day after. Both profile writers now store the calendar day the value names in the owner's zone, at noon UTC like other date-only values, so an iOS client reading the instant back in its own zone keeps the date. Older rows keep their instant: dateOfBirthKey reads a date-only anchor (UTC midnight or noon) by its date and any other instant in the owner's zone. The report collector, the targets age, and the web forms that send the date back read it through that helper.
This commit is contained in:
1 parent
82125815d5
commit
17dbb225d4
13 files changed
+275
-30
No files matched your search
@@ -9,6 +9,8 @@ import {
|
||||
type BaselineFieldErrors,
|
||||
} from "@/components/onboarding/baseline-fields";
|
||||
import { useUnitDisplay } from "@/hooks/use-unit-display";
|
||||
import { dateOfBirthKey } from "@/lib/tz/date-only";
|
||||
import { DEFAULT_TIMEZONE } from "@/lib/tz/format";
|
||||
import {
|
||||
resolveHeightUnitAdapter,
|
||||
type HeightDraft,
|
||||
@@ -59,6 +61,8 @@ interface BaselineInitialValues {
|
||||
heightCm: number | null;
|
||||
dateOfBirth: string | null;
|
||||
gender: string | null;
|
||||
/** The owner's zone, which a date of birth from the iOS app is read in. */
|
||||
timezone?: string;
|
||||
}
|
||||
|
||||
interface BaselineFormState {
|
||||
@@ -133,8 +137,12 @@ export function BaselineForm({
|
||||
const [form, setForm] = useState<BaselineFormState>(() => ({
|
||||
displayName: "",
|
||||
height: heightAdapter.toDraft(initial.heightCm),
|
||||
// The payload's ISO instant, as the date field's YYYY-MM-DD.
|
||||
dateOfBirth: initial.dateOfBirth ? initial.dateOfBirth.slice(0, 10) : "",
|
||||
dateOfBirth: initial.dateOfBirth
|
||||
? dateOfBirthKey(
|
||||
new Date(initial.dateOfBirth),
|
||||
initial.timezone ?? DEFAULT_TIMEZONE,
|
||||
)
|
||||
: "",
|
||||
gender: initial.gender ?? "",
|
||||
}));
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
@@ -35,6 +35,8 @@ import {
|
||||
questionsToPassBeforeConfirm,
|
||||
} from "@/lib/onboarding/wizard-steps";
|
||||
|
||||
const NO_BASELINE = { heightCm: null, dateOfBirth: null, gender: null };
|
||||
|
||||
/**
|
||||
* v1.39 — the confirm screen.
|
||||
*
|
||||
@@ -334,11 +336,7 @@ export function ConfirmScreen({
|
||||
) : (
|
||||
<BaselineForm
|
||||
backHref={back ? screenHref(back) : undefined}
|
||||
initial={{
|
||||
heightCm: user?.heightCm ?? null,
|
||||
dateOfBirth: user?.dateOfBirth ?? null,
|
||||
gender: user?.gender ?? null,
|
||||
}}
|
||||
initial={user ?? NO_BASELINE}
|
||||
onConfirmed={finish}
|
||||
confirming={finishing}
|
||||
demoMode={demoMode}
|
||||
|
||||
@@ -52,7 +52,7 @@ import {
|
||||
} from "./account-section-utils";
|
||||
import { AvatarSection } from "./avatar-section";
|
||||
import { SetupCard } from "./setup-card";
|
||||
import { dateOnlyKey } from "@/lib/tz/date-only";
|
||||
import { dateOfBirthKey } from "@/lib/tz/date-only";
|
||||
import { translateCodeOrNull } from "@/lib/i18n/translate-code";
|
||||
|
||||
export { resolveInitialTimezone } from "./account-section-utils";
|
||||
@@ -149,7 +149,7 @@ export function AccountSection() {
|
||||
displayName: user.displayName ?? "",
|
||||
height: heightAdapter.toDraft(user.heightCm),
|
||||
dateOfBirth: user.dateOfBirth
|
||||
? dateOnlyKey(new Date(user.dateOfBirth))
|
||||
? dateOfBirthKey(new Date(user.dateOfBirth), user.timezone)
|
||||
: "",
|
||||
gender: user.gender ?? "",
|
||||
fullName: user.fullName ?? "",
|
||||
|
||||
Reference in new issue
Block a user