diff --git a/mobile/src/services/api.ts b/mobile/src/services/api.ts index c196cac..d6e86e6 100644 --- a/mobile/src/services/api.ts +++ b/mobile/src/services/api.ts @@ -1,4 +1,14 @@ -export const API_URL = 'https://supercuriously-precongested-chester.ngrok-free.dev/api'; +import { useAuthStore } from '../store/useAuthStore'; + +const getAuthHeaders = () => { + const token = useAuthStore.getState().user?.token; + return { + 'Accept': 'application/json', + ...(token ? { 'Authorization': `Bearer ${token}` } : {}), + }; +}; + +export const API_URL = 'https://supercuriously-precongested-chester.ngrok-free.dev/api'; // ngrok URL for testing export const authService = { login: async (username, password) => { @@ -45,11 +55,11 @@ export const authService = { }, body: JSON.stringify({ email }), }); - + if (response.ok) { return { ok: true }; } - + // Parse error message const data = await response.json(); return { ok: false, message: data.message || 'Failed to send reset link' }; @@ -71,4 +81,114 @@ export const authService = { }); return response.json(); }, + + getProfile: async () => { + const response = await fetch(`${API_URL}/me`, { + method: 'GET', + headers: getAuthHeaders(), + }); + return response.json(); + }, + + updateProfile: async (data: any) => { + const response = await fetch(`${API_URL}/me/details`, { + method: 'PATCH', + headers: { + ...getAuthHeaders(), + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }); + + const text = await response.text(); + try { + const json = JSON.parse(text); + if (response.ok) { + return { ok: true, data: json }; + } + return { ok: false, message: json.message || 'Failed to update profile' }; + } catch (e) { + console.error('Update Profile Error (Raw Response):', text); + return { ok: false, message: 'Server returned an invalid response' }; + } + }, + + uploadAvatar: async (imageUri: string) => { + const formData = new FormData(); + + // Simplification: Always name the file 'avatar.jpg' and type 'image/jpeg'. + // This avoids issues with weird temp filenames or missing extensions + // that cause backend validation ("must be an image") to fail. + // React Native's FormData will read the file bytes correctly from the URI. + const filename = 'avatar.jpg'; + const type = 'image/jpeg'; + + // React Native FormData expects an object with uri, name, type. + // Cast to 'any' to avoid TypeScript type mismatch. + formData.append('file', { uri: imageUri, name: filename, type } as any); + + const response = await fetch(`${API_URL}/me/avatar`, { + method: 'POST', + headers: { + ...getAuthHeaders(), + }, + body: formData, + }); + + const text = await response.text(); + try { + const json = JSON.parse(text); + if (response.ok) { + return { ok: true, data: json }; + } + console.error('Upload Avatar Failed (Server Message):', json); + return { ok: false, message: json.message || 'Failed to upload avatar' }; + } catch (e) { + console.error('Upload Avatar Error (Raw Response):', text); + return { ok: false, message: 'Server returned an invalid response' }; + } + }, + + resendVerificationEmail: async () => { + const response = await fetch(`${API_URL}/email/resend`, { + method: 'POST', + headers: { + ...getAuthHeaders(), + 'Content-Type': 'application/json', + }, + }); + + if (response.ok) { + return { ok: true }; + } + + const text = await response.text(); + try { + const json = JSON.parse(text); + return { ok: false, message: json.message || 'Failed to send verification email' }; + } catch { + return { ok: false, message: 'Network error' }; + } + }, + + deleteAccount: async () => { + const response = await fetch(`${API_URL}/me`, { + method: 'DELETE', + headers: getAuthHeaders(), + }); + + if (response.ok) { + return { ok: true }; + } + + const text = await response.text(); + try { + const json = JSON.parse(text); + console.error('Delete Account Failed (Server Message):', json); + return { ok: false, message: json.message || 'Failed to delete account' }; + } catch { + console.error('Delete Account Failed (Raw Response):', text); + return { ok: false, message: 'Server returned an invalid response' }; + } + }, };