mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-29 22:23:01 +00:00
- 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.
160 lines
3.9 KiB
TypeScript
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);
|