import axios, { AxiosError, AxiosInstance, InternalAxiosRequestConfig } from 'axios'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { LoginRequest, SignupRequest, ForgotPasswordRequest, ResetPasswordRequest, AuthResponse, User, Resource, ResourceCategory, CommunitySpace, UserPreferences, ApiError, } from './types'; // Storage keys const TOKEN_KEY = 'auth_token'; const REFRESH_TOKEN_KEY = 'refresh_token'; // API base URL - update this for your environment const API_BASE_URL = process.env.EXPO_PUBLIC_API_URL || 'https://api.morethanadiagnosis.org/api/v1'; // Create axios instance const api: AxiosInstance = axios.create({ baseURL: API_BASE_URL, timeout: 30000, headers: { 'Content-Type': 'application/json', }, }); // Request interceptor - add auth token to requests api.interceptors.request.use( async (config: InternalAxiosRequestConfig) => { const token = await AsyncStorage.getItem(TOKEN_KEY); if (token && config.headers) { config.headers.Authorization = `Bearer ${token}`; } return config; }, (error) => { return Promise.reject(error); } ); // Response interceptor - handle token refresh api.interceptors.response.use( (response) => response, async (error: AxiosError) => { const originalRequest = error.config; // If 401 and we haven't tried to refresh yet if (error.response?.status === 401 && originalRequest && !originalRequest._retry) { originalRequest._retry = true; try { const refreshToken = await AsyncStorage.getItem(REFRESH_TOKEN_KEY); if (refreshToken) { const response = await axios.post(`${API_BASE_URL}/auth/refresh`, { refreshToken, }); const { accessToken, refreshToken: newRefreshToken } = response.data; // Store new tokens await AsyncStorage.setItem(TOKEN_KEY, accessToken); await AsyncStorage.setItem(REFRESH_TOKEN_KEY, newRefreshToken); // Retry original request with new token if (originalRequest.headers) { originalRequest.headers.Authorization = `Bearer ${accessToken}`; } return api(originalRequest); } } catch (refreshError) { // Refresh failed - clear tokens and redirect to login await AsyncStorage.multiRemove([TOKEN_KEY, REFRESH_TOKEN_KEY]); // The auth store should handle navigation } } return Promise.reject(error); } ); // Extend AxiosRequestConfig to include _retry property declare module 'axios' { export interface InternalAxiosRequestConfig { _retry?: boolean; } } // ========================================== // Authentication API // ========================================== export const authApi = { login: async (data: LoginRequest): Promise => { const response = await api.post('/auth/login', data); const { accessToken, refreshToken } = response.data; // Store tokens await AsyncStorage.setItem(TOKEN_KEY, accessToken); await AsyncStorage.setItem(REFRESH_TOKEN_KEY, refreshToken); return response.data; }, signup: async (data: SignupRequest): Promise => { const response = await api.post('/auth/signup', data); const { accessToken, refreshToken } = response.data; // Store tokens await AsyncStorage.setItem(TOKEN_KEY, accessToken); await AsyncStorage.setItem(REFRESH_TOKEN_KEY, refreshToken); return response.data; }, forgotPassword: async (data: ForgotPasswordRequest): Promise<{ message: string }> => { const response = await api.post<{ message: string }>('/auth/forgot-password', data); return response.data; }, resetPassword: async (data: ResetPasswordRequest): Promise<{ message: string }> => { const response = await api.post<{ message: string }>('/auth/reset-password', data); return response.data; }, logout: async (): Promise => { try { await api.post('/auth/logout'); } catch { // Ignore errors on logout } finally { await AsyncStorage.multiRemove([TOKEN_KEY, REFRESH_TOKEN_KEY]); } }, refreshToken: async (): Promise => { const refreshToken = await AsyncStorage.getItem(REFRESH_TOKEN_KEY); if (!refreshToken) { throw new Error('No refresh token available'); } const response = await api.post('/auth/refresh', { refreshToken }); const { accessToken, refreshToken: newRefreshToken } = response.data; await AsyncStorage.setItem(TOKEN_KEY, accessToken); await AsyncStorage.setItem(REFRESH_TOKEN_KEY, newRefreshToken); return response.data; }, getStoredToken: async (): Promise => { return AsyncStorage.getItem(TOKEN_KEY); }, }; // ========================================== // User API // ========================================== export const userApi = { getMe: async (): Promise => { const response = await api.get('/user/me'); return response.data; }, updateMe: async (data: Partial): Promise => { const response = await api.put('/user/me', data); return response.data; }, getPreferences: async (): Promise => { const response = await api.get('/user/preferences'); return response.data; }, updatePreferences: async (data: Partial): Promise => { const response = await api.put('/user/preferences', data); return response.data; }, }; // ========================================== // Resources API // ========================================== export const resourcesApi = { getCategories: async (): Promise => { const response = await api.get('/resources/categories'); return response.data; }, getAll: async (): Promise => { const response = await api.get('/resources'); return response.data; }, getById: async (id: string): Promise => { const response = await api.get(`/resources/${id}`); return response.data; }, getByCategory: async (categoryId: string): Promise => { const response = await api.get(`/resources/category/${categoryId}`); return response.data; }, }; // ========================================== // Community API // ========================================== export const communityApi = { getSpaces: async (): Promise => { const response = await api.get('/community'); return response.data; }, getSpaceById: async (id: string): Promise => { const response = await api.get(`/community/${id}`); return response.data; }, joinSpace: async (id: string): Promise<{ message: string }> => { const response = await api.post<{ message: string }>(`/community/${id}/join`); return response.data; }, leaveSpace: async (id: string): Promise<{ message: string }> => { const response = await api.post<{ message: string }>(`/community/${id}/leave`); return response.data; }, }; // ========================================== // Helper functions // ========================================== export const handleApiError = (error: unknown): string => { if (axios.isAxiosError(error)) { const axiosError = error as AxiosError; // Network error if (!axiosError.response) { return 'Network error. Please check your connection.'; } // API error response const apiError = axiosError.response.data; if (apiError?.message) { return apiError.message; } // HTTP status errors switch (axiosError.response.status) { case 400: return 'Invalid request. Please check your input.'; case 401: return 'Invalid credentials. Please try again.'; case 403: return 'You do not have permission to perform this action.'; case 404: return 'Resource not found.'; case 422: return 'Validation error. Please check your input.'; case 500: return 'Server error. Please try again later.'; default: return 'An unexpected error occurred.'; } } if (error instanceof Error) { return error.message; } return 'An unexpected error occurred.'; }; export default api;