| | import type { ApiRequestOptions, ApiError } from '../types/api.types.ts'; |
| | import { getUserId } from '../utils/index.ts'; |
| |
|
| | const DEFAULT_HEADERS: HeadersInit = { |
| | 'Content-Type': 'application/json', |
| | }; |
| |
|
| | const BASE_URL = |
| | process.env.REACT_APP_API_BASE_URL?.replace(/\/$/, ''); |
| |
|
| | export type { ApiRequestOptions, ApiError }; |
| |
|
| | export async function apiRequest<TResponse = unknown, TBody = unknown>({ |
| | path, |
| | method = 'GET', |
| | body, |
| | headers, |
| | signal, |
| | }: ApiRequestOptions<TBody>): Promise<TResponse> { |
| | const url = `${BASE_URL}${path.startsWith('/') ? '' : '/'}${path}`; |
| |
|
| | |
| | const userId = getUserId(); |
| | |
| | |
| | let baseHeaders: Record<string, string> = {}; |
| | if (headers instanceof Headers) { |
| | baseHeaders = Object.fromEntries(headers.entries()); |
| | } else if (headers) { |
| | baseHeaders = headers as Record<string, string>; |
| | } |
| | |
| | const requestHeaders: Record<string, string> = { |
| | ...(DEFAULT_HEADERS as Record<string, string>), |
| | ...baseHeaders, |
| | }; |
| |
|
| | if (userId) { |
| | requestHeaders['X-User-ID'] = userId; |
| | } |
| |
|
| | const response = await fetch(url, { |
| | method, |
| | headers: requestHeaders, |
| | body: body ? JSON.stringify(body) : undefined, |
| | signal, |
| | }); |
| |
|
| | const contentType = response.headers.get('content-type'); |
| | const isJson = contentType?.includes('application/json'); |
| | const payload = isJson ? await response.json().catch(() => undefined) : undefined; |
| |
|
| | if (!response.ok) { |
| | const error: ApiError = { |
| | status: response.status, |
| | message: |
| | (payload as { message?: string })?.message || |
| | response.statusText || |
| | 'Request failed', |
| | details: payload, |
| | }; |
| | throw error; |
| | } |
| |
|
| | return payload as TResponse; |
| | } |
| |
|
| | export const api = { |
| | get: <TResponse>(path: string, init?: Omit<ApiRequestOptions, 'path' | 'method'>) => |
| | apiRequest<TResponse>({ path, method: 'GET', ...init }), |
| | post: <TResponse, TBody = unknown>( |
| | path: string, |
| | body?: TBody, |
| | init?: Omit<ApiRequestOptions<TBody>, 'path' | 'method' | 'body'>, |
| | ) => apiRequest<TResponse, TBody>({ path, method: 'POST', body, ...init }), |
| | put: <TResponse, TBody = unknown>( |
| | path: string, |
| | body?: TBody, |
| | init?: Omit<ApiRequestOptions<TBody>, 'path' | 'method' | 'body'>, |
| | ) => apiRequest<TResponse, TBody>({ path, method: 'PUT', body, ...init }), |
| | patch: <TResponse, TBody = unknown>( |
| | path: string, |
| | body?: TBody, |
| | init?: Omit<ApiRequestOptions<TBody>, 'path' | 'method' | 'body'>, |
| | ) => apiRequest<TResponse, TBody>({ path, method: 'PATCH', body, ...init }), |
| | delete: <TResponse>(path: string, init?: Omit<ApiRequestOptions, 'path' | 'method'>) => |
| | apiRequest<TResponse>({ path, method: 'DELETE', ...init }), |
| | }; |
| |
|
| | export default api; |
| |
|
| |
|