Files
Hy2026/client/src/services/api/client.ts
T
Rafał Tyński 92a904b8a9 feat(client): add accessible design system and API client foundation
Add design tokens, base components (page header, section, status chip, empty state, loading, announcer), a11y composables (text scaling, announcements), high-contrast theme and a typed HTTP client with ProblemDetails error mapping.
2026-10-04 04:04:31 +02:00

73 lines
2.0 KiB
TypeScript

import { ApiError, type ProblemDetails } from './errors'
const baseUrl = import.meta.env.VITE_API_BASE_URL ?? ''
let tokenProvider: () => string | null = () => null
/** Wires the API client to the session store without creating a hard dependency. */
export function setAuthTokenProvider(provider: () => string | null): void {
tokenProvider = provider
}
export interface RequestOptions {
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
body?: unknown
headers?: Record<string, string>
signal?: AbortSignal
/** Attach the bearer token when available (default: true). */
auth?: boolean
}
async function parseBody(response: Response): Promise<unknown> {
if (response.status === 204 || response.headers.get('content-length') === '0') {
return undefined
}
const contentType = response.headers.get('content-type') ?? ''
if (!contentType.includes('application/json')) {
return undefined
}
try {
return await response.json()
} catch {
return undefined
}
}
/**
* Minimal typed fetch wrapper: JSON in/out, bearer auth, RFC 7807 error mapping.
* Throws `ApiError` on non-2xx responses.
*/
export async function request<T>(path: string, options: RequestOptions = {}): Promise<T> {
const { method = 'GET', body, headers = {}, signal, auth = true } = options
const requestHeaders: Record<string, string> = { Accept: 'application/json', ...headers }
const token = auth ? tokenProvider() : null
if (token) {
requestHeaders.Authorization = `Bearer ${token}`
}
if (body !== undefined) {
requestHeaders['Content-Type'] = 'application/json'
}
const response = await fetch(`${baseUrl}${path}`, {
method,
headers: requestHeaders,
body: body === undefined ? undefined : JSON.stringify(body),
signal,
})
const payload = await parseBody(response)
if (!response.ok) {
const problem = (payload as ProblemDetails | undefined) ?? null
throw new ApiError(response.status, problem)
}
return payload as T
}