import * as SecureStore from "expo-secure-store"; import { User, AuthResponse, VerifyTokenResponse, SignUpPayload, ResetPasswordRequestResponse, ResetPasswordResponse, UserUpdatePayload, CreateMoodEntryPayload, SleepRecordPayload, MoodEntry, SleepRecord, SleepRecordResponse, MoodEntryResponse, Trigger, TriggerResponse, CreateTriggerPayload, ActivateResponse, PaginatedResponse, } from "@/lib/api/types"; const API_BASE_URL = process.env.EXPO_PUBLIC_API_BASE_URL; const TOKEN_KEY = process.env.EXPO_PUBLIC_TOKEN_KEY; class ApiClient { private async getStoredToken(): Promise { try { return await SecureStore.getItemAsync(TOKEN_KEY!); } catch (error) { console.error("Failed to get stored token:", error); return null; } } private async storeToken(token: string): Promise { try { await SecureStore.setItemAsync(TOKEN_KEY!, token); } catch (error) { console.error("Failed to store token:", error); throw error; } } private async removeToken(): Promise { try { await SecureStore.deleteItemAsync(TOKEN_KEY!); } catch (error) { console.error("Failed to remove token:", error); } } async getStoredAuthData(): Promise<{ token: string | null }> { const token = await this.getStoredToken(); return { token }; } private async request( endpoint: string, options: RequestInit = {}, ): Promise { const token = await this.getStoredToken(); const isFormData = options.body instanceof FormData; const config: RequestInit = { ...options, headers: { ...(token && { Authorization: `Bearer ${token}` }), ...(!isFormData && { "Content-Type": "application/json" }), ...options.headers, }, }; const response = await fetch(`${API_BASE_URL}${endpoint}`, config); if (!response.ok) { const error = await response .json() .catch(() => ({ error: "Network error" })); console.error("[API]: ", error, response.status); throw new Error(error.error || `HTTP ${response.status}`); } return response.json(); } // Auth async login( email: string, password: string, ): Promise { const response = await this.request("/auth/login", { method: "POST", body: JSON.stringify({ email, password }), }); if (response.token) { await this.storeToken(response.token); } return response; } async activate(code: number): Promise { return this.request("/auth/activate", { method: "POST", body: JSON.stringify({ code }), }); } async requestActivateCode(): Promise { return this.request("/auth/resend_code", { method: "POST", }); } async signup(user: SignUpPayload): Promise { const response = await this.request("/users", { method: "POST", body: JSON.stringify({ user }), }); await this.storeToken(response.token); return response; } async forgotPassword(email: string): Promise { return this.request("/auth/forgot-password", { method: "POST", body: JSON.stringify({ email }), }); } async resetPassword( token: string, password: string, ): Promise { return this.request("/auth/reset-password", { method: "POST", body: JSON.stringify({ token, newPassword: password }), }); } async verifyToken(): Promise { try { return await this.request("/auth/verify"); } catch (error) { // Token is invalid or expired await this.removeToken(); throw error; } } async logout(): Promise { await this.removeToken(); } // User-related async updateUser(user: UserUpdatePayload): Promise { return await this.request(`/users/${user.id}`, { method: "PUT", body: JSON.stringify({ user }), }); } async avatar(formData: FormData): Promise { return await this.request(`/users/me/avatar`, { method: "POST", body: formData, headers: { "Content-Type": "multipart/form-data", } as any, }); } async removeAvatar(): Promise { return await this.request(`/users/me/avatar`, { method: "DELETE", }); } // Mood-related async createMoodEntry( mood: CreateMoodEntryPayload, ): Promise { return await this.request(`/moods`, { method: "POST", body: JSON.stringify({ mood }), }); } async getMoodEntries(params?: { from?: string; to?: string; page?: number; limit?: number; }): Promise> { const query = new URLSearchParams(); if (params?.from) query.set("from", params.from); if (params?.to) query.set("to", params.to); if (params?.page) query.set("page", String(params.page)); if (params?.limit) query.set("limit", String(params.limit)); const qs = query.toString(); return this.request(`/moods${qs ? `?${qs}` : ""}`); } async getMoodEntry(id: string): Promise { return this.request(`/moods/${id}`); } async deleteMoodEntry(id: string): Promise { return this.request(`/moods/${id}`, { method: "DELETE" }); } // Sleep Record async createSleepRecord( sleepRecord: SleepRecordPayload, ): Promise { return await this.request(`/sleep_records`, { method: "POST", body: JSON.stringify({ sleepRecord }), }); } async getSleepRecords(params?: { from?: string; to?: string; page?: number; limit?: number; }): Promise> { const query = new URLSearchParams(); if (params?.from) query.set("from", params.from); if (params?.to) query.set("to", params.to); if (params?.page) query.set("page", String(params.page)); if (params?.limit) query.set("limit", String(params.limit)); const qs = query.toString(); return this.request(`/sleep_records${qs ? `?${qs}` : ""}`); } // Trigger async createTrigger(trigger: CreateTriggerPayload): Promise { return await this.request(`/triggers`, { method: "POST", body: JSON.stringify({ trigger }), }); } async getTriggers(params?: { from?: string; to?: string; page?: number; limit?: number; }): Promise> { const query = new URLSearchParams(); if (params?.from) query.set("from", params.from); if (params?.to) query.set("to", params.to); if (params?.page) query.set("page", String(params.page)); if (params?.limit) query.set("limit", String(params.limit)); const qs = query.toString(); return this.request(`/triggers${qs ? `?${qs}` : ""}`); } async getTrigger(id: string): Promise { return this.request(`/triggers/${id}`); } async deleteTrigger(id: string): Promise { return this.request(`/triggers/${id}`, { method: "DELETE" }); } } export const apiClient = new ApiClient();