File size: 2,795 Bytes
b6bed80
 
cbf89ee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b6bed80
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cbf89ee
 
b6bed80
cbf89ee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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}`;

  // Automatically include user ID header if available
  const userId = getUserId();
  
  // Convert headers to plain object if needed
  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;