fix(people): 本人 can edit their own birth data in 星盘档案 (S1, BUG-1081)

The owner had no edit entry anywhere once /people replaced the settings
chart pane. 本人 now gets 编辑 with the same form; the save goes through
PATCH /api/account via a shared lib/self-profile-save.ts that the home
onboarding also uses. The dead saveProfile is removed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
This commit is contained in:
Jesse_Chen
2026-09-29 00:43:43 +08:00
co-authored by Claude Opus 5.5
parent adbb205440
commit 8415fdca72
18 changed files with 337 additions and 106 deletions
+3 -73
View File
@@ -7,14 +7,9 @@ import {
type BeamAvatarPatch,
} from "@/lib/beam-avatar";
import {
applyPersistedBirthTime,
birthTimePersistenceValues,
isBirthTimeDraftReady,
} from "@/lib/birth-time-intake-model";
import {
birthTimeConsultationOptionsCopy,
createBirthTimeConsultationConsentState,
type BirthTimeConsultationConsentState,
type LatestAccountRequestGuard,
} from "@/lib/birth-time-consultation-consent";
import { readChartBirthFingerprint } from "@/lib/chart-birth-truth";
@@ -30,13 +25,12 @@ import {
pinChartSnapshotIdentity,
} from "@/lib/secondary-page-data";
import {
birthProfileDeclarationChanged,
missingProfileStep,
readProfile,
selectedBirthPlace,
} from "@/lib/home-profile";
import { resolveAyanamsa } from "@/lib/ayanamsa";
import { withSavedGender, type ProfileGender } from "@/lib/profile-gender";
import { saveSelfProfile } from "@/lib/self-profile-save";
import { createStartGreeting } from "@/lib/starter-greeting";
import { clearHomeWarmSnapshot } from "@/lib/home-warm-snapshot";
import {
@@ -68,7 +62,6 @@ export type ProfileOnboardingParams = {
setAccountMenuOpen: Dispatch<SetStateAction<boolean>>;
setActiveAccountDialog: Dispatch<SetStateAction<AccountDialog | null>>;
setBirthTimeAssessmentPhase: Dispatch<SetStateAction<BirthTimeAssessmentPhase | null>>;
setBirthTimeConsultationConsent: Dispatch<SetStateAction<BirthTimeConsultationConsentState>>;
setBirthTimeError: Dispatch<SetStateAction<string>>;
setBirthTimeJourney: Dispatch<SetStateAction<JourneyClientResponse | null>>;
setBillingPane: Dispatch<SetStateAction<OpenAccountDialogOptions>>;
@@ -76,7 +69,6 @@ export type ProfileOnboardingParams = {
setOnboardingJustCompleted: Dispatch<SetStateAction<boolean>>;
setOnboardingStep: Dispatch<SetStateAction<OnboardingStep>>;
setPresetMessageLength: Dispatch<SetStateAction<number>>;
setRectificationError: Dispatch<SetStateAction<string>>;
setSigningOut: Dispatch<SetStateAction<boolean>>;
setStartGreeting: Dispatch<SetStateAction<string>>;
signingOut: boolean;
@@ -99,7 +91,6 @@ export function useProfileOnboarding(params: ProfileOnboardingParams) {
setAccountMenuOpen,
setActiveAccountDialog,
setBirthTimeAssessmentPhase,
setBirthTimeConsultationConsent,
setBirthTimeError,
setBirthTimeJourney,
setBillingPane,
@@ -107,7 +98,6 @@ export function useProfileOnboarding(params: ProfileOnboardingParams) {
setOnboardingJustCompleted,
setOnboardingStep,
setPresetMessageLength,
setRectificationError,
setSigningOut,
setStartGreeting,
signingOut,
@@ -227,42 +217,9 @@ export function useProfileOnboarding(params: ProfileOnboardingParams) {
async function persistProfile(nextProfile: Profile): Promise<Profile> {
if (!account) throw new Error("账户尚未加载完成");
if (process.env.NODE_ENV === "development" && uiPreview.current) return nextProfile;
const birthPlace = selectedBirthPlace(nextProfile);
const response = await fetch("/api/account", {
method: "PATCH",
credentials: "same-origin",
headers: { "content-type": "application/json" },
body: JSON.stringify({
name: nextProfile.name.trim() || null,
birth_date: nextProfile.date || null,
...birthTimePersistenceValues(nextProfile),
country_code: nextProfile.countryCode,
province_code: nextProfile.provinceCode || null,
city_code: nextProfile.cityCode || null,
district_code: nextProfile.districtCode || null,
birth_place_label: nextProfile.birthPlaceLabel || null,
birth_place_type: nextProfile.birthPlaceType || null,
birth_place_provider: nextProfile.birthPlaceProvider || null,
birth_place_provider_id: nextProfile.birthPlaceProviderId || null,
latitude: birthPlace?.lat ?? null,
longitude: birthPlace?.lon ?? null,
timezone_id: nextProfile.timezoneId || null,
timezone_offset: birthPlace?.tz ?? null,
timezone_source: nextProfile.timezoneSource || null,
ayanamsa: resolveAyanamsa(nextProfile),
}),
});
const payload = await response.json().catch(() => null) as {
error?: string;
birthTime?: unknown;
chartBirth?: { fingerprint?: unknown };
} | null;
if (!response.ok) {
throw new Error(payload?.error || "账户资料暂时无法保存。");
}
const fingerprint = readChartBirthFingerprint(payload);
// One write path for the owner's birth data, shared with 星盘档案 (BUG-1081).
const { profile: savedProfile, fingerprint } = await saveSelfProfile(nextProfile);
if (fingerprint) pinChartSnapshotIdentity({ accountId: account.user.id, fingerprint });
const savedProfile = applyPersistedBirthTime(nextProfile, payload?.birthTime);
setAccount((current) => current ? { ...current, profile: savedProfile } : current);
return savedProfile;
}
@@ -290,32 +247,6 @@ export function useProfileOnboarding(params: ProfileOnboardingParams) {
return assessedProfile;
}
async function saveProfile(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
if (!profileDraft.name.trim() || !isBirthTimeDraftReady(profileDraft) || !selectedBirthPlace(profileDraft) || !account || profileSaving) return;
setProfileSaving(true);
setProfileNotice("");
setAccountError("");
try {
const declarationChanged = birthProfileDeclarationChanged(profile, profileDraft);
const savedProfile = await persistProfile(profileDraft);
setProfile(savedProfile);
setProfileDraft(savedProfile);
setRectificationError("");
if (declarationChanged) {
setBirthTimeConsultationConsent(createBirthTimeConsultationConsentState());
void refreshAccount();
}
setProfileNotice(savedProfile.birthTimeStatus === "confirmed"
? "出生资料已保存到云端,可在同一账号的其他设备使用。"
: `出生资料已保存。${birthTimeConsultationOptionsCopy(savedProfile)}`);
} catch (caught) {
setAccountError(friendlyError(caught instanceof Error ? caught.message : "出生资料保存失败"));
} finally {
setProfileSaving(false);
}
}
async function saveOnboardingName() {
const name = composerDraftSnapshot().replace(/\s+/g, " ").trim().slice(0, 80);
if (!name || !account || profileSaving) return;
@@ -456,7 +387,6 @@ export function useProfileOnboarding(params: ProfileOnboardingParams) {
persistGender,
persistProfile,
assessSavedBirthTime,
saveProfile,
saveOnboardingName,
saveOnboardingBirth,
editDeclaredBirthTimeDetails,