Merge pull request 'feat: implement session persistence for onboarding flow and fix typo in password label' (#3) from fix-onboarding into development
Reviewed-on: https://gitea.2pidev.com/jselesan/playzer/pulls/3
This commit is contained in:
@@ -14,3 +14,4 @@ BETTER_AUTH_SECRET=R1MfeeeekXSNdE65hOhhr0Mt0KLwczYr
|
|||||||
BETTER_AUTH_URL=http://localhost:3000
|
BETTER_AUTH_URL=http://localhost:3000
|
||||||
MAILTRAP_API_TOKEN=2c040e2e577dc48909d3206dd2d47fc6
|
MAILTRAP_API_TOKEN=2c040e2e577dc48909d3206dd2d47fc6
|
||||||
MAIL_SANDBOX=true
|
MAIL_SANDBOX=true
|
||||||
|
MAIL_INBOX_ID=1114587
|
||||||
@@ -34,7 +34,7 @@ export function OnboardingCompleteStep({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<Field data-invalid={Boolean(form.formState.errors.password)}>
|
<Field data-invalid={Boolean(form.formState.errors.password)}>
|
||||||
<FieldLabel htmlFor="onboard-password">Contrasena</FieldLabel>
|
<FieldLabel htmlFor="onboard-password">Contraseña</FieldLabel>
|
||||||
<Input
|
<Input
|
||||||
id="onboard-password"
|
id="onboard-password"
|
||||||
type="password"
|
type="password"
|
||||||
|
|||||||
@@ -19,10 +19,72 @@ import {
|
|||||||
} from '@repo/api-contract';
|
} from '@repo/api-contract';
|
||||||
import { useNavigate } from '@tanstack/react-router';
|
import { useNavigate } from '@tanstack/react-router';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm, useWatch } from 'react-hook-form';
|
||||||
|
|
||||||
const OTP_MAX_ATTEMPTS = 5;
|
const OTP_MAX_ATTEMPTS = 5;
|
||||||
type OnboardStep = 'start' | 'verify-otp' | 'complete';
|
type OnboardStep = 'start' | 'verify-otp' | 'complete';
|
||||||
|
const ONBOARD_SESSION_KEY = 'playzer:onboarding-session';
|
||||||
|
|
||||||
|
type OnboardSessionState = {
|
||||||
|
step: OnboardStep;
|
||||||
|
requestId: string | null;
|
||||||
|
onboardingEmail: string | null;
|
||||||
|
verifiedEmail: string | null;
|
||||||
|
remainingAttempts: number;
|
||||||
|
resendCooldownSeconds: number;
|
||||||
|
startForm: StartValues;
|
||||||
|
verifyOtpForm: VerifyOtpValues;
|
||||||
|
completeForm: CompleteValues;
|
||||||
|
};
|
||||||
|
|
||||||
|
function isOnboardStep(value: unknown): value is OnboardStep {
|
||||||
|
return value === 'start' || value === 'verify-otp' || value === 'complete';
|
||||||
|
}
|
||||||
|
|
||||||
|
function readOnboardSession(): OnboardSessionState | null {
|
||||||
|
if (typeof window === 'undefined') return null;
|
||||||
|
|
||||||
|
const raw = window.sessionStorage.getItem(ONBOARD_SESSION_KEY);
|
||||||
|
if (!raw) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(raw) as Partial<OnboardSessionState>;
|
||||||
|
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() {
|
export function OnboardPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -38,6 +100,7 @@ export function OnboardPage() {
|
|||||||
const [isResending, setIsResending] = useState(false);
|
const [isResending, setIsResending] = useState(false);
|
||||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||||
const [infoMessage, setInfoMessage] = useState<string | null>(null);
|
const [infoMessage, setInfoMessage] = useState<string | null>(null);
|
||||||
|
const [isSessionRestored, setIsSessionRestored] = useState(false);
|
||||||
|
|
||||||
const startForm = useForm<StartValues>({
|
const startForm = useForm<StartValues>({
|
||||||
resolver: zodResolver(onboardingStartSchema),
|
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(() => {
|
useEffect(() => {
|
||||||
if (step !== 'complete') return;
|
if (step !== 'complete') return;
|
||||||
|
|
||||||
@@ -189,6 +314,7 @@ export function OnboardPage() {
|
|||||||
await signInWithPassword({ email: emailForSignIn, password: values.password });
|
await signInWithPassword({ email: emailForSignIn, password: values.password });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearOnboardSession();
|
||||||
await navigate({ to: '/complex/$slug/edit', params: { slug: result.complexSlug } });
|
await navigate({ to: '/complex/$slug/edit', params: { slug: result.complexSlug } });
|
||||||
} catch {
|
} catch {
|
||||||
setErrorMessage('No pudimos completar el onboarding. Intenta nuevamente.');
|
setErrorMessage('No pudimos completar el onboarding. Intenta nuevamente.');
|
||||||
|
|||||||
Reference in New Issue
Block a user