179 lines
6.0 KiB
TypeScript
Raw Normal View History

/**
* 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 {
useGetStartedStore,
type GetStartedStep,
type GetStartedAddress,
} from "../stores/get-started.store";
import type { AccountStatus, VerifyCodeResponse } from "@customer-portal/domain/get-started";
// Session data staleness threshold (5 minutes)
const SESSION_STALE_THRESHOLD_MS = 5 * 60 * 1000;
export function GetStartedView() {
const searchParams = useSearchParams();
const {
updateFormData,
goToStep,
setHandoffToken,
setSessionToken,
setAccountStatus,
setPrefill,
} = useGetStartedStore();
const [meta, setMeta] = useState({
title: "Get Started",
subtitle: "Enter your email to begin",
});
const [initialized, setInitialized] = useState(false);
// Helper to clear all get-started sessionStorage items
const clearGetStartedSessionStorage = () => {
sessionStorage.removeItem("get-started-session-token");
sessionStorage.removeItem("get-started-account-status");
sessionStorage.removeItem("get-started-prefill");
sessionStorage.removeItem("get-started-email");
sessionStorage.removeItem("get-started-timestamp");
sessionStorage.removeItem("get-started-handoff-token");
};
// Check for handoff from eligibility check on mount
useEffect(() => {
if (initialized) return;
// Check for verified handoff (user already completed OTP on eligibility page)
const verifiedParam = searchParams.get("verified");
if (verifiedParam === "true") {
// Read all session data at once
const storedSessionToken = sessionStorage.getItem("get-started-session-token");
const storedAccountStatus = sessionStorage.getItem("get-started-account-status");
const storedPrefillRaw = sessionStorage.getItem("get-started-prefill");
const storedEmail = sessionStorage.getItem("get-started-email");
const storedTimestamp = sessionStorage.getItem("get-started-timestamp");
// Validate timestamp to prevent stale data
const isStale =
!storedTimestamp ||
Date.now() - Number.parseInt(storedTimestamp, 10) > SESSION_STALE_THRESHOLD_MS;
// Clear sessionStorage immediately after reading
clearGetStartedSessionStorage();
if (storedSessionToken && !isStale) {
// Parse prefill data
let prefill: VerifyCodeResponse["prefill"] | null = null;
if (storedPrefillRaw) {
try {
prefill = JSON.parse(storedPrefillRaw);
} catch {
// Ignore parse errors
}
}
// Set session data in store
setSessionToken(storedSessionToken);
if (storedAccountStatus) {
setAccountStatus(storedAccountStatus as AccountStatus);
}
if (prefill) {
setPrefill(prefill);
// Also update form data with prefill
updateFormData({
email: storedEmail || prefill.email || "",
firstName: prefill.firstName || "",
lastName: prefill.lastName || "",
phone: prefill.phone || "",
address: (prefill.address as GetStartedAddress) || {},
});
} else if (storedEmail) {
updateFormData({ email: storedEmail });
}
// Skip directly to complete-account (email already verified)
goToStep("complete-account");
setInitialized(true);
return;
}
// If stale or no token, fall through to normal flow
}
// Check for unverified handoff (from success page CTA or SF email)
const emailParam = searchParams.get("email");
const handoffParam = searchParams.get("handoff");
const storedHandoffToken = sessionStorage.getItem("get-started-handoff-token");
const storedEmail = sessionStorage.getItem("get-started-email");
// Clear handoff-related sessionStorage after reading
sessionStorage.removeItem("get-started-handoff-token");
sessionStorage.removeItem("get-started-email");
const email = emailParam || storedEmail;
const isHandoff = handoffParam === "true" || !!storedHandoffToken;
if (email && isHandoff) {
// User came from eligibility check - email is NOT verified yet
// Pre-fill email and let user verify via OTP
updateFormData({ email });
// Store handoff token if available - will be used during OTP verification
if (storedHandoffToken) {
setHandoffToken(storedHandoffToken);
}
// Stay at email step - user needs to verify their email
// Don't call goToStep - let the form start at its default step
// The email is pre-filled so user just clicks "Send Code"
} else if (email) {
// Just email param (from SF email link without handoff)
updateFormData({ email });
}
setInitialized(true);
}, [
initialized,
searchParams,
updateFormData,
setHandoffToken,
goToStep,
setSessionToken,
setAccountStatus,
setPrefill,
]);
const handleStepChange = useCallback(
(_step: GetStartedStep, stepMeta: { title: string; subtitle: string }) => {
setMeta(stepMeta);
},
[]
);
return (
<AuthLayout title={meta.title} subtitle={meta.subtitle} wide>
<GetStartedForm onStepChange={handleStepChange} />
</AuthLayout>
);
}
export default GetStartedView;