Files
Jyotisha/frontend/src/components/home-onboarding-shell.tsx
T

144 lines
7.8 KiB
TypeScript

"use client";
import dynamic from "next/dynamic";
import type { FormEvent } from "react";
import { BirthTimeAssessmentOverlay, type BirthTimeAssessmentPhase } from "@/components/birth-time-assessment-overlay";
import { BirthLocationFields } from "@/components/birth-location-fields";
import { BirthTimeIntakeFields } from "@/components/birth-time-intake";
import { OnboardingChatMessage } from "@/components/onboarding-chat-message";
import type { BirthTimeGuidedController } from "@/hooks/use-birth-time-guided-journey";
import {
applyBirthTimeDraftPatch,
birthTimeDraftReadyHint,
isBirthTimeDraftReady,
} from "@/lib/birth-time-intake-model";
import type { JourneyClientResponse } from "@/lib/birth-time-journey-client";
import {
birthQuestion,
formatBirthMoment,
placeQuestion,
selectedBirthPlace,
} from "@/lib/home-profile";
import { presetOnboardingMessage, type OnboardingStep, type Profile } from "@/lib/home-types";
const BirthTimeRectification = dynamic(
() => import("@/components/birth-time-rectification").then((module) => module.BirthTimeRectification),
{
ssr: false,
loading: () => <p className="birth-time-assistant-intent" role="status">正在加载出生时间评估...</p>,
},
);
export type HomeOnboardingShellProps = {
readonly profileComplete: boolean;
readonly profileDraft: Profile;
readonly setProfileDraft: (update: Profile | ((current: Profile) => Profile)) => void;
readonly onboardingStep: OnboardingStep;
readonly presetMessageLength: number;
readonly currentOnboardingMessage: string;
readonly onboardingJustCompleted: boolean;
readonly onboardingCardReady: boolean;
readonly presetMessageFinished: boolean;
readonly birthTimeJourney: JourneyClientResponse | null;
readonly birthTimeAssessmentPhase: BirthTimeAssessmentPhase | null;
readonly birthTimeError: string;
readonly birthTimeGuided: BirthTimeGuidedController;
readonly profileSaving: boolean;
readonly saveOnboardingBirth: (event: FormEvent<HTMLFormElement>) => void;
readonly saveOnboardingPlace: (event: FormEvent<HTMLFormElement>) => void;
readonly retryBirthTimeAssessment: () => void;
};
export function HomeOnboardingShell({
profileComplete,
profileDraft,
setProfileDraft,
onboardingStep,
presetMessageLength,
currentOnboardingMessage,
onboardingJustCompleted,
onboardingCardReady,
presetMessageFinished,
birthTimeJourney,
birthTimeAssessmentPhase,
birthTimeError,
birthTimeGuided,
profileSaving,
saveOnboardingBirth,
saveOnboardingPlace,
retryBirthTimeAssessment,
}: HomeOnboardingShellProps) {
const birthTimeContinueHint = onboardingStep === "birth" ? birthTimeDraftReadyHint(profileDraft) : "";
return (
<>
<OnboardingChatMessage role="assistant" text={presetOnboardingMessage} streaming={onboardingStep === "name" && !profileComplete} length={presetMessageLength} />
{(onboardingStep !== "name" || profileComplete) && <OnboardingChatMessage role="user" text={profileDraft.name.trim()} />}
{(onboardingStep !== "name" || profileComplete) && <OnboardingChatMessage role="assistant" text={birthQuestion(profileDraft.name.trim())} streaming={onboardingStep === "birth" && !profileComplete} length={presetMessageLength} />}
{(onboardingStep === "place" || onboardingStep === "rectification" || profileComplete) && <OnboardingChatMessage role="user" text={formatBirthMoment(profileDraft)} />}
{(onboardingStep === "place" || onboardingStep === "rectification" || profileComplete) && <OnboardingChatMessage role="assistant" text={placeQuestion(profileDraft)} streaming={onboardingStep === "place" && !profileComplete} length={presetMessageLength} />}
{!profileComplete && onboardingStep === "rectification" && selectedBirthPlace(profileDraft) && <OnboardingChatMessage role="user" text={selectedBirthPlace(profileDraft)?.label ?? ""} />}
{!profileComplete && onboardingStep === "rectification" && birthTimeJourney && <OnboardingChatMessage role="assistant" text={currentOnboardingMessage} streaming length={presetMessageLength} phraseSafe />}
{profileComplete && onboardingJustCompleted && selectedBirthPlace(profileDraft) && <OnboardingChatMessage role="user" text={selectedBirthPlace(profileDraft)?.label ?? ""} />}
{profileComplete && onboardingJustCompleted && <OnboardingChatMessage role="assistant" text={currentOnboardingMessage} streaming length={presetMessageLength} />}
{!profileComplete && onboardingStep === "birth" && onboardingCardReady && (
<div className="onboarding-card-reveal">
<div className="onboarding-card-reveal-inner">
<form className="profile-form onboarding-card onboarding-step-card birth-time-transition-card" onSubmit={saveOnboardingBirth} aria-busy={birthTimeAssessmentPhase !== null}>
<div className="onboarding-card-heading">
<div className="onboarding-card-heading-copy">
<b>出生日期与时间</b>
</div>
</div>
<fieldset className="birth-time-transition-fields" disabled={birthTimeAssessmentPhase !== null}>
<BirthTimeIntakeFields value={profileDraft} onPatch={(patch) => setProfileDraft((current) => applyBirthTimeDraftPatch(current, patch))} />
<div className="onboarding-card-actions">
{birthTimeContinueHint ? <p className="onboarding-card-action-hint">{birthTimeContinueHint}</p> : <span />}
<button className="button-primary" type="submit" disabled={profileSaving || !isBirthTimeDraftReady(profileDraft)}>{profileSaving ? "保存中" : "继续"}</button>
</div>
</fieldset>
<BirthTimeAssessmentOverlay phase={birthTimeAssessmentPhase} />
</form>
</div>
</div>
)}
{!profileComplete && onboardingStep === "place" && onboardingCardReady && (
<div className="onboarding-card-reveal">
<div className="onboarding-card-reveal-inner">
<form className="profile-form onboarding-card onboarding-step-card birth-time-transition-card" onSubmit={saveOnboardingPlace} aria-busy={birthTimeAssessmentPhase !== null}>
<div className="onboarding-card-heading"><b>出生地点</b><small>搜索全球城市或区县,系统会匹配出生当日的时区。</small></div>
<fieldset className="birth-time-transition-fields" disabled={birthTimeAssessmentPhase !== null}>
<BirthLocationFields value={profileDraft} onChange={setProfileDraft} />
<div className="onboarding-card-actions"><button className="button-primary" type="submit" disabled={profileSaving || !selectedBirthPlace(profileDraft)}>{profileSaving ? "保存中" : "确定"}</button></div>
</fieldset>
<BirthTimeAssessmentOverlay phase={birthTimeAssessmentPhase} />
</form>
</div>
</div>
)}
{!profileComplete && onboardingStep === "rectification" && presetMessageFinished && birthTimeJourney && (
<div className="onboarding-card-reveal">
<div className="onboarding-card-reveal-inner">
<BirthTimeRectification
journey={birthTimeJourney}
controller={birthTimeGuided}
externalError={birthTimeError}
/>
</div>
</div>
)}
{!profileComplete && onboardingStep === "rectification" && presetMessageFinished && !birthTimeJourney && (
<div className="onboarding-card birth-time-retry-card" role="status">
<b>出生时间尚未完成评估</b>
<p>{birthTimeError || "资料已经保留,但暂时无法恢复校正进度。系统不会应用未经验证的具体时间。"}</p>
<button className="button-primary" type="button" disabled={profileSaving} onClick={() => void retryBirthTimeAssessment()}>{profileSaving ? "评估中" : "重新评估"}</button>
</div>
)}
</>
);
}