feat(ui): optional gender radio in the people form and 个人资料 (T2)

One ProfileGenderField (女 / 男 / 不填, 44px, one hint line) in the
people-archive edit form (saved with the form, 不填 → null) and in
设置 → 个人资料 for the owner (saves on its own PATCH; birth-data saves never
send gender). The detail card shows 性别 (未填 when empty). Not asked in
onboarding. DESIGN/VOICE updated; 390/1280 screenshots with fictional data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
This commit is contained in:
Jesse_Chen
2026-09-27 18:30:58 +08:00
co-authored by Claude Opus 5.5
parent 5caf03844e
commit d79dada970
19 changed files with 318 additions and 3 deletions
@@ -36,6 +36,7 @@ import {
selectedBirthPlace,
} from "@/lib/home-profile";
import { resolveAyanamsa } from "@/lib/ayanamsa";
import { withSavedGender, type ProfileGender } from "@/lib/profile-gender";
import { createStartGreeting } from "@/lib/starter-greeting";
import { clearHomeWarmSnapshot } from "@/lib/home-warm-snapshot";
import {
@@ -200,6 +201,26 @@ export function useProfileOnboarding(params: ProfileOnboardingParams) {
}
}
/**
* The owner's optional gender (设置 → 个人资料). A PATCH of this one field:
* birth-data saves never send it, so they can never clear it.
*/
async function persistGender(gender: ProfileGender | null): Promise<void> {
if (!account) throw new Error("账户尚未加载完成");
const response = await fetch("/api/account", {
method: "PATCH",
credentials: "same-origin",
headers: { "content-type": "application/json" },
body: JSON.stringify({ gender }),
});
const payload = await response.json().catch(() => null) as { error?: string } | null;
if (!response.ok) throw new Error(payload?.error || "性别没保存上,再试一次。");
const apply = (current: Profile): Profile => ({ ...current, gender });
setProfile(apply);
setProfileDraft(apply);
setAccount((current) => current ? { ...current, profile: withSavedGender(current.profile, gender) } : current);
}
async function persistProfile(nextProfile: Profile): Promise<Profile> {
if (!account) throw new Error("账户尚未加载完成");
if (process.env.NODE_ENV === "development" && uiPreview.current) return nextProfile;
@@ -427,6 +448,7 @@ export function useProfileOnboarding(params: ProfileOnboardingParams) {
openAccountDialog,
closeAccountDialog,
persistAvatar,
persistGender,
persistProfile,
assessSavedBirthTime,
saveProfile,