/** * GetStartedView - Main view for the get-started flow * * Supports multiple handoff scenarios from eligibility check: * * 1. Verified handoff (?verified=true): * - User already completed OTP on eligibility page * - SessionStorage has: sessionToken, accountStatus, prefill, email * - Skip directly to complete-account step * * 2. Unverified handoff (?handoff=true): * - User came from eligibility check success page or SF email * - Email is pre-filled but NOT verified yet * - User must complete OTP verification * - SessionStorage may have: handoff-token for prefill data */ "use client"; import { useState, useCallback, useEffect } from "react"; import { useSearchParams } from "next/navigation"; import { AuthLayout } from "@/components/templates/AuthLayout"; import { GetStartedForm } from "../components"; import { getSafeRedirect } from "@/features/auth/utils/route-protection"; import { useGetStartedStore, type GetStartedStep, type GetStartedAddress, } from "../stores/get-started.store"; import type { AccountStatus, VerifyCodeResponse } from "@customer-portal/domain/get-started"; // Session data staleness threshold (15 minutes) const SESSION_STALE_THRESHOLD_MS = 15 * 60 * 1000; // SessionStorage key constants const SESSION_KEY_TOKEN = "get-started-session-token"; const SESSION_KEY_ACCOUNT_STATUS = "get-started-account-status"; const SESSION_KEY_PREFILL = "get-started-prefill"; const SESSION_KEY_EMAIL = "get-started-email"; const SESSION_KEY_TIMESTAMP = "get-started-timestamp"; const SESSION_KEY_HANDOFF_TOKEN = "get-started-handoff-token"; function clearGetStartedSessionStorage(): void { sessionStorage.removeItem(SESSION_KEY_TOKEN); sessionStorage.removeItem(SESSION_KEY_ACCOUNT_STATUS); sessionStorage.removeItem(SESSION_KEY_PREFILL); sessionStorage.removeItem(SESSION_KEY_EMAIL); sessionStorage.removeItem(SESSION_KEY_TIMESTAMP); sessionStorage.removeItem(SESSION_KEY_HANDOFF_TOKEN); } interface SessionData { sessionToken: string | null; accountStatus: string | null; prefillRaw: string | null; email: string | null; timestamp: string | null; } function readVerifiedHandoffData(): SessionData { return { sessionToken: sessionStorage.getItem(SESSION_KEY_TOKEN), accountStatus: sessionStorage.getItem(SESSION_KEY_ACCOUNT_STATUS), prefillRaw: sessionStorage.getItem(SESSION_KEY_PREFILL), email: sessionStorage.getItem(SESSION_KEY_EMAIL), timestamp: sessionStorage.getItem(SESSION_KEY_TIMESTAMP), }; } function isSessionStale(timestamp: string | null): boolean { if (!timestamp) return true; return Date.now() - Number.parseInt(timestamp, 10) > SESSION_STALE_THRESHOLD_MS; } function parsePrefillData(prefillRaw: string | null): VerifyCodeResponse["prefill"] | null { if (!prefillRaw) return null; try { return JSON.parse(prefillRaw); } catch { return null; } } export function GetStartedView(): React.JSX.Element { const searchParams = useSearchParams(); const { updateFormData, goToStep, setHandoffToken, setSessionToken, setAccountStatus, setPrefill, setRedirectTo, } = useGetStartedStore(); const [meta, setMeta] = useState({ title: "Get Started", subtitle: "Enter your email to begin", }); const [initialized, setInitialized] = useState(false); useEffect(() => { if (initialized) return; handleRedirectParam(); const handledVerified = handleVerifiedHandoff(); if (!handledVerified) { handleUnverifiedHandoff(); } setInitialized(true); function handleRedirectParam(): void { const redirectParam = searchParams.get("redirect"); if (redirectParam) { const safeRedirect = getSafeRedirect(redirectParam, "/account/dashboard"); setRedirectTo(safeRedirect); } } function handleVerifiedHandoff(): boolean { const verifiedParam = searchParams.get("verified"); if (verifiedParam !== "true") return false; const sessionData = readVerifiedHandoffData(); clearGetStartedSessionStorage(); if (!sessionData.sessionToken || isSessionStale(sessionData.timestamp)) { return false; } setSessionToken(sessionData.sessionToken); if (sessionData.accountStatus) { setAccountStatus(sessionData.accountStatus as AccountStatus); } const prefill = parsePrefillData(sessionData.prefillRaw); if (prefill) { setPrefill(prefill); updateFormData({ email: sessionData.email || prefill.email || "", firstName: prefill.firstName || "", lastName: prefill.lastName || "", phone: prefill.phone || "", address: (prefill.address as GetStartedAddress) || {}, }); } else if (sessionData.email) { updateFormData({ email: sessionData.email }); } goToStep("complete-account"); return true; } function handleUnverifiedHandoff(): void { const emailParam = searchParams.get("email"); const handoffParam = searchParams.get("handoff"); const storedHandoffToken = sessionStorage.getItem(SESSION_KEY_HANDOFF_TOKEN); const storedEmail = sessionStorage.getItem(SESSION_KEY_EMAIL); sessionStorage.removeItem(SESSION_KEY_HANDOFF_TOKEN); sessionStorage.removeItem(SESSION_KEY_EMAIL); const email = emailParam || storedEmail; const isHandoff = handoffParam === "true" || !!storedHandoffToken; if (email && isHandoff) { updateFormData({ email }); if (storedHandoffToken) { setHandoffToken(storedHandoffToken); } } else if (email) { updateFormData({ email }); } } }, [ initialized, searchParams, updateFormData, setHandoffToken, goToStep, setSessionToken, setAccountStatus, setPrefill, setRedirectTo, ]); const handleStepChange = useCallback( (_step: GetStartedStep, stepMeta: { title: string; subtitle: string }) => { setMeta(stepMeta); }, [] ); return ( ); } export default GetStartedView;