mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 14:32:03 +00:00
Q2, Q4 and Q6 let "Weiter" write an answer with nothing ticked. On Q2 that answered "no areas", which is what switched every optional module off for somebody whose click meant "no preference"; on Q6 it marked the step done while recording no unit at all. An empty selection now yields no body, so the primary button is dead until something is chosen. Skip stays the way past, and Skip is the conservative derivation the completion route already applies.
139 lines
4.6 KiB
TypeScript
139 lines
4.6 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
import { toast } from "sonner";
|
|
|
|
import { ChoiceChip } from "@/components/onboarding/choice-chip";
|
|
import { StepActions } from "@/components/onboarding/step-actions";
|
|
import { StepHeading } from "@/components/onboarding/step-heading";
|
|
import {
|
|
screenHref,
|
|
useOnboardingAnswer,
|
|
} from "@/components/onboarding/use-onboarding-flow";
|
|
import { useAuth } from "@/hooks/use-auth";
|
|
import { localizedApiError } from "@/lib/api/localized-error";
|
|
import { useTranslations } from "@/lib/i18n/context";
|
|
import type { OnboardingStateDto } from "@/lib/onboarding/needs";
|
|
import {
|
|
unitQuestionsFor,
|
|
unitsAnswerBody,
|
|
} from "@/lib/onboarding/question-config";
|
|
import { nextScreen, previousScreen } from "@/lib/onboarding/wizard-steps";
|
|
|
|
type GlucoseUnit = "mg/dL" | "mmol/L";
|
|
type UnitPreference = "metric" | "imperial";
|
|
|
|
/**
|
|
* v1.39 (C2) — Q6, the optional units question.
|
|
*
|
|
* Shown only while an area that needs a unit is among the answers and the
|
|
* account does not already hold the preference (the step machine decides;
|
|
* this screen is never reached otherwise). Each of the two questions is
|
|
* asked only for the area that needs it, and each is pre-filled from what
|
|
* the flow recorded or, failing that, from the account's own column — so
|
|
* confirming what the account already had sends nothing for that field.
|
|
*/
|
|
export function UnitsScreen({ state }: { state: OnboardingStateDto }) {
|
|
const { t } = useTranslations();
|
|
const router = useRouter();
|
|
const { user } = useAuth();
|
|
const answer = useOnboardingAnswer();
|
|
const asked = unitQuestionsFor(state.needs);
|
|
|
|
const [glucoseUnit, setGlucoseUnit] = useState<GlucoseUnit | null>(
|
|
() =>
|
|
state.needs.units.glucoseUnit ??
|
|
(user?.glucoseUnit === "mmol/L" || user?.glucoseUnit === "mg/dL"
|
|
? user.glucoseUnit
|
|
: null),
|
|
);
|
|
const [unitPreference, setUnitPreference] = useState<UnitPreference | null>(
|
|
() => state.needs.units.unitPreference ?? user?.unitPreference ?? null,
|
|
);
|
|
|
|
const back = previousScreen(state, "units");
|
|
|
|
const answerBody = unitsAnswerBody({ asked, glucoseUnit, unitPreference });
|
|
|
|
async function persist(
|
|
input:
|
|
| NonNullable<ReturnType<typeof unitsAnswerBody>>
|
|
| {
|
|
step: "units";
|
|
status: "skipped";
|
|
},
|
|
) {
|
|
try {
|
|
const written = await answer.mutateAsync(input);
|
|
router.push(screenHref(nextScreen(written, "units") ?? "confirm"));
|
|
} catch (err) {
|
|
toast.error(localizedApiError(err, t, "onboarding.errorGeneric"));
|
|
}
|
|
}
|
|
|
|
return (
|
|
<section aria-labelledby="onboarding-units-title" className="space-y-6">
|
|
<StepHeading
|
|
id="onboarding-units-title"
|
|
title={t("onboarding.flow.units.title")}
|
|
description={t("onboarding.flow.units.body")}
|
|
/>
|
|
|
|
{asked.glucose ? (
|
|
<fieldset
|
|
className="grid grid-cols-2 gap-3"
|
|
data-slot="onboarding-question-glucose-unit"
|
|
>
|
|
<legend className="text-foreground mb-2 text-sm font-medium">
|
|
{t("onboarding.flow.units.glucoseLegend")}
|
|
</legend>
|
|
{(["mg/dL", "mmol/L"] as const).map((option) => (
|
|
<ChoiceChip
|
|
key={option}
|
|
name="onboarding-glucose-unit"
|
|
value={option}
|
|
kind="single"
|
|
checked={glucoseUnit === option}
|
|
onChange={() => setGlucoseUnit(option)}
|
|
label={option}
|
|
/>
|
|
))}
|
|
</fieldset>
|
|
) : null}
|
|
|
|
{asked.weight ? (
|
|
<fieldset
|
|
className="grid grid-cols-2 gap-3"
|
|
data-slot="onboarding-question-unit-preference"
|
|
>
|
|
<legend className="text-foreground mb-2 text-sm font-medium">
|
|
{t("onboarding.flow.units.weightLegend")}
|
|
</legend>
|
|
{(["metric", "imperial"] as const).map((option) => (
|
|
<ChoiceChip
|
|
key={option}
|
|
name="onboarding-unit-preference"
|
|
value={option}
|
|
kind="single"
|
|
checked={unitPreference === option}
|
|
onChange={() => setUnitPreference(option)}
|
|
label={t(`onboarding.flow.units.options.${option}`)}
|
|
/>
|
|
))}
|
|
</fieldset>
|
|
) : null}
|
|
|
|
<StepActions
|
|
backHref={back ? screenHref(back) : undefined}
|
|
onSkip={() => void persist({ step: "units", status: "skipped" })}
|
|
onNext={() => {
|
|
if (answerBody) void persist(answerBody);
|
|
}}
|
|
nextDisabled={answerBody === null}
|
|
pending={answer.isPending}
|
|
/>
|
|
</section>
|
|
);
|
|
}
|