feat: add group waitlist functionality

- Introduced GroupWaitlistEntry model in Prisma schema to manage waitlisted users for groups.
- Implemented API route to add users to the group waitlist with validation.
- Enhanced attendee creation logic to handle group capacity and waitlisting scenarios.
- Added new problem builders for handling waitlist-related errors.
- Updated frontend to support waitlist interactions, including modals for capacity warnings.
- Created tests for waitlist functionality, ensuring proper handling of full groups and existing waitlist entries.
This commit is contained in:
Jose Selesan
2026-09-22 16:28:54 -03:00
parent a937c827bb
commit 785d54df7e
16 changed files with 848 additions and 31 deletions

View File

@@ -42,6 +42,18 @@ export const CreateAttendeeSchema = z.object({
});
export type CreateAttendee = z.output<typeof CreateAttendeeSchema>;
export const CreateAttendeeResultSchema = z.discriminatedUnion('outcome', [
z.object({
outcome: z.literal('created'),
attendee: AttendeeDtoSchema,
}),
z.object({
outcome: z.literal('waitlisted'),
message: z.string(),
}),
]);
export type CreateAttendeeResult = z.output<typeof CreateAttendeeResultSchema>;
export const JoinGroupViaInviteSchema = z.object({
firstName: z.string().trim().min(1, 'El nombre es obligatorio'),
lastName: z.string().trim().min(1, 'El apellido es obligatorio'),
@@ -50,6 +62,13 @@ export const JoinGroupViaInviteSchema = z.object({
});
export type JoinGroupViaInvite = z.output<typeof JoinGroupViaInviteSchema>;
export const JoinGroupViaInviteResultSchema = z.object({
status: z.enum(['registered', 'waitlisted']),
message: z.string(),
attendee: AttendeeDtoSchema.nullable(),
});
export type JoinGroupViaInviteResult = z.output<typeof JoinGroupViaInviteResultSchema>;
export const BulkAttendeeItemSchema = z.object({
firstName: z.string().trim().min(1, 'El nombre es obligatorio'),
lastName: z.string().trim().optional().default(''),

View File

@@ -28,4 +28,26 @@ export const WaitlistListSchema = z.object({
data: z.array(WaitlistEntryDtoSchema),
pagination: paginationSchema,
});
export type WaitlistList = z.output<typeof WaitlistListSchema>;
export type WaitlistList = z.output<typeof WaitlistListSchema>;
export const GroupWaitlistEntryDtoSchema = z.object({
id: z.string(),
groupId: z.string(),
fullName: z.string(),
phone: z.string(),
email: z.string().nullable(),
notes: z.string().nullable(),
status: waitlistStatusSchema,
createdAt: isoDateTimeSchema,
updatedAt: isoDateTimeSchema,
});
export type GroupWaitlistEntryDto = z.output<typeof GroupWaitlistEntryDtoSchema>;
export const CreateGroupWaitlistEntrySchema = z.object({
firstName: z.string().trim().min(1, 'El nombre es obligatorio'),
lastName: z.string().trim().optional().default(''),
phone: z.string().trim().min(6, 'Ingresa un número de teléfono válido'),
email: z.string().trim().email('Email inválido').optional().or(z.literal('')),
notes: z.string().trim().optional(),
});
export type CreateGroupWaitlistEntry = z.output<typeof CreateGroupWaitlistEntrySchema>;