From 2843b61f8214ecdf58fd5d7a1604689127a1b78d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoni=20Nu=C3=B1ez=20Romeu?= Date: Tue, 7 Jul 2026 16:07:15 +0200 Subject: [PATCH] fix: resolve 6 frontend-mobile build and runtime issues - Add missing User type fields (first_name, last_name, email, avatar_url, city, address) used in profile.tsx - Fix expo-constants import to use namespace import (consistent with rest of codebase) - Replace placeholder production API URL with configurable value - Fix avatar preset/color selection passing require() IDs to Image uri by tracking local source separately - Replace all relative fetch() calls with configured axios instance (relative URLs don't resolve in React Native) --- apps/frontend-mobile/app/(tabs)/profile.tsx | 154 ++++++-------------- apps/frontend-mobile/constants/config.ts | 4 +- apps/frontend-mobile/types/index.ts | 6 + 3 files changed, 54 insertions(+), 110 deletions(-) diff --git a/apps/frontend-mobile/app/(tabs)/profile.tsx b/apps/frontend-mobile/app/(tabs)/profile.tsx index afb33ae..5ed845b 100644 --- a/apps/frontend-mobile/app/(tabs)/profile.tsx +++ b/apps/frontend-mobile/app/(tabs)/profile.tsx @@ -1,10 +1,11 @@ import React, { useState, useEffect } from 'react'; -import { View, Text, StyleSheet, TouchableOpacity, Alert, TextInput, ScrollView, Image, ActivityIndicator, Modal, Pressable } from 'react-native'; +import { View, Text, StyleSheet, TouchableOpacity, Alert, TextInput, ScrollView, Image, ActivityIndicator, Modal, Pressable, ImageSourcePropType } from 'react-native'; import { useRouter } from 'expo-router'; import { Ionicons } from '@expo/vector-icons'; import * as ImagePicker from 'expo-image-picker'; import { useAuth } from '../../hooks/useAuth'; import { colors, spacing, borderRadius } from '../../constants/theme'; +import api from '../../services/api'; const AVATARS = [ require('../../assets/avatars/avatar1.png'), @@ -49,9 +50,17 @@ export default function ProfileScreen() { const [firstName, setFirstName] = useState(user?.first_name || ''); const [lastName, setLastName] = useState(user?.last_name || ''); const [avatarUrl, setAvatarUrl] = useState(user?.avatar_url || ''); - const [avatarLocalSource, setAvatarLocalSource] = useState(null); const [searchHistory, setSearchHistory] = useState([]); + function getAvatarSource(): ImageSourcePropType { + if (!avatarUrl) return { uri: '' }; + const presetMatch = avatarUrl.match(/^preset_avatar_(\d+)$/); + if (presetMatch) return AVATARS[parseInt(presetMatch[1], 10) - 1]; + const colorMatch = avatarUrl.match(/^color_circle_(\d+)$/); + if (colorMatch) return COLOR_CIRCLES[parseInt(colorMatch[1], 10) - 1]; + return { uri: avatarUrl }; + } + // Avatar modal state const [showAvatarModal, setShowAvatarModal] = useState(false); const [avatarTab, setAvatarTab] = useState<'presets' | 'colors' | 'upload'>('presets'); @@ -92,11 +101,8 @@ export default function ProfileScreen() { async function loadSearchHistory() { try { - const res = await fetch('/api/search-history', { credentials: 'include' }); - if (res.ok) { - const data = await res.json(); - setSearchHistory(data); - } + const { data } = await api.get('/search-history'); + setSearchHistory(data); } catch (err) { console.error('Error loading search history:', err); } @@ -116,29 +122,20 @@ export default function ProfileScreen() { setConfigSaving(true); setConfigFeedback(null); try { - const res = await fetch('/api/users/me', { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - credentials: 'include', - body: JSON.stringify({ - first_name: configFirstName.trim() || null, - last_name: configLastName.trim() || null, - email: configEmail.trim() || null, - city: configCity.trim() || null, - address: configAddress.trim() || null, - }), + const { data: updated } = await api.put('/users/me', { + first_name: configFirstName.trim() || null, + last_name: configLastName.trim() || null, + email: configEmail.trim() || null, + city: configCity.trim() || null, + address: configAddress.trim() || null, }); - if (!res.ok) { - const err = await res.json().catch(() => ({})); - throw new Error(err.error || 'Error al guardar'); - } - const updated = await res.json(); setFirstName(updated.first_name || ''); setLastName(updated.last_name || ''); setConfigFeedback({ type: 'ok', text: 'Perfil guardado.' }); setTimeout(() => setShowConfig(false), 1200); } catch (err: any) { - setConfigFeedback({ type: 'err', text: err.message || 'Error al guardar' }); + const msg = err.response?.data?.error || err.message || 'Error al guardar'; + setConfigFeedback({ type: 'err', text: msg }); } finally { setConfigSaving(false); } @@ -156,16 +153,7 @@ export default function ProfileScreen() { setAvatarUrl(result.assets[0].uri); setShowAvatarModal(false); try { - const res = await fetch('/api/users/me', { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - credentials: 'include', - body: JSON.stringify({ avatar_url: result.assets[0].uri }), - }); - if (res.ok) { - const updated = await res.json(); - // Trigger auth refresh to update user state - } + await api.put('/users/me', { avatar_url: result.assets[0].uri }); } catch (err) { console.error('Error saving avatar:', err); } @@ -189,15 +177,7 @@ export default function ProfileScreen() { setAvatarUrl(result.assets[0].uri); setShowAvatarModal(false); try { - const res = await fetch('/api/users/me', { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - credentials: 'include', - body: JSON.stringify({ avatar_url: result.assets[0].uri }), - }); - if (res.ok) { - const updated = await res.json(); - } + await api.put('/users/me', { avatar_url: result.assets[0].uri }); } catch (err) { console.error('Error saving avatar:', err); } @@ -205,38 +185,22 @@ export default function ProfileScreen() { } async function handleSelectPresetAvatar(index: number) { - const avatarSource = AVATARS[index]; - setAvatarUrl(avatarSource); + const avatarValue = `preset_avatar_${index + 1}`; + setAvatarUrl(avatarValue); setShowAvatarModal(false); try { - const res = await fetch('/api/users/me', { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - credentials: 'include', - body: JSON.stringify({ avatar_url: `preset_avatar_${index + 1}` }), - }); - if (res.ok) { - const updated = await res.json(); - } + await api.put('/users/me', { avatar_url: avatarValue }); } catch (err) { console.error('Error saving avatar:', err); } } async function handleSelectColor(index: number) { - const colorSource = COLOR_CIRCLES[index]; - setAvatarUrl(colorSource); + const colorValue = `color_circle_${index + 1}`; + setAvatarUrl(colorValue); setShowAvatarModal(false); try { - const res = await fetch('/api/users/me', { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - credentials: 'include', - body: JSON.stringify({ avatar_url: `color_circle_${index + 1}` }), - }); - if (res.ok) { - const updated = await res.json(); - } + await api.put('/users/me', { avatar_url: colorValue }); } catch (err) { console.error('Error saving avatar:', err); } @@ -244,13 +208,8 @@ export default function ProfileScreen() { async function handleDeleteSearch(id: number) { try { - const res = await fetch(`/api/search-history/${id}`, { - method: 'DELETE', - credentials: 'include', - }); - if (res.ok) { - setSearchHistory(prev => prev.filter(item => item.id !== id)); - } + await api.delete(`/search-history/${id}`); + setSearchHistory(prev => prev.filter(item => item.id !== id)); } catch (err) { console.error('Error deleting search:', err); } @@ -260,11 +219,8 @@ export default function ProfileScreen() { async function loadAddresses() { setAddressesLoading(true); try { - const res = await fetch('/api/addresses', { credentials: 'include' }); - if (res.ok) { - const data = await res.json(); - setAddresses(data); - } + const { data } = await api.get('/addresses'); + setAddresses(data); } catch (err) { console.error('Error loading addresses:', err); } finally { @@ -304,27 +260,19 @@ export default function ProfileScreen() { setFormSaving(true); setFormError(''); try { - const url = editingAddressId ? `/api/addresses/${editingAddressId}` : '/api/addresses'; - const method = editingAddressId ? 'PUT' : 'POST'; - const res = await fetch(url, { - method, - headers: { 'Content-Type': 'application/json' }, - credentials: 'include', - body: JSON.stringify({ - address: addr, - label: formLabel.trim(), - is_default: formDefault, - }), + const url = editingAddressId ? `/addresses/${editingAddressId}` : '/addresses'; + const method = editingAddressId ? 'put' : 'post'; + await api[method](url, { + address: addr, + label: formLabel.trim(), + is_default: formDefault, }); - if (!res.ok) { - const err = await res.json().catch(() => ({})); - throw new Error(err.error || 'Error al guardar la dirección'); - } setShowAddressForm(false); setEditingAddressId(null); loadAddresses(); } catch (err: any) { - setFormError(err.message); + const msg = err.response?.data?.error || err.message || 'Error al guardar la dirección'; + setFormError(msg); } finally { setFormSaving(false); } @@ -332,13 +280,8 @@ export default function ProfileScreen() { async function handleDeleteAddress(id: number) { try { - const res = await fetch(`/api/addresses/${id}`, { - method: 'DELETE', - credentials: 'include', - }); - if (res.ok) { - setAddresses(prev => prev.filter(a => a.id !== id)); - } + await api.delete(`/addresses/${id}`); + setAddresses(prev => prev.filter(a => a.id !== id)); } catch (err) { console.error('Error deleting address:', err); } @@ -346,13 +289,8 @@ export default function ProfileScreen() { async function handleSetDefault(id: number) { try { - const res = await fetch(`/api/addresses/${id}/default`, { - method: 'PUT', - credentials: 'include', - }); - if (res.ok) { - loadAddresses(); - } + await api.put(`/addresses/${id}/default`); + loadAddresses(); } catch (err) { console.error('Error setting default address:', err); } @@ -416,7 +354,7 @@ export default function ProfileScreen() { setShowAvatarModal(true)}> {avatarUrl ? ( - + ) : ( )} diff --git a/apps/frontend-mobile/constants/config.ts b/apps/frontend-mobile/constants/config.ts index 92299fe..50800b4 100644 --- a/apps/frontend-mobile/constants/config.ts +++ b/apps/frontend-mobile/constants/config.ts @@ -1,11 +1,11 @@ -import Constants from 'expo-constants'; +import * as Constants from 'expo-constants'; const ENV = { development: { API_BASE_URL: 'http://localhost:3001/api', }, production: { - API_BASE_URL: 'https://your-production-api.com/api', + API_BASE_URL: Constants.expoConfig?.extra?.apiUrl || 'https://api.farmafinder.com/api', }, }; diff --git a/apps/frontend-mobile/types/index.ts b/apps/frontend-mobile/types/index.ts index 9a119e7..84f24e8 100644 --- a/apps/frontend-mobile/types/index.ts +++ b/apps/frontend-mobile/types/index.ts @@ -32,6 +32,12 @@ export interface User { id: number; username: string; is_admin: boolean; + first_name?: string; + last_name?: string; + email?: string; + avatar_url?: string; + city?: string; + address?: string; } export interface AuthResponse {