From 400f67b268231cbf2015f18e16b81b51626a7831 Mon Sep 17 00:00:00 2001 From: Jose Selesan Date: Fri, 17 Apr 2026 16:03:40 -0300 Subject: [PATCH] feat: implement session persistence for onboarding flow and fix typo in password label --- apps/backend/.env | 3 +- .../components/onboarding-complete-step.tsx | 2 +- .../src/features/onboard/onboard-page.tsx | 128 +++++++++++++++++- 3 files changed, 130 insertions(+), 3 deletions(-) diff --git a/apps/backend/.env b/apps/backend/.env index d18f69a..ea6a38b 100644 --- a/apps/backend/.env +++ b/apps/backend/.env @@ -13,4 +13,5 @@ SMTP_FROM=Playzer BETTER_AUTH_SECRET=R1MfeeeekXSNdE65hOhhr0Mt0KLwczYr BETTER_AUTH_URL=http://localhost:3000 MAILTRAP_API_TOKEN=2c040e2e577dc48909d3206dd2d47fc6 -MAIL_SANDBOX=true \ No newline at end of file +MAIL_SANDBOX=true +MAIL_INBOX_ID=1114587 \ No newline at end of file diff --git a/apps/frontend/src/features/onboard/components/onboarding-complete-step.tsx b/apps/frontend/src/features/onboard/components/onboarding-complete-step.tsx index f9bd31a..99a2f6d 100644 --- a/apps/frontend/src/features/onboard/components/onboarding-complete-step.tsx +++ b/apps/frontend/src/features/onboard/components/onboarding-complete-step.tsx @@ -34,7 +34,7 @@ export function OnboardingCompleteStep({ )} - Contrasena + ContraseƱa ; + if (!isOnboardStep(parsed.step)) return null; + + return { + step: parsed.step, + requestId: typeof parsed.requestId === 'string' ? parsed.requestId : null, + onboardingEmail: typeof parsed.onboardingEmail === 'string' ? parsed.onboardingEmail : null, + verifiedEmail: typeof parsed.verifiedEmail === 'string' ? parsed.verifiedEmail : null, + remainingAttempts: + typeof parsed.remainingAttempts === 'number' + ? parsed.remainingAttempts + : OTP_MAX_ATTEMPTS, + resendCooldownSeconds: + typeof parsed.resendCooldownSeconds === 'number' ? parsed.resendCooldownSeconds : 0, + startForm: { + fullName: parsed.startForm?.fullName ?? '', + email: parsed.startForm?.email ?? '', + }, + verifyOtpForm: { + otp: parsed.verifyOtpForm?.otp ?? '', + }, + completeForm: { + password: parsed.completeForm?.password ?? '', + complexName: parsed.completeForm?.complexName ?? '', + physicalAddress: parsed.completeForm?.physicalAddress ?? '', + planCode: parsed.completeForm?.planCode ?? '', + }, + }; + } catch { + return null; + } +} + +function clearOnboardSession() { + if (typeof window === 'undefined') return; + window.sessionStorage.removeItem(ONBOARD_SESSION_KEY); +} export function OnboardPage() { const navigate = useNavigate(); @@ -38,6 +100,7 @@ export function OnboardPage() { const [isResending, setIsResending] = useState(false); const [errorMessage, setErrorMessage] = useState(null); const [infoMessage, setInfoMessage] = useState(null); + const [isSessionRestored, setIsSessionRestored] = useState(false); const startForm = useForm({ resolver: zodResolver(onboardingStartSchema), @@ -67,6 +130,68 @@ export function OnboardPage() { }, }); + const startValues = useWatch({ control: startForm.control }); + const verifyOtpValues = useWatch({ control: verifyOtpForm.control }); + const completeValues = useWatch({ control: completeForm.control }); + + useEffect(() => { + const savedSession = readOnboardSession(); + if (!savedSession) { + setIsSessionRestored(true); + return; + } + + setStep(savedSession.step); + setRequestId(savedSession.requestId); + setOnboardingEmail(savedSession.onboardingEmail); + setVerifiedEmail(savedSession.verifiedEmail); + setRemainingAttempts(savedSession.remainingAttempts); + setResendCooldownSeconds(savedSession.resendCooldownSeconds); + startForm.reset(savedSession.startForm); + verifyOtpForm.reset(savedSession.verifyOtpForm); + completeForm.reset(savedSession.completeForm); + setIsSessionRestored(true); + }, [startForm, verifyOtpForm, completeForm]); + + useEffect(() => { + if (!isSessionRestored || typeof window === 'undefined') return; + + const sessionState: OnboardSessionState = { + step, + requestId, + onboardingEmail, + verifiedEmail, + remainingAttempts, + resendCooldownSeconds, + startForm: { + fullName: startValues.fullName ?? '', + email: startValues.email ?? '', + }, + verifyOtpForm: { + otp: verifyOtpValues.otp ?? '', + }, + completeForm: { + password: completeValues.password ?? '', + complexName: completeValues.complexName ?? '', + physicalAddress: completeValues.physicalAddress ?? '', + planCode: completeValues.planCode ?? '', + }, + }; + + window.sessionStorage.setItem(ONBOARD_SESSION_KEY, JSON.stringify(sessionState)); + }, [ + completeValues, + isSessionRestored, + onboardingEmail, + remainingAttempts, + requestId, + resendCooldownSeconds, + startValues, + step, + verifiedEmail, + verifyOtpValues, + ]); + useEffect(() => { if (step !== 'complete') return; @@ -189,6 +314,7 @@ export function OnboardPage() { await signInWithPassword({ email: emailForSignIn, password: values.password }); } + clearOnboardSession(); await navigate({ to: '/complex/$slug/edit', params: { slug: result.complexSlug } }); } catch { setErrorMessage('No pudimos completar el onboarding. Intenta nuevamente.');