232 lines
6.9 KiB
TypeScript
232 lines
6.9 KiB
TypeScript
/**
|
|
* Get Started Domain - Schemas
|
|
*
|
|
* Zod validation schemas for the unified "Get Started" flow.
|
|
*/
|
|
|
|
import { z } from "zod";
|
|
|
|
import {
|
|
emailSchema,
|
|
nameSchema,
|
|
passwordSchema,
|
|
phoneSchema,
|
|
genderEnum,
|
|
} from "../common/schema.js";
|
|
import { addressFormSchema } from "../customer/schema.js";
|
|
|
|
// ============================================================================
|
|
// OTP Verification Schemas
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Request to send a verification code to an email address
|
|
*/
|
|
export const sendVerificationCodeRequestSchema = z.object({
|
|
email: emailSchema,
|
|
});
|
|
|
|
/**
|
|
* Response after sending verification code
|
|
*/
|
|
export const sendVerificationCodeResponseSchema = z.object({
|
|
/** Whether the code was sent successfully */
|
|
sent: z.boolean(),
|
|
/** Message to display to user */
|
|
message: z.string(),
|
|
/** When a new code can be requested (ISO timestamp) */
|
|
retryAfter: z.string().datetime().optional(),
|
|
});
|
|
|
|
/**
|
|
* 6-digit OTP code schema
|
|
*/
|
|
export const otpCodeSchema = z
|
|
.string()
|
|
.length(6, "Code must be 6 digits")
|
|
.regex(/^\d{6}$/, "Code must be 6 digits");
|
|
|
|
/**
|
|
* Request to verify an OTP code
|
|
*/
|
|
export const verifyCodeRequestSchema = z.object({
|
|
email: emailSchema,
|
|
code: otpCodeSchema,
|
|
});
|
|
|
|
/**
|
|
* Account status enum for verification response
|
|
*/
|
|
export const accountStatusSchema = z.enum([
|
|
"portal_exists",
|
|
"whmcs_unmapped",
|
|
"sf_unmapped",
|
|
"new_customer",
|
|
]);
|
|
|
|
/**
|
|
* Response after verifying OTP code
|
|
* Includes account status to determine next flow
|
|
*/
|
|
export const verifyCodeResponseSchema = z.object({
|
|
/** Whether the code was valid */
|
|
verified: z.boolean(),
|
|
/** Error message if verification failed */
|
|
error: z.string().optional(),
|
|
/** Remaining attempts if verification failed */
|
|
attemptsRemaining: z.number().optional(),
|
|
/** Session token for continuing the flow (only if verified) */
|
|
sessionToken: z.string().optional(),
|
|
/** Account status determining next flow (only if verified) */
|
|
accountStatus: accountStatusSchema.optional(),
|
|
/** Pre-filled data from existing account (only if SF or WHMCS exists) */
|
|
prefill: z
|
|
.object({
|
|
firstName: z.string().optional(),
|
|
lastName: z.string().optional(),
|
|
email: z.string().optional(),
|
|
phone: z.string().optional(),
|
|
address: addressFormSchema.partial().optional(),
|
|
eligibilityStatus: z.string().optional(),
|
|
})
|
|
.optional(),
|
|
});
|
|
|
|
// ============================================================================
|
|
// Quick Eligibility Check Schemas
|
|
// ============================================================================
|
|
|
|
/**
|
|
* ISO date string (YYYY-MM-DD)
|
|
*/
|
|
const isoDateOnlySchema = z
|
|
.string()
|
|
.regex(/^\d{4}-\d{2}-\d{2}$/, "Enter a valid date (YYYY-MM-DD)")
|
|
.refine(value => !Number.isNaN(Date.parse(value)), "Enter a valid date (YYYY-MM-DD)");
|
|
|
|
/**
|
|
* Request for quick eligibility check (guest flow)
|
|
* Minimal data required to create SF Account and check eligibility
|
|
*/
|
|
export const quickEligibilityRequestSchema = z.object({
|
|
/** Session token from email verification */
|
|
sessionToken: z.string().min(1, "Session token is required"),
|
|
/** Customer first name */
|
|
firstName: nameSchema,
|
|
/** Customer last name */
|
|
lastName: nameSchema,
|
|
/** Full address for eligibility check */
|
|
address: addressFormSchema,
|
|
/** Optional phone number */
|
|
phone: phoneSchema.optional(),
|
|
});
|
|
|
|
/**
|
|
* Response from quick eligibility check
|
|
*/
|
|
export const quickEligibilityResponseSchema = z.object({
|
|
/** Whether the request was submitted successfully */
|
|
submitted: z.boolean(),
|
|
/** Case ID for the eligibility request */
|
|
requestId: z.string().optional(),
|
|
/** SF Account ID created */
|
|
sfAccountId: z.string().optional(),
|
|
/** Message to display */
|
|
message: z.string(),
|
|
});
|
|
|
|
// ============================================================================
|
|
// Account Completion Schemas
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Request to complete account for SF-only users
|
|
* Creates WHMCS client and Portal user, links to existing SF Account
|
|
*/
|
|
export const completeAccountRequestSchema = z.object({
|
|
/** Session token from verified email */
|
|
sessionToken: z.string().min(1, "Session token is required"),
|
|
/** Password for the new portal account */
|
|
password: passwordSchema,
|
|
/** Phone number (may be pre-filled from SF) */
|
|
phone: phoneSchema,
|
|
/** Date of birth */
|
|
dateOfBirth: isoDateOnlySchema,
|
|
/** Gender */
|
|
gender: genderEnum,
|
|
/** Accept terms of service */
|
|
acceptTerms: z.boolean().refine(val => val === true, {
|
|
message: "You must accept the terms of service",
|
|
}),
|
|
/** Marketing consent */
|
|
marketingConsent: z.boolean().optional(),
|
|
});
|
|
|
|
// ============================================================================
|
|
// "Maybe Later" Flow Schemas
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Request for "Maybe Later" flow
|
|
* Creates SF Account and eligibility case, customer can return later
|
|
*/
|
|
export const maybeLaterRequestSchema = z.object({
|
|
/** Session token from email verification */
|
|
sessionToken: z.string().min(1, "Session token is required"),
|
|
/** Customer first name */
|
|
firstName: nameSchema,
|
|
/** Customer last name */
|
|
lastName: nameSchema,
|
|
/** Full address for eligibility check */
|
|
address: addressFormSchema,
|
|
/** Optional phone number */
|
|
phone: phoneSchema.optional(),
|
|
});
|
|
|
|
/**
|
|
* Response from "Maybe Later" flow
|
|
*/
|
|
export const maybeLaterResponseSchema = z.object({
|
|
/** Whether the SF account and case were created */
|
|
success: z.boolean(),
|
|
/** Case ID for the eligibility request */
|
|
requestId: z.string().optional(),
|
|
/** Message to display */
|
|
message: z.string(),
|
|
});
|
|
|
|
// ============================================================================
|
|
// Session Schema
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Get Started session stored in Redis
|
|
* Tracks progress through the unified flow
|
|
*/
|
|
export const getStartedSessionSchema = z.object({
|
|
/** Email address (normalized) */
|
|
email: z.string(),
|
|
/** Whether email has been verified via OTP */
|
|
emailVerified: z.boolean(),
|
|
/** First name (if provided during quick check) */
|
|
firstName: z.string().optional(),
|
|
/** Last name (if provided during quick check) */
|
|
lastName: z.string().optional(),
|
|
/** Address (if provided during quick check) */
|
|
address: addressFormSchema.partial().optional(),
|
|
/** Phone number (if provided) */
|
|
phone: z.string().optional(),
|
|
/** Account status after verification */
|
|
accountStatus: accountStatusSchema.optional(),
|
|
/** SF Account ID (if exists or created) */
|
|
sfAccountId: z.string().optional(),
|
|
/** WHMCS Client ID (if exists) */
|
|
whmcsClientId: z.number().optional(),
|
|
/** Eligibility status from SF */
|
|
eligibilityStatus: z.string().optional(),
|
|
/** Session creation timestamp */
|
|
createdAt: z.string().datetime(),
|
|
/** Session expiry timestamp */
|
|
expiresAt: z.string().datetime(),
|
|
});
|