fix(onboarding): restore uncertain birth-time intake
Independent Staging Quality Gate / validate (push) Successful in 20m10s
Independent Staging Quality Gate / publish (push) Successful in 8m46s

This commit is contained in:
Jesse_Chen
2026-08-15 19:29:33 +08:00
parent 2984b6d1d0
commit 995da2d4b4
5 changed files with 110 additions and 18 deletions
+73 -4
View File
@@ -6,6 +6,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
import { birthTimeConsultationOptionsCopy } from "@/lib/birth-time-consultation-consent";
import {
birthTimeDisplayState,
birthTimePeriodOptions,
birthTimeSourceDefaults,
birthTimeSourceOptions,
type BirthTimeDraft,
@@ -70,6 +71,9 @@ export function BirthTimeIntakeFields({ value, onPatch }: BirthTimeIntakeProps)
const source = value.birthTimeSource;
const isConfirmed = value.birthTimeStatus === "confirmed";
const displayState = birthTimeDisplayState(value);
const knowledgeMode = source === "period_only" || source === "unknown"
? "uncertain"
: source ? "exact" : "";
const usesClockTime = source === "hospital_record"
|| source === "family_exact"
|| source === "approximate"
@@ -114,16 +118,20 @@ export function BirthTimeIntakeFields({ value, onPatch }: BirthTimeIntakeProps)
)}
{!isConfirmed && <fieldset className="birth-time-source-fieldset">
<legend></legend>
<p className="birth-time-source-intro"></p>
<legend></legend>
<p className="birth-time-source-intro"></p>
<div className="birth-time-source-list">
{birthTimeSourceOptions.map((option) => (
<label
className={`birth-time-source-option ${source === option.value ? "is-selected" : ""}`}
className={`birth-time-source-option ${option.value === "family_exact"
? knowledgeMode === "exact" ? "is-selected" : ""
: knowledgeMode === "uncertain" ? "is-selected" : ""}`}
key={option.value}
>
<input
checked={source === option.value}
checked={option.value === "family_exact"
? knowledgeMode === "exact"
: knowledgeMode === "uncertain"}
name={`birth-time-source-${groupId}`}
type="radio"
value={option.value}
@@ -158,6 +166,67 @@ export function BirthTimeIntakeFields({ value, onPatch }: BirthTimeIntakeProps)
)}
</div>
)}
{source === "period_only" && (
<div className="birth-time-detail-grid birth-time-period-details onboarding-card-reveal">
<label>
<span></span>
<Select
required
value={value.birthTimePeriod || null}
onValueChange={(nextValue) => {
if (typeof nextValue === "string") onPatch({ birthTimePeriod: nextValue });
}}
>
<SelectTrigger aria-label="最接近的时间范围">
<SelectValue placeholder="请选择大致时段">
{(selectedValue) => birthTimePeriodOptions.find((option) => option.value === selectedValue)?.label ?? "请选择大致时段"}
</SelectValue>
</SelectTrigger>
<SelectContent>
{birthTimePeriodOptions.map((option) => (
<SelectItem key={option.value} value={option.value}>{option.label}</SelectItem>
))}
</SelectContent>
</Select>
</label>
<label>
<span></span>
<textarea
maxLength={240}
placeholder="例如:天刚亮、午饭前后、家人记得大约 6—8 点"
rows={2}
value={value.birthTimeClue}
onChange={(event) => onPatch({ birthTimeClue: event.target.value })}
/>
</label>
<button
className="button-secondary birth-time-skip-button"
type="button"
onClick={() => onPatch({
birthTimeSource: "unknown",
reportedTime: "",
birthTimePeriod: "",
birthTimeClue: "",
uncertaintyBeforeMinutes: null,
uncertaintyAfterMinutes: null,
birthTimeStatus: "reported",
time: "",
})}
></button>
</div>
)}
{source === "unknown" && (
<div className="birth-time-detail-note onboarding-card-reveal" role="status">
<p>使</p>
<button
className="button-secondary"
type="button"
onClick={() => onPatch({ birthTimeSource: "period_only", birthTimeStatus: "reported" })}
></button>
</div>
)}
</div>
);
}
@@ -84,6 +84,7 @@ export function formatBirthDate(value: Date): string {
export const birthTimeSourceOptions = [
{ value: "family_exact", label: "我知道准确出生时间", hint: "保存为初始化填报时间,不会自动标记为引擎确认" },
{ value: "period_only", label: "我不确定准确时间", hint: "告诉我们大致时段;完全不清楚也可以直接跳过" },
] as const;
export const birthTimeSourceDefaults = {
@@ -206,18 +206,19 @@ test("unverified birth time no longer emits a modal or toast gate", () => {
assert.doesNotMatch(page, /role="alertdialog"[\s\S]{0,300}出生时间还没有完成校正/);
});
test("birth time intake only accepts one concrete initialization time", () => {
test("birth time intake starts with exact or uncertain choices and keeps rectification optional", () => {
const intake = readFileSync(new URL("../src/components/birth-time-intake.tsx", import.meta.url), "utf8");
const model = readFileSync(new URL("../src/lib/birth-time-intake-model.ts", import.meta.url), "utf8");
assert.match(model, /我知道准确出生时间/);
assert.match(intake, /请填写你初始化提供的具体出生时间/);
assert.match(model, /我不确定准确时间/);
assert.match(intake, /你对出生时间了解多少?/);
assert.match(intake, /不用猜具体分钟/);
assert.match(intake, /aria-label="选择出生时间"/);
assert.doesNotMatch(model, /我不确定准确时间/);
assert.doesNotMatch(intake, /source === "family_exact" \|\| source === "approximate"/);
assert.doesNotMatch(intake, /请选择最接近的时间范围/);
assert.doesNotMatch(intake, /完全不清楚,跳过出生时间/);
assert.doesNotMatch(intake, /生时校正以后需要时再做/);
assert.match(intake, /请选择最接近的时间范围/);
assert.match(intake, /完全不清楚,跳过出生时间/);
assert.match(intake, /生时校正以后需要时再做/);
});
test("homepage and profile result copy use the source-aware consultation options", () => {
+10 -4
View File
@@ -308,13 +308,19 @@ test("persisted birth dates normalize database ISO values without accepting inva
assert.equal(normalizePersistedBirthDate("1997-08-08junk"), "");
});
test("fresh intake only offers a concrete reported time and does not auto-confirm it", () => {
test("fresh intake preserves exact, approximate-period, and unknown-time paths without auto-confirming", () => {
const source = readFileSync(new URL("../src/components/birth-time-intake.tsx", import.meta.url), "utf8");
assert.deepEqual(birthTimeSourceOptions.map((option) => option.value), ["family_exact"]);
assert.deepEqual(
birthTimeSourceOptions.map((option) => option.value),
["family_exact", "period_only"],
);
assert.doesNotMatch(source, /已用于当前排盘/);
assert.doesNotMatch(source, /可能误差|选择误差范围|最接近的时间范围|大致时段|描述一个时间范围/);
assert.doesNotMatch(source, /birthTimePeriodOptions|birthTimeSource: "period_only"|birthTimeSource: "unknown"/);
assert.match(source, /birthTimePeriodOptions/);
assert.match(source, /birthTimeSource: "unknown"/);
assert.match(source, /请选择最接近的时间范围/);
assert.match(source, /完全不清楚,跳过出生时间/);
assert.match(source, /我可以描述一个时间范围/);
assert.match(source, /birthTimeStatus: "reported"/);
assert.match(source, /birthTimeConsultationOptionsCopy\(value\)/);
});