diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 65006c9a..fc2eac83 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -242,17 +242,12 @@ const BillingPanel = dynamic( export default function Home() { const router = useRouter(); - const [profile, setProfile] = useState(emptyProfile); - const [profileDraft, setProfileDraft] = useState(emptyProfile); const [accountMenuOpen, setAccountMenuOpen] = useState(false); const [activeAccountDialog, setActiveAccountDialog] = useState(null); const [chartLibrary, setChartLibrary] = useState([]); const [activeChartId, setActiveChartId] = useState("self"); const [billingPane, setBillingPane] = useState({}); const [dailyStarlanguage, setDailyStarlanguage] = useState({ kind: "pending" }); - const [profileNotice, setProfileNotice] = useState(""); - const [avatarNotice, setAvatarNotice] = useState(""); - const [avatarSaving, setAvatarSaving] = useState(false); const [account, setAccount] = useState(null); const [accountError, setAccountError] = useState(""); const [signingOut, setSigningOut] = useState(false); @@ -299,7 +294,6 @@ export default function Home() { const [bootstrapPhase, setBootstrapPhase] = useState("account"); const prepareStartedAt = useRef(null); const [guidedJourneyPreview, setGuidedJourneyPreview] = useState(false); - const [profileSaving, setProfileSaving] = useState(false); const [creatingSession, setCreatingSession] = useState(false); const [sessionDetailLoadingId, setSessionDetailLoadingId] = useState(null); const [sessionFullPrompt, setSessionFullPrompt] = useState<{ question: string; theme: Theme } | null>(null); @@ -315,7 +309,7 @@ export default function Home() { const conversation = useRef(null); const accountTrigger = useRef(null); const accountDialog = useRef(null); - const dialogReturnTarget = useRef(null); + const dialogReturnTargetRef = useRef(null); const closeButton = useRef(null); const onboardingPaywallShown = useRef(false); const starterGreetingUnseen = useRef(true); @@ -348,17 +342,48 @@ export default function Home() { const sessionDetailInFlight = useRef(new Set()); const sessionsRef = useRef(sessions); const rectificationSessionOpenerRef = useRef(async (_exactSessionId: string) => {}); - const guidedBirthTimeReadyRef = useRef((_result: JourneyClientResponse) => {}); - const editDeclaredBirthTimeDetailsRef = useRef(() => {}); const uiPreview = useRef(false); const uiPreviewMode = useRef(null); - const birthTimeRevisionPending = useRef(false); + const birthTimeRevisionPendingRef = useRef(false); + const { + profile, + setProfile, + profileDraft, + setProfileDraft, + profileNotice, + setProfileNotice, + avatarNotice, + avatarSaving, + profileSaving, + refreshAccount, + completeGuidedBirthTime, + editDeclaredBirthTimeDetails, + openAccountDialog, + closeAccountDialog, + persistAvatar, + saveProfile, + saveOnboardingName, + saveOnboardingBirth, + saveOnboardingPlace, + retryBirthTimeAssessment, + signOut, + } = useProfileOnboarding({ + account, accountRefreshGuard, accountTrigger, activeChartId, + birthTimeRevisionPendingRef, chartLibrary, dialogReturnTargetRef, + setAccount, setAccountError, setAccountMenuOpen, setActiveAccountDialog, + setActiveChartId, setBirthTimeAssessmentPhase, + setBirthTimeConsultationConsent, setBirthTimeError, setBirthTimeJourney, setBillingPane, setDraft, + setOnboardingJustCompleted, setOnboardingStep, setPresetMessageLength, + setRectificationError, + setSigningOut, setStartGreeting, signingOut, uiPreview, + }); + const birthTimeGuided = useBirthTimeGuidedJourney({ journey: birthTimeJourney, preview: guidedJourneyPreview, onJourney: setBirthTimeJourney, - onReady: (result) => guidedBirthTimeReadyRef.current(result), - onEditBirthTimeDetails: () => editDeclaredBirthTimeDetailsRef.current(), + onReady: (result) => completeGuidedBirthTime(result), + onEditBirthTimeDetails: () => editDeclaredBirthTimeDetails(), }); const activeSession = sessions.find((session) => session.id === activeSessionId) ?? sessions[0]; @@ -437,29 +462,6 @@ export default function Home() { openRectificationSession: (exactSessionId) => rectificationSessionOpenerRef.current(exactSessionId), }); - const { - refreshAccount, - openAccountDialog, - closeAccountDialog, - persistAvatar, - saveProfile, - saveOnboardingName, - saveOnboardingBirth, - saveOnboardingPlace, - retryBirthTimeAssessment, - signOut, - } = useProfileOnboarding({ - account, accountRefreshGuard, accountTrigger, activeChartId, avatarSaving, - birthTimeRevisionPending, chartLibrary, dialogReturnTarget, profile, profileDraft, - profileSaving, setAccount, setAccountError, setAccountMenuOpen, setActiveAccountDialog, - setActiveChartId, setAvatarNotice, setAvatarSaving, setBirthTimeAssessmentPhase, - setBirthTimeConsultationConsent, setBirthTimeError, setBirthTimeJourney, setBillingPane, setDraft, - setOnboardingJustCompleted, setOnboardingStep, setPresetMessageLength, - setProfile, setProfileDraft, setProfileNotice, setProfileSaving, setRectificationError, - setSigningOut, setStartGreeting, signingOut, uiPreview, - guidedBirthTimeReadyRef, editDeclaredBirthTimeDetailsRef, - }); - const { synastryPanel, clearSynastryHistory, applyCloudSynastryHistory } = useSynastry({ accountId, profile, chooseSuggestedQuestion, closeAccountDialog, }); @@ -632,7 +634,7 @@ export default function Home() { setProfile((current) => preserveShallowEqual(current, nextProfile)); if (syncSelfDraft) setProfileDraft((current) => preserveShallowEqual(current, nextProfile)); }); - }, [accountId, activeChartId, chartLibrary, hydrated]); + }, [accountId, activeChartId, chartLibrary, hydrated, setProfile, setProfileDraft]); const profileComplete = isProfileComplete(profile); const birthTimeRoute = resolveBirthTimeConsultationRoute(profile, birthTimeConsultationConsent, activeSessionId); @@ -1096,7 +1098,7 @@ export default function Home() { window.clearTimeout(bootstrapTimeout); controller.abort(); }; - }, []); + }, [setProfile, setProfileDraft]); useEffect(() => { if (!hydrated || uiPreview.current) return; @@ -1313,7 +1315,7 @@ export default function Home() { if (event.key === "Escape") { if (signingOut) return; setActiveAccountDialog(null); - const returnTarget = dialogReturnTarget.current; + const returnTarget = dialogReturnTargetRef.current; window.requestAnimationFrame(() => returnTarget?.focus()); return; } @@ -1514,7 +1516,7 @@ export default function Home() { dialogClass: activeAccountDialog ? accountDialogClasses[activeAccountDialog] : "", signingOut, close: closeAccountDialog, - navigate: (dialog) => openAccountDialog(dialog, dialogReturnTarget.current), + navigate: (dialog) => openAccountDialog(dialog, dialogReturnTargetRef.current), overlayRef: accountDialog, closeButtonRef: closeButton, renderProfile: () => ( diff --git a/frontend/src/hooks/use-profile-onboarding.ts b/frontend/src/hooks/use-profile-onboarding.ts index 0e70f609..64670343 100644 --- a/frontend/src/hooks/use-profile-onboarding.ts +++ b/frontend/src/hooks/use-profile-onboarding.ts @@ -1,6 +1,6 @@ "use client"; -import { type Dispatch, FormEvent, MutableRefObject, SetStateAction } from "react"; +import { useState, type Dispatch, FormEvent, MutableRefObject, SetStateAction } from "react"; import { beamAvatarSchema, @@ -43,6 +43,7 @@ import { type OpenAccountDialogOptions, type Profile, accountDialogOptions, + emptyProfile, } from "@/lib/home-types"; import { preserveShallowEqual } from "@/lib/preserve-shallow-equal"; import { @@ -58,20 +59,14 @@ export type ProfileOnboardingParams = { accountRefreshGuard: MutableRefObject; accountTrigger: MutableRefObject; activeChartId: string; - avatarSaving: boolean; - birthTimeRevisionPending: MutableRefObject; + birthTimeRevisionPendingRef: MutableRefObject; chartLibrary: ChartLibraryRecord[]; - dialogReturnTarget: MutableRefObject; - profile: Profile; - profileDraft: Profile; - profileSaving: boolean; + dialogReturnTargetRef: MutableRefObject; setAccount: Dispatch>; setAccountError: Dispatch>; setAccountMenuOpen: Dispatch>; setActiveAccountDialog: Dispatch>; setActiveChartId: Dispatch>; - setAvatarNotice: Dispatch>; - setAvatarSaving: Dispatch>; setBirthTimeAssessmentPhase: Dispatch>; setBirthTimeConsultationConsent: Dispatch>; setBirthTimeError: Dispatch>; @@ -81,17 +76,11 @@ export type ProfileOnboardingParams = { setOnboardingJustCompleted: Dispatch>; setOnboardingStep: Dispatch>; setPresetMessageLength: Dispatch>; - setProfile: Dispatch>; - setProfileDraft: Dispatch>; - setProfileNotice: Dispatch>; - setProfileSaving: Dispatch>; setRectificationError: Dispatch>; setSigningOut: Dispatch>; setStartGreeting: Dispatch>; signingOut: boolean; uiPreview: MutableRefObject; - guidedBirthTimeReadyRef: MutableRefObject<(result: JourneyClientResponse) => void>; - editDeclaredBirthTimeDetailsRef: MutableRefObject<() => void>; }; export function useProfileOnboarding(params: ProfileOnboardingParams) { @@ -100,20 +89,14 @@ export function useProfileOnboarding(params: ProfileOnboardingParams) { accountRefreshGuard, accountTrigger, activeChartId, - avatarSaving, - birthTimeRevisionPending, + birthTimeRevisionPendingRef, chartLibrary, - dialogReturnTarget, - profile, - profileDraft, - profileSaving, + dialogReturnTargetRef, setAccount, setAccountError, setAccountMenuOpen, setActiveAccountDialog, setActiveChartId, - setAvatarNotice, - setAvatarSaving, setBirthTimeAssessmentPhase, setBirthTimeConsultationConsent, setBirthTimeError, @@ -123,19 +106,22 @@ export function useProfileOnboarding(params: ProfileOnboardingParams) { setOnboardingJustCompleted, setOnboardingStep, setPresetMessageLength, - setProfile, - setProfileDraft, - setProfileNotice, - setProfileSaving, setRectificationError, setSigningOut, setStartGreeting, signingOut, uiPreview, - guidedBirthTimeReadyRef, - editDeclaredBirthTimeDetailsRef, } = params; + // Owned here since 2026-09-16 (state lowering batch 2). Home reads them back + // from this hook's return instead of declaring them itself. + const [profile, setProfile] = useState(emptyProfile); + const [profileDraft, setProfileDraft] = useState(emptyProfile); + const [profileNotice, setProfileNotice] = useState(""); + const [avatarNotice, setAvatarNotice] = useState(""); + const [avatarSaving, setAvatarSaving] = useState(false); + const [profileSaving, setProfileSaving] = useState(false); + async function refreshAccount() { const requestIdentity = accountRefreshGuard.current.begin(); try { @@ -162,7 +148,7 @@ export function useProfileOnboarding(params: ProfileOnboardingParams) { returnTargetOrOptions?: HTMLButtonElement | null | OpenAccountDialogOptions, ) { const options = accountDialogOptions(returnTargetOrOptions); - dialogReturnTarget.current = options.returnTarget ?? accountTrigger.current; + dialogReturnTargetRef.current = options.returnTarget ?? accountTrigger.current; setAccountMenuOpen(false); setAccountError(""); if (dialog === "profile") { @@ -186,7 +172,7 @@ export function useProfileOnboarding(params: ProfileOnboardingParams) { function closeAccountDialog() { if (signingOut) return; setActiveAccountDialog(null); - const returnTarget = dialogReturnTarget.current; + const returnTarget = dialogReturnTargetRef.current; window.requestAnimationFrame(() => returnTarget?.focus()); } @@ -337,7 +323,7 @@ export function useProfileOnboarding(params: ProfileOnboardingParams) { setAccountError(""); try { const savedProfile = await persistProfile(profileDraft); - birthTimeRevisionPending.current = false; + birthTimeRevisionPendingRef.current = false; setProfile(savedProfile); setProfileDraft(savedProfile); setPresetMessageLength(0); @@ -353,7 +339,7 @@ export function useProfileOnboarding(params: ProfileOnboardingParams) { } function editDeclaredBirthTimeDetails() { - birthTimeRevisionPending.current = true; + birthTimeRevisionPendingRef.current = true; setBirthTimeError(""); setPresetMessageLength(0); setOnboardingStep("birth"); @@ -423,10 +409,17 @@ export function useProfileOnboarding(params: ProfileOnboardingParams) { } } - guidedBirthTimeReadyRef.current = completeGuidedBirthTime; - editDeclaredBirthTimeDetailsRef.current = editDeclaredBirthTimeDetails; return { + profile, + setProfile, + profileDraft, + setProfileDraft, + profileNotice, + setProfileNotice, + avatarNotice, + avatarSaving, + profileSaving, refreshAccount, openAccountDialog, closeAccountDialog,