feat: basic onboarding flow\

This commit is contained in:
Jose Selesan
2026-09-15 15:41:45 -03:00
parent 874b6e0cff
commit a92e9e77c3
37 changed files with 1775 additions and 42 deletions

65
apps/web/src/lib/api.ts Normal file
View File

@@ -0,0 +1,65 @@
import type {
ConnectPayment,
ConnectPaymentResult,
CreateFirstGroup,
CreateFirstGroupResult,
GroupList,
OnboardingStatusDto,
ProblemDetails,
} 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?.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 getGroups = () => apiFetch<GroupList>('/api/v1/groups')