refactor(onboarding): restructure onboarding routes and components
- Removed obsolete onboarding routes: complete, create-complex, index, verify-email, and verify. - Introduced new setup stepper page for onboarding process. - Created a multi-step setup component to handle complex creation with validation. - Added new schemas for complex creation and plan features. - Implemented detailed steps for complex name, location, plan selection, and court setup. - Enhanced error handling and user feedback during the onboarding process.
This commit is contained in:
@@ -1,5 +1,15 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
const dayOfWeekEnum = z.enum([
|
||||
'MONDAY',
|
||||
'TUESDAY',
|
||||
'WEDNESDAY',
|
||||
'THURSDAY',
|
||||
'FRIDAY',
|
||||
'SATURDAY',
|
||||
'SUNDAY',
|
||||
])
|
||||
|
||||
export const complexSchema = z.object({
|
||||
id: z.uuid(),
|
||||
complexName: z.string(),
|
||||
@@ -14,39 +24,77 @@ export const complexSchema = z.object({
|
||||
updatedAt: z.string().datetime(),
|
||||
})
|
||||
|
||||
export const createComplexSchema = z.object({
|
||||
complexName: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(3, 'El nombre del complejo debe tener al menos 3 caracteres.')
|
||||
.max(120, 'El nombre del complejo no puede superar los 120 caracteres.'),
|
||||
physicalAddress: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(5, 'La direccion debe tener al menos 5 caracteres.')
|
||||
.max(200, 'La direccion no puede superar los 200 caracteres.'),
|
||||
city: z
|
||||
.string()
|
||||
.trim()
|
||||
.max(100, 'La ciudad no puede superar los 100 caracteres.')
|
||||
.optional(),
|
||||
state: z
|
||||
.string()
|
||||
.trim()
|
||||
.max(100, 'La provincia/estado no puede superar los 100 caracteres.')
|
||||
.optional(),
|
||||
country: z
|
||||
.string()
|
||||
.trim()
|
||||
.max(100, 'El país no puede superar los 100 caracteres.')
|
||||
.optional(),
|
||||
planCode: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, 'El planCode no puede estar vacío.')
|
||||
.max(10, 'El planCode no puede superar los 10 caracteres.')
|
||||
.optional(),
|
||||
})
|
||||
export const createComplexSchema = z
|
||||
.object({
|
||||
complexName: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(3, 'El nombre del complejo debe tener al menos 3 caracteres.')
|
||||
.max(120, 'El nombre del complejo no puede superar los 120 caracteres.'),
|
||||
physicalAddress: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(5, 'La direccion debe tener al menos 5 caracteres.')
|
||||
.max(200, 'La direccion no puede superar los 200 caracteres.'),
|
||||
city: z
|
||||
.string()
|
||||
.trim()
|
||||
.max(100, 'La ciudad no puede superar los 100 caracteres.')
|
||||
.optional(),
|
||||
state: z
|
||||
.string()
|
||||
.trim()
|
||||
.max(100, 'La provincia/estado no puede superar los 100 caracteres.')
|
||||
.optional(),
|
||||
country: z
|
||||
.string()
|
||||
.trim()
|
||||
.max(100, 'El país no puede superar los 100 caracteres.')
|
||||
.optional(),
|
||||
planCode: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, 'El planCode no puede estar vacío.')
|
||||
.max(10, 'El planCode no puede superar los 10 caracteres.')
|
||||
.optional(),
|
||||
setupCourts: z.boolean().default(false),
|
||||
courtSportId: z.string().uuid('El deporte seleccionado no es válido.').optional(),
|
||||
courtStartTime: z.string().optional(),
|
||||
courtEndTime: z.string().optional(),
|
||||
courtDaysOfWeek: z.array(dayOfWeekEnum).optional(),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
if (data.setupCourts) {
|
||||
if (!data.courtSportId) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ['courtSportId'],
|
||||
message: 'Debes seleccionar un deporte.',
|
||||
})
|
||||
}
|
||||
if (!data.courtStartTime || !/^\d{2}:\d{2}$/.test(data.courtStartTime)) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ['courtStartTime'],
|
||||
message: 'La hora de inicio debe tener formato HH:mm.',
|
||||
})
|
||||
}
|
||||
if (!data.courtEndTime || !/^\d{2}:\d{2}$/.test(data.courtEndTime)) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ['courtEndTime'],
|
||||
message: 'La hora de fin debe tener formato HH:mm.',
|
||||
})
|
||||
}
|
||||
if (!data.courtDaysOfWeek || data.courtDaysOfWeek.length === 0) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ['courtDaysOfWeek'],
|
||||
message: 'Debes seleccionar al menos un día.',
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const updateComplexSchema = z
|
||||
.object({
|
||||
|
||||
@@ -4,28 +4,18 @@ export {
|
||||
planRulesV1Schema,
|
||||
planSchema,
|
||||
planSummarySchema,
|
||||
planWithFeaturesSchema,
|
||||
planLimitsSchema,
|
||||
} from './plan'
|
||||
export type { Plan, PlanFeatureFlags, PlanRules, PlanRulesV1, PlanSummary } from './plan'
|
||||
export {
|
||||
onboardingCompleteResponseSchema,
|
||||
onboardingCompleteSchema,
|
||||
onboardingResendOtpResponseSchema,
|
||||
onboardingResendOtpSchema,
|
||||
onboardingStartResponseSchema,
|
||||
onboardingStartSchema,
|
||||
onboardingVerifyOtpResponseSchema,
|
||||
onboardingVerifyOtpSchema,
|
||||
} from './onboarding'
|
||||
export type {
|
||||
OnboardingCompleteInput,
|
||||
OnboardingCompleteResponse,
|
||||
OnboardingResendOtpInput,
|
||||
OnboardingResendOtpResponse,
|
||||
OnboardingStartInput,
|
||||
OnboardingStartResponse,
|
||||
OnboardingVerifyOtpInput,
|
||||
OnboardingVerifyOtpResponse,
|
||||
} from './onboarding'
|
||||
Plan,
|
||||
PlanFeatureFlags,
|
||||
PlanRules,
|
||||
PlanRulesV1,
|
||||
PlanSummary,
|
||||
PlanWithFeatures,
|
||||
PlanLimits,
|
||||
} from './plan'
|
||||
export {
|
||||
acceptComplexInvitationsSchema,
|
||||
complexInvitationSchema,
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
const otpSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.regex(/^\d{6}$/, 'El OTP debe tener 6 digitos numericos.')
|
||||
|
||||
export const onboardingStartSchema = z.object({
|
||||
fullName: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(2, 'El nombre debe tener al menos 2 caracteres.')
|
||||
.max(80, 'El nombre no puede superar los 80 caracteres.'),
|
||||
email: z.email('Ingresa un email valido.'),
|
||||
})
|
||||
|
||||
export const onboardingVerifyOtpSchema = z.object({
|
||||
requestId: z.uuid('El requestId de onboarding no es valido.'),
|
||||
otp: otpSchema,
|
||||
})
|
||||
|
||||
export const onboardingResendOtpSchema = z.object({
|
||||
requestId: z.uuid('El requestId de onboarding no es valido.'),
|
||||
})
|
||||
|
||||
export const onboardingCompleteSchema = z.object({
|
||||
onboardingRequestId: z.uuid('El onboardingRequestId no es valido.'),
|
||||
password: z
|
||||
.string()
|
||||
.min(8, 'La contrasena debe tener al menos 8 caracteres.')
|
||||
.max(72, 'La contrasena no puede superar los 72 caracteres.'),
|
||||
complexName: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(3, 'El nombre del complejo debe tener al menos 3 caracteres.')
|
||||
.max(120, 'El nombre del complejo no puede superar los 120 caracteres.'),
|
||||
physicalAddress: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(5, 'La direccion debe tener al menos 5 caracteres.')
|
||||
.max(200, 'La direccion no puede superar los 200 caracteres.'),
|
||||
city: z
|
||||
.string()
|
||||
.trim()
|
||||
.max(100, 'La ciudad no puede superar los 100 caracteres.')
|
||||
.optional(),
|
||||
state: z
|
||||
.string()
|
||||
.trim()
|
||||
.max(100, 'La provincia/estado no puede superar los 100 caracteres.')
|
||||
.optional(),
|
||||
country: z
|
||||
.string()
|
||||
.trim()
|
||||
.max(100, 'El país no puede superar los 100 caracteres.')
|
||||
.optional(),
|
||||
planCode: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, 'Debes seleccionar un plan.')
|
||||
.max(10, 'El plan seleccionado no es valido.'),
|
||||
})
|
||||
|
||||
export const onboardingStartResponseSchema = z.object({
|
||||
message: z.string(),
|
||||
requestId: z.uuid(),
|
||||
email: z.email(),
|
||||
expiresAt: z.string().datetime(),
|
||||
cooldownSeconds: z.int().nonnegative(),
|
||||
})
|
||||
|
||||
export const onboardingVerifyOtpResponseSchema = z.object({
|
||||
message: z.string(),
|
||||
verified: z.boolean(),
|
||||
requestId: z.uuid(),
|
||||
email: z.email().nullable(),
|
||||
expiresAt: z.string().datetime().nullable(),
|
||||
remainingAttempts: z.int().nonnegative(),
|
||||
cooldownSeconds: z.int().nonnegative(),
|
||||
})
|
||||
|
||||
export const onboardingResendOtpResponseSchema = z.object({
|
||||
message: z.string(),
|
||||
requestId: z.uuid(),
|
||||
email: z.email(),
|
||||
expiresAt: z.string().datetime(),
|
||||
cooldownSeconds: z.int().nonnegative(),
|
||||
remainingAttempts: z.int().nonnegative(),
|
||||
})
|
||||
|
||||
export const onboardingCompleteResponseSchema = z.object({
|
||||
message: z.string(),
|
||||
userId: z.uuid(),
|
||||
complexId: z.uuid(),
|
||||
complexSlug: z.string(),
|
||||
})
|
||||
|
||||
export type OnboardingStartInput = z.infer<typeof onboardingStartSchema>
|
||||
export type OnboardingVerifyOtpInput = z.infer<typeof onboardingVerifyOtpSchema>
|
||||
export type OnboardingResendOtpInput = z.infer<typeof onboardingResendOtpSchema>
|
||||
export type OnboardingCompleteInput = z.infer<typeof onboardingCompleteSchema>
|
||||
|
||||
export type OnboardingStartResponse = z.infer<typeof onboardingStartResponseSchema>
|
||||
export type OnboardingVerifyOtpResponse = z.infer<
|
||||
typeof onboardingVerifyOtpResponseSchema
|
||||
>
|
||||
export type OnboardingResendOtpResponse = z.infer<
|
||||
typeof onboardingResendOtpResponseSchema
|
||||
>
|
||||
export type OnboardingCompleteResponse = z.infer<
|
||||
typeof onboardingCompleteResponseSchema
|
||||
>
|
||||
@@ -49,8 +49,25 @@ export const planSummarySchema = z.object({
|
||||
price: z.number().nonnegative(),
|
||||
})
|
||||
|
||||
export const planLimitsSchema = z.object({
|
||||
maxCourts: positiveInt,
|
||||
maxBookingsPerDay: positiveInt,
|
||||
maxActiveUsers: positiveInt.optional(),
|
||||
maxConcurrentBookingsPerSlot: positiveInt.optional(),
|
||||
})
|
||||
|
||||
export const planWithFeaturesSchema = z.object({
|
||||
code: z.string().trim().min(1).max(10),
|
||||
name: z.string().trim().min(1).max(30),
|
||||
price: z.number().nonnegative(),
|
||||
features: planFeatureFlagsSchema,
|
||||
limits: planLimitsSchema,
|
||||
})
|
||||
|
||||
export type PlanFeatureFlags = z.infer<typeof planFeatureFlagsSchema>
|
||||
export type PlanRulesV1 = z.infer<typeof planRulesV1Schema>
|
||||
export type PlanRules = z.infer<typeof planRulesSchema>
|
||||
export type Plan = z.infer<typeof planSchema>
|
||||
export type PlanSummary = z.infer<typeof planSummarySchema>
|
||||
export type PlanWithFeatures = z.infer<typeof planWithFeaturesSchema>
|
||||
export type PlanLimits = z.infer<typeof planLimitsSchema>
|
||||
|
||||
Reference in New Issue
Block a user