Files
openhuman/src/services/apiClient.ts
T
Steven Enamakel 3458bb0bbc Add UserProvider and user data management with Redux
- Introduced UserProvider to automatically fetch user data when a JWT token is available, enhancing user experience by ensuring data is readily accessible.
- Created useUser hook for accessing user data and managing loading states, streamlining the process of fetching and utilizing user information.
- Implemented userSlice for managing user state in Redux, including actions for fetching and clearing user data, improving state management consistency.
- Added apiClient for handling API requests, including error handling and authentication, to facilitate communication with the backend.

These changes enhance the application's user data management and improve the overall architecture by integrating user state with Redux.
2026-01-28 04:40:15 +05:30

160 lines
3.9 KiB
TypeScript

import { BACKEND_URL } from '../utils/config';
import type { ApiResponse, ApiError } from '../types/api';
import { store } from '../store';
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
interface RequestOptions {
method?: HttpMethod;
body?: unknown;
headers?: Record<string, string>;
requireAuth?: boolean;
}
/**
* API Client for making requests to the backend
* Handles authentication, error handling, and response typing
*/
class ApiClient {
private baseUrl: string;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}
/**
* Get the current JWT token from Redux store
*/
private getToken(): string | null {
return store.getState().auth.token;
}
/**
* Build headers for the request
*/
private buildHeaders(options: RequestOptions): HeadersInit {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...options.headers,
};
// Add authorization header if auth is required
if (options.requireAuth !== false) {
const token = this.getToken();
if (token) {
headers.Authorization = `Bearer ${token}`;
}
}
return headers;
}
/**
* Make an API request
*/
private async request<T>(
endpoint: string,
options: RequestOptions = {}
): Promise<T> {
const { method = 'GET', body, requireAuth = true } = options;
const url = `${this.baseUrl}${endpoint}`;
const headers = this.buildHeaders({ ...options, requireAuth });
const config: RequestInit = {
method,
headers,
};
if (body && method !== 'GET') {
config.body = JSON.stringify(body);
}
try {
const response = await fetch(url, config);
// Handle non-JSON responses
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return {} as T;
}
const data = await response.json();
// Handle error responses
if (!response.ok) {
const error: ApiError = data.error
? { success: false, error: data.error, message: data.message }
: { success: false, error: `HTTP ${response.status}: ${response.statusText}` };
throw error;
}
return data as T;
} catch (error) {
// Re-throw API errors as-is
if (error && typeof error === 'object' && 'error' in error) {
throw error;
}
// Wrap network/other errors
throw {
success: false,
error: error instanceof Error ? error.message : 'Unknown error occurred',
} as ApiError;
}
}
/**
* GET request
*/
async get<T>(endpoint: string, options?: Omit<RequestOptions, 'method' | 'body'>): Promise<T> {
return this.request<T>(endpoint, { ...options, method: 'GET' });
}
/**
* POST request
*/
async post<T>(
endpoint: string,
body?: unknown,
options?: Omit<RequestOptions, 'method' | 'body'>
): Promise<T> {
return this.request<T>(endpoint, { ...options, method: 'POST', body });
}
/**
* PUT request
*/
async put<T>(
endpoint: string,
body?: unknown,
options?: Omit<RequestOptions, 'method' | 'body'>
): Promise<T> {
return this.request<T>(endpoint, { ...options, method: 'PUT', body });
}
/**
* PATCH request
*/
async patch<T>(
endpoint: string,
body?: unknown,
options?: Omit<RequestOptions, 'method' | 'body'>
): Promise<T> {
return this.request<T>(endpoint, { ...options, method: 'PATCH', body });
}
/**
* DELETE request
*/
async delete<T>(endpoint: string, options?: Omit<RequestOptions, 'method' | 'body'>): Promise<T> {
return this.request<T>(endpoint, { ...options, method: 'DELETE' });
}
}
// Export singleton instance
export const apiClient = new ApiClient(BACKEND_URL);