Files
gruperly/apps/web/src/lib/api.ts
Jose Selesan 5572ef0869 feat(groups): implement group creation functionality and refactor related components
- 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.
2026-09-23 09:19:52 -03:00

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',
},
)