barsa d9734b0c82 Enhance Signup Workflow and Update Catalog Components
- Refactored the SignupWorkflowService to throw a DomainHttpException for legacy account conflicts, improving error handling.
- Updated the SignupForm component to include initialEmail and showFooterLinks props, enhancing user experience during account creation.
- Improved the AccountStep in the SignupForm to allow users to add optional details, such as date of birth and gender, for a more personalized signup process.
- Enhanced the PasswordStep to include terms acceptance and marketing consent options, ensuring compliance and user engagement.
- Updated various catalog views to improve layout and user guidance, streamlining the onboarding process for new users.
2025-12-22 18:59:38 +09:00

307 lines
9.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Opportunity Domain - Schemas
*
* Zod schemas for runtime validation of Opportunity data.
* Includes cancellation form validation with deadline logic.
*/
import { z } from "zod";
// ============================================================================
// Enum Value Arrays (for Zod schemas)
// ============================================================================
/**
* Opportunity stage values - match existing Salesforce picklist
*/
const OPPORTUNITY_STAGE_VALUES = [
"Introduction",
"WIKI",
"Ready",
"Post Processing",
"Active",
"△Cancelling",
"Cancelled",
"Completed",
"Void",
"Pending",
] as const;
/**
* Application stage values
*/
const APPLICATION_STAGE_VALUES = ["INTRO-1", "N/A"] as const;
/**
* Cancellation notice values
*/
const CANCELLATION_NOTICE_VALUES = ["有", "未", "不要", "移転"] as const;
/**
* Line return status values
*/
const LINE_RETURN_STATUS_VALUES = [
"NotYet",
"SentKit",
"AS/Pickup予定",
"Returned1",
"Returned2",
"NTT派遣",
"Compensated",
"N/A",
] as const;
const OPPORTUNITY_PRODUCT_TYPE_VALUES = ["Internet", "SIM", "VPN"] as const;
/**
* Commodity type values - existing Salesforce picklist
*/
const COMMODITY_TYPE_VALUES = [
"Personal SonixNet Home Internet",
"Corporate SonixNet Home Internet",
"SIM",
"VPN",
"Onsite Support",
] as const;
const OPPORTUNITY_SOURCE_VALUES = [
"Portal - Internet Eligibility Request",
"Portal - SIM Checkout Registration",
"Portal - Order Placement",
"Agent Created",
] as const;
// ============================================================================
// Opportunity Record Schema
// ============================================================================
/**
* Schema for Opportunity record returned from Salesforce
*/
export const opportunityRecordSchema = z.object({
id: z.string(),
name: z.string(),
accountId: z.string(),
stage: z.enum(OPPORTUNITY_STAGE_VALUES),
closeDate: z.string(),
commodityType: z.enum(COMMODITY_TYPE_VALUES).optional(),
productType: z.enum(OPPORTUNITY_PRODUCT_TYPE_VALUES).optional(), // Derived from commodityType
source: z.enum(OPPORTUNITY_SOURCE_VALUES).optional(),
applicationStage: z.enum(APPLICATION_STAGE_VALUES).optional(),
isClosed: z.boolean(),
// Linked entities
// Note: Cases and Orders link TO Opportunity via their OpportunityId field
whmcsServiceId: z.number().int().optional(),
// Cancellation fields (updated by CS when processing cancellation Case)
cancellationNotice: z.enum(CANCELLATION_NOTICE_VALUES).optional(),
scheduledCancellationDate: z.string().optional(),
lineReturnStatus: z.enum(LINE_RETURN_STATUS_VALUES).optional(),
// NOTE: alternativeContactEmail and cancellationComments are on the Cancellation Case
// Metadata
createdDate: z.string(),
lastModifiedDate: z.string(),
});
export type OpportunityRecord = z.infer<typeof opportunityRecordSchema>;
// ============================================================================
// Create Opportunity Request Schema
// ============================================================================
/**
* Schema for creating a new Opportunity
*
* Note: Opportunity Name is auto-generated by Salesforce workflow,
* so we don't need to provide account name.
*/
export const createOpportunityRequestSchema = z.object({
accountId: z.string().min(15, "Salesforce Account ID must be at least 15 characters"),
productType: z.enum(OPPORTUNITY_PRODUCT_TYPE_VALUES),
stage: z.enum(OPPORTUNITY_STAGE_VALUES),
source: z.enum(OPPORTUNITY_SOURCE_VALUES),
applicationStage: z.enum(APPLICATION_STAGE_VALUES).optional(),
closeDate: z.string().optional(),
// Note: Create Case separately with Case.OpportunityId = returned Opportunity ID
});
export type CreateOpportunityRequest = z.infer<typeof createOpportunityRequestSchema>;
// ============================================================================
// Update Stage Request Schema
// ============================================================================
/**
* Schema for updating Opportunity stage
*/
export const updateOpportunityStageRequestSchema = z.object({
opportunityId: z.string().min(15, "Salesforce Opportunity ID must be at least 15 characters"),
stage: z.enum(OPPORTUNITY_STAGE_VALUES),
reason: z.string().optional(),
});
export type UpdateOpportunityStageRequest = z.infer<typeof updateOpportunityStageRequestSchema>;
// ============================================================================
// Cancellation Form Schema
// ============================================================================
/**
* Regex for YYYY-MM format
*/
const CANCELLATION_MONTH_REGEX = /^\d{4}-(0[1-9]|1[0-2])$/;
/**
* Schema for cancellation form data from customer
*
* Validates:
* - cancellationMonth is in YYYY-MM format
* - Both confirmations are checked
* - Email is valid if provided
*/
export const cancellationFormDataSchema = z
.object({
cancellationMonth: z
.string()
.regex(CANCELLATION_MONTH_REGEX, "Cancellation month must be in YYYY-MM format"),
confirmTermsRead: z.boolean(),
confirmMonthEndCancellation: z.boolean(),
alternativeEmail: z.string().email().optional().or(z.literal("")),
comments: z.string().max(2000, "Comments must be 2000 characters or less").optional(),
})
.refine(data => data.confirmTermsRead === true, {
message: "You must confirm you have read the cancellation terms",
path: ["confirmTermsRead"],
})
.refine(data => data.confirmMonthEndCancellation === true, {
message: "You must confirm you understand cancellation is at month end",
path: ["confirmMonthEndCancellation"],
});
export type CancellationFormData = z.infer<typeof cancellationFormDataSchema>;
/**
* Schema for cancellation data to populate on Opportunity
*/
export const cancellationOpportunityDataSchema = z.object({
scheduledCancellationDate: z.string(),
cancellationNotice: z.enum(CANCELLATION_NOTICE_VALUES),
lineReturnStatus: z.enum(LINE_RETURN_STATUS_VALUES),
alternativeEmail: z.string().email().optional(),
comments: z.string().max(2000).optional(),
});
export type CancellationOpportunityData = z.infer<typeof cancellationOpportunityDataSchema>;
// ============================================================================
// Cancellation Eligibility Schema
// ============================================================================
/**
* Schema for a cancellation month option
*/
export const cancellationMonthOptionSchema = z.object({
value: z.string(),
label: z.string(),
serviceEndDate: z.string(),
rentalReturnDeadline: z.string(),
isCurrentMonth: z.boolean(),
});
export type CancellationMonthOption = z.infer<typeof cancellationMonthOptionSchema>;
/**
* Schema for cancellation eligibility check result
*/
export const cancellationEligibilitySchema = z.object({
canCancel: z.boolean(),
earliestCancellationMonth: z.string(),
availableMonths: z.array(cancellationMonthOptionSchema),
currentMonthDeadline: z.string().nullable(),
reason: z.string().optional(),
});
export type CancellationEligibility = z.infer<typeof cancellationEligibilitySchema>;
// ============================================================================
// Cancellation Status Schema
// ============================================================================
/**
* Schema for cancellation status display
*/
export const cancellationStatusSchema = z.object({
stage: z.enum(OPPORTUNITY_STAGE_VALUES),
isPending: z.boolean(),
isComplete: z.boolean(),
scheduledEndDate: z.string().optional(),
rentalReturnStatus: z.enum(LINE_RETURN_STATUS_VALUES).optional(),
rentalReturnDeadline: z.string().optional(),
hasRentalEquipment: z.boolean(),
});
export type CancellationStatus = z.infer<typeof cancellationStatusSchema>;
// ============================================================================
// Opportunity Lookup Schema
// ============================================================================
/**
* Schema for Opportunity lookup criteria
*/
export const opportunityLookupCriteriaSchema = z.object({
accountId: z.string().min(15),
productType: z.enum(OPPORTUNITY_PRODUCT_TYPE_VALUES),
allowedStages: z.array(z.enum(OPPORTUNITY_STAGE_VALUES)).optional(),
});
export type OpportunityLookupCriteria = z.infer<typeof opportunityLookupCriteriaSchema>;
// ============================================================================
// Opportunity Match Result Schema
// ============================================================================
/**
* Schema for Opportunity matching result
*/
export const opportunityMatchResultSchema = z.object({
opportunityId: z.string(),
wasCreated: z.boolean(),
previousStage: z.enum(OPPORTUNITY_STAGE_VALUES).optional(),
});
export type OpportunityMatchResult = z.infer<typeof opportunityMatchResultSchema>;
// ============================================================================
// API Response Schemas
// ============================================================================
/**
* Schema for Opportunity API response
*/
export const opportunityResponseSchema = z.object({
opportunity: opportunityRecordSchema,
});
/**
* Schema for create Opportunity response
*/
export const createOpportunityResponseSchema = z.object({
opportunityId: z.string(),
stage: z.enum(OPPORTUNITY_STAGE_VALUES),
});
/**
* Schema for cancellation preview response
*/
export const cancellationPreviewResponseSchema = z.object({
eligibility: cancellationEligibilitySchema,
terms: z.object({
deadlineDay: z.number(),
rentalReturnDeadlineDay: z.number(),
fullMonthCharge: z.boolean(),
}),
});