- Removed organization dependency from group creation logic. - Introduced new route and use-case for creating groups. - Refactored form handling for group creation into a reusable GroupForm component. - Updated API calls to support new group creation endpoint. - Adjusted tests to reflect changes in group creation logic and validation. - Removed obsolete organizations route and related components. - Updated breadcrumb navigation and routing for group management.
161 lines
4.9 KiB
TypeScript
161 lines
4.9 KiB
TypeScript
import type {
|
|
AttendeeDto,
|
|
AttendeeList,
|
|
BulkCreateAttendees,
|
|
BulkCreateAttendeesResult,
|
|
ConnectPayment,
|
|
ConnectPaymentResult,
|
|
CreateAttendee,
|
|
CreateAttendeeResult,
|
|
CreateFirstGroup,
|
|
CreateFirstGroupResult,
|
|
CreateGroupResult,
|
|
CreateGroupWaitlistEntry,
|
|
GroupDto,
|
|
GroupInviteInfoDto,
|
|
GroupList,
|
|
GroupWaitlistEntryDto,
|
|
GroupWaitlistList,
|
|
InviteTokenResult,
|
|
JoinGroupViaInvite,
|
|
JoinGroupViaInviteResult,
|
|
OnboardingStatusDto,
|
|
ProblemDetails,
|
|
PromoteGroupWaitlistEntryResult,
|
|
RemoveAttendeeResult,
|
|
RemoveGroupWaitlistEntryResult,
|
|
} from '@gruperly/shared'
|
|
|
|
const API_URL = import.meta.env.VITE_API_URL ?? 'http://localhost:4000'
|
|
|
|
export class ApiError extends Error {
|
|
constructor(
|
|
readonly status: number,
|
|
readonly problem: ProblemDetails | null,
|
|
) {
|
|
super(problem?.detail ?? problem?.title ?? `Error ${status}`)
|
|
this.name = 'ApiError'
|
|
}
|
|
}
|
|
|
|
type JsonBody = Record<string, unknown> | unknown[]
|
|
|
|
async function apiFetch<T>(path: string, init?: RequestInit): Promise<T> {
|
|
const headers = new Headers(init?.headers)
|
|
headers.set('Content-Type', 'application/json')
|
|
|
|
const res = await fetch(`${API_URL}${path}`, {
|
|
...init,
|
|
headers,
|
|
credentials: 'include',
|
|
})
|
|
|
|
if (!res.ok) {
|
|
let problem: ProblemDetails | null = null
|
|
try {
|
|
const body = (await res.json()) as Partial<ProblemDetails>
|
|
if (body && typeof body === 'object' && typeof body.title === 'string') {
|
|
problem = body as ProblemDetails
|
|
}
|
|
} catch {
|
|
// Sin cuerpo JSON: usamos el error genérico.
|
|
}
|
|
throw new ApiError(res.status, problem)
|
|
}
|
|
|
|
return (await res.json()) as T
|
|
}
|
|
|
|
export const getOnboardingStatus = () => apiFetch<OnboardingStatusDto>('/api/v1/onboarding/status')
|
|
|
|
export const connectPayment = (payload: ConnectPayment) =>
|
|
apiFetch<ConnectPaymentResult>('/api/v1/onboarding/payment-setup', {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload),
|
|
})
|
|
|
|
export const createFirstGroup = (payload: CreateFirstGroup) =>
|
|
apiFetch<CreateFirstGroupResult>('/api/v1/onboarding/first-group', {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload),
|
|
})
|
|
|
|
export const createGroup = (payload: CreateFirstGroup) =>
|
|
apiFetch<CreateGroupResult>('/api/v1/groups', {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload),
|
|
})
|
|
|
|
export const getGroups = () => apiFetch<GroupList>('/api/v1/groups')
|
|
|
|
export const getGroup = (groupId: string) =>
|
|
apiFetch<GroupDto>(`/api/v1/groups/${groupId}`)
|
|
|
|
export const getInviteToken = (groupId: string, regenerate = false) =>
|
|
regenerate
|
|
? apiFetch<InviteTokenResult>(`/api/v1/groups/${groupId}/invite-token?regenerate=true`, {
|
|
method: 'POST',
|
|
})
|
|
: apiFetch<InviteTokenResult>(`/api/v1/groups/${groupId}/invite-token`)
|
|
|
|
export const getInviteInfo = (token: string) =>
|
|
apiFetch<GroupInviteInfoDto>(`/api/v1/invitations/${token}`)
|
|
|
|
export const joinViaInvite = (token: string, payload: JoinGroupViaInvite) =>
|
|
apiFetch<JoinGroupViaInviteResult>(`/api/v1/invitations/${token}/join`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload),
|
|
})
|
|
|
|
export const createAttendee = (
|
|
groupId: string,
|
|
payload: CreateAttendee,
|
|
options?: { allowOverflow?: boolean },
|
|
) =>
|
|
apiFetch<CreateAttendeeResult>(
|
|
`/api/v1/groups/${groupId}/attendees${options?.allowOverflow ? '?allowOverflow=true' : ''}`,
|
|
{
|
|
method: 'POST',
|
|
body: JSON.stringify(payload),
|
|
},
|
|
)
|
|
|
|
export const addToGroupWaitlist = (groupId: string, payload: CreateGroupWaitlistEntry) =>
|
|
apiFetch<GroupWaitlistEntryDto>(`/api/v1/groups/${groupId}/waitlist`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload),
|
|
})
|
|
|
|
export const bulkCreateAttendees = (groupId: string, payload: BulkCreateAttendees) =>
|
|
apiFetch<BulkCreateAttendeesResult>(`/api/v1/groups/${groupId}/attendees/bulk`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload),
|
|
})
|
|
|
|
export const getGroupAttendees = (groupId: string, page = 1, pageSize = 20) =>
|
|
apiFetch<AttendeeList>(`/api/v1/groups/${groupId}/attendees?page=${page}&pageSize=${pageSize}`)
|
|
|
|
export const getGroupWaitlist = (groupId: string, page = 1, pageSize = 100) =>
|
|
apiFetch<GroupWaitlistList>(`/api/v1/groups/${groupId}/waitlist?page=${page}&pageSize=${pageSize}`)
|
|
|
|
export const promoteGroupWaitlistEntry = (groupId: string, entryId: string) =>
|
|
apiFetch<PromoteGroupWaitlistEntryResult>(`/api/v1/groups/${groupId}/waitlist/${entryId}/promote`, {
|
|
method: 'POST',
|
|
})
|
|
|
|
export const removeGroupWaitlistEntry = (groupId: string, entryId: string) =>
|
|
apiFetch<RemoveGroupWaitlistEntryResult>(`/api/v1/groups/${groupId}/waitlist/${entryId}`, {
|
|
method: 'DELETE',
|
|
})
|
|
|
|
export const removeGroupAttendee = (
|
|
groupId: string,
|
|
attendeeId: string,
|
|
options?: { promoteFromWaitlist?: boolean },
|
|
) =>
|
|
apiFetch<RemoveAttendeeResult>(
|
|
`/api/v1/groups/${groupId}/attendees/${attendeeId}${options?.promoteFromWaitlist ? '?promoteFromWaitlist=true' : ''}`,
|
|
{
|
|
method: 'DELETE',
|
|
},
|
|
) |