- 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.
119 lines
3.1 KiB
TypeScript
119 lines
3.1 KiB
TypeScript
import type { ProblemDetails } from '@gruperly/shared';
|
|
import { notFoundProblem } from './problem-details';
|
|
import { PROBLEM_DOMAIN } from './problem-domain';
|
|
|
|
export function validationProblem(params: {
|
|
detail?: string;
|
|
instance?: string;
|
|
errors?: Record<string, string[]>;
|
|
}): ProblemDetails {
|
|
return {
|
|
type: `${PROBLEM_DOMAIN}/problems/validation`,
|
|
title: 'Bad Request',
|
|
status: 400,
|
|
detail: params.detail ?? 'Invalid request data',
|
|
instance: params.instance,
|
|
errors: params.errors,
|
|
};
|
|
}
|
|
|
|
export function conflictProblem(params: {
|
|
detail: string;
|
|
code?: string;
|
|
instance?: string;
|
|
}): ProblemDetails {
|
|
return {
|
|
type: `${PROBLEM_DOMAIN}/problems/conflict`,
|
|
title: 'Conflict',
|
|
status: 409,
|
|
detail: params.detail,
|
|
instance: params.instance,
|
|
code: params.code,
|
|
};
|
|
}
|
|
|
|
export function forbiddenProblem(params: {
|
|
detail: string;
|
|
code?: string;
|
|
instance?: string;
|
|
}): ProblemDetails {
|
|
return {
|
|
type: `${PROBLEM_DOMAIN}/problems/forbidden`,
|
|
title: 'Forbidden',
|
|
status: 403,
|
|
detail: params.detail,
|
|
instance: params.instance,
|
|
code: params.code,
|
|
};
|
|
}
|
|
|
|
export function organizationNotFoundProblem(id: string): ProblemDetails {
|
|
return {
|
|
type: `${PROBLEM_DOMAIN}/problems/organization-not-found`,
|
|
title: 'Not Found',
|
|
status: 404,
|
|
detail: `Organization ${id} was not found.`,
|
|
code: 'organization_not_found',
|
|
};
|
|
}
|
|
|
|
export function groupOwnerRequiredProblem(): ProblemDetails {
|
|
return forbiddenProblem({
|
|
detail: 'Only the organization owner can create the group.',
|
|
code: 'group_owner_required',
|
|
});
|
|
}
|
|
|
|
export function databaseUnavailableProblem(params?: {
|
|
instance?: string;
|
|
}): ProblemDetails {
|
|
return {
|
|
type: `${PROBLEM_DOMAIN}/problems/database-unavailable`,
|
|
title: 'Service Unavailable',
|
|
status: 503,
|
|
detail: 'Database health check failed',
|
|
instance: params?.instance,
|
|
code: 'database_unavailable',
|
|
};
|
|
}
|
|
|
|
export function noGroupAccessProblem(): ProblemDetails {
|
|
return forbiddenProblem({
|
|
detail: 'You do not have access to this group.',
|
|
code: 'group_access_denied',
|
|
});
|
|
}
|
|
|
|
export function notFoundResourceProblem(resourceName: string, id: string): ProblemDetails {
|
|
return notFoundProblem(`${resourceName} ${id}`);
|
|
}
|
|
|
|
export function paymentNotSetupProblem(): ProblemDetails {
|
|
return conflictProblem({
|
|
detail: 'Vinculá una cuenta de cobro antes de crear tu primer grupo.',
|
|
code: 'payment_not_setup',
|
|
});
|
|
}
|
|
|
|
export function onboardingAlreadyCompletedProblem(): ProblemDetails {
|
|
return conflictProblem({
|
|
detail: 'Ya completaste el onboarding de Gruperly.',
|
|
code: 'onboarding_already_completed',
|
|
});
|
|
}
|
|
|
|
export function capacityReachedProblem(capacity: number | null): ProblemDetails {
|
|
return conflictProblem({
|
|
detail: capacity
|
|
? `El grupo alcanzó su cupo máximo de ${capacity} miembros.`
|
|
: 'El grupo alcanzó su cupo máximo de miembros.',
|
|
code: 'group_capacity_reached',
|
|
});
|
|
}
|
|
|
|
export function alreadyWaitlistedProblem(): ProblemDetails {
|
|
return conflictProblem({
|
|
detail: 'Este número de teléfono ya está en la lista de espera del grupo.',
|
|
code: 'already_waitlisted',
|
|
});
|
|
} |