import React, { useState, useEffect } from 'react'; import { View, Text, StyleSheet, TouchableOpacity, Alert, TextInput, ScrollView, Image, ActivityIndicator, Modal, Pressable } 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'; const AVATARS = [ require('../../assets/avatars/avatar1.png'), require('../../assets/avatars/avatar2.png'), require('../../assets/avatars/avatar3.png'), require('../../assets/avatars/avatar4.png'), require('../../assets/avatars/avatar5.png'), require('../../assets/avatars/avatar6.png'), ]; const COLOR_CIRCLES = [ require('../../assets/avatars/color1.png'), require('../../assets/avatars/color2.png'), require('../../assets/avatars/color3.png'), require('../../assets/avatars/color4.png'), require('../../assets/avatars/color5.png'), require('../../assets/avatars/color6.png'), ]; interface SearchHistoryItem { id: number; address: string; latitude: number | null; longitude: number | null; created_at: string; } interface Address { id: number; address: string; latitude: number | null; longitude: number | null; label: string; is_default: number; created_at: string; } export default function ProfileScreen() { const router = useRouter(); const { user, isAuthenticated, isLoading, logout, isAdmin } = useAuth(); 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([]); // Avatar modal state const [showAvatarModal, setShowAvatarModal] = useState(false); const [avatarTab, setAvatarTab] = useState<'presets' | 'colors' | 'upload'>('presets'); // Config modal state const [showConfig, setShowConfig] = useState(false); const [configFirstName, setConfigFirstName] = useState(''); const [configLastName, setConfigLastName] = useState(''); const [configEmail, setConfigEmail] = useState(''); const [configCity, setConfigCity] = useState(''); const [configAddress, setConfigAddress] = useState(''); const [configSaving, setConfigSaving] = useState(false); const [configFeedback, setConfigFeedback] = useState<{ type: 'ok' | 'err'; text: string } | null>(null); // Addresses modal state const [showAddresses, setShowAddresses] = useState(false); const [addresses, setAddresses] = useState([]); const [addressesLoading, setAddressesLoading] = useState(false); const [showAddressForm, setShowAddressForm] = useState(false); const [editingAddressId, setEditingAddressId] = useState(null); const [formAddress, setFormAddress] = useState(''); const [formLabel, setFormLabel] = useState(''); const [formDefault, setFormDefault] = useState(false); const [formSaving, setFormSaving] = useState(false); const [formError, setFormError] = useState(''); useEffect(() => { if (isAuthenticated) { loadSearchHistory(); } }, [isAuthenticated]); useEffect(() => { setFirstName(user?.first_name || ''); setLastName(user?.last_name || ''); setAvatarUrl(user?.avatar_url || ''); }, [user]); async function loadSearchHistory() { try { const res = await fetch('/api/search-history', { credentials: 'include' }); if (res.ok) { const data = await res.json(); setSearchHistory(data); } } catch (err) { console.error('Error loading search history:', err); } } function openConfig() { setConfigFirstName(user?.first_name || ''); setConfigLastName(user?.last_name || ''); setConfigEmail(user?.email || ''); setConfigCity(user?.city || ''); setConfigAddress(user?.address || ''); setConfigFeedback(null); setShowConfig(true); } async function handleConfigSave() { 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, }), }); 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' }); } finally { setConfigSaving(false); } } async function handlePickImage() { const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Images, allowsEditing: true, aspect: [1, 1], quality: 0.8, }); if (!result.canceled && result.assets[0]) { 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 } } catch (err) { console.error('Error saving avatar:', err); } } } async function handleTakePhoto() { const { status } = await ImagePicker.requestCameraPermissionsAsync(); if (status !== 'granted') { Alert.alert('Permiso requerido', 'Necesitamos permiso para acceder a la cámara.'); return; } const result = await ImagePicker.launchCameraAsync({ allowsEditing: true, aspect: [1, 1], quality: 0.8, }); if (!result.canceled && result.assets[0]) { 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(); } } catch (err) { console.error('Error saving avatar:', err); } } } async function handleSelectPresetAvatar(index: number) { const avatarSource = AVATARS[index]; setAvatarUrl(avatarSource); 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(); } } catch (err) { console.error('Error saving avatar:', err); } } async function handleSelectColor(index: number) { const colorSource = COLOR_CIRCLES[index]; setAvatarUrl(colorSource); 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(); } } catch (err) { console.error('Error saving avatar:', err); } } 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)); } } catch (err) { console.error('Error deleting search:', err); } } // Addresses async function loadAddresses() { setAddressesLoading(true); try { const res = await fetch('/api/addresses', { credentials: 'include' }); if (res.ok) { const data = await res.json(); setAddresses(data); } } catch (err) { console.error('Error loading addresses:', err); } finally { setAddressesLoading(false); } } function openAddresses() { setShowAddresses(true); loadAddresses(); } function openAddAddressForm() { setEditingAddressId(null); setFormAddress(''); setFormLabel(''); setFormDefault(addresses.length === 0); setFormError(''); setShowAddressForm(true); } function openEditAddressForm(addr: Address) { setEditingAddressId(addr.id); setFormAddress(addr.address); setFormLabel(addr.label || ''); setFormDefault(Boolean(addr.is_default)); setFormError(''); setShowAddressForm(true); } async function handleAddressSave() { const addr = formAddress.trim(); if (!addr) { setFormError('La dirección es obligatoria'); return; } 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, }), }); 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); } finally { setFormSaving(false); } } 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)); } } catch (err) { console.error('Error deleting address:', err); } } async function handleSetDefault(id: number) { try { const res = await fetch(`/api/addresses/${id}/default`, { method: 'PUT', credentials: 'include', }); if (res.ok) { loadAddresses(); } } catch (err) { console.error('Error setting default address:', err); } } const handleLogout = async () => { Alert.alert( 'Cerrar Sesión', '¿Estás seguro que deseas cerrar sesión?', [ { text: 'Cancelar', style: 'cancel' }, { text: 'Cerrar Sesión', style: 'destructive', onPress: async () => { await logout(); router.replace('/auth/login'); } }, ] ); }; if (isLoading) { return ( Cargando... ); } if (!isAuthenticated) { return ( Inicia Sesión Inicia sesión para acceder a todas las funcionalidades router.push('/auth/login')} > Iniciar Sesión router.push('/auth/register')} > Crear cuenta nueva ); } return ( {/* Avatar Section */} setShowAvatarModal(true)}> {avatarUrl ? ( ) : ( )} Toca para cambiar foto {/* Read-only Name Section */} Nombre {firstName || '—'} Apellidos {lastName || '—'} {/* Menu Section */} Configuración Mis Direcciones {searchHistory.length > 0 && ( Tus búsquedas recientes: {searchHistory.map((item) => ( {item.address} handleDeleteSearch(item.id)} style={styles.searchDelete} > ))} )} {isAdmin && ( Panel Admin )} {/* Logout Button */} Cerrar Sesión {/* Avatar Selection Modal */} Cambiar Avatar setShowAvatarModal(false)}> {/* Tab Buttons */} setAvatarTab('presets')} > Prediseñado setAvatarTab('colors')} > Colores setAvatarTab('upload')} > Subir {/* Tab Content */} {avatarTab === 'presets' && ( {AVATARS.map((avatar, index) => ( handleSelectPresetAvatar(index)} > ))} )} {avatarTab === 'colors' && ( {COLOR_CIRCLES.map((color, index) => ( handleSelectColor(index)} > ))} )} {avatarTab === 'upload' && ( Tomar foto Usa la cámara de tu dispositivo Elegir de galería Selecciona una imagen existente )} {/* Config Modal */} Configuración setShowConfig(false)}> Nombre Apellidos Correo electrónico Ciudad Dirección {configFeedback && ( {configFeedback.text} )} setShowConfig(false)} disabled={configSaving} > Cancelar {configSaving ? ( ) : ( Guardar )} {/* Addresses Modal */} Mis Direcciones { setShowAddresses(false); setShowAddressForm(false); }}> {showAddressForm ? ( Dirección Etiqueta (opcional) setFormDefault(!formDefault)} disabled={formSaving} > Dirección predeterminada {formError ? ( {formError} ) : null} { setShowAddressForm(false); setFormError(''); }} disabled={formSaving} > Cancelar {formSaving ? ( ) : ( {editingAddressId ? 'Actualizar' : 'Añadir'} )} ) : ( {addressesLoading ? ( Cargando direcciones... ) : ( {user?.address ? ( Dirección principal {user.address} Predeterminada ) : null} {addresses.map((addr) => ( {addr.label ? {addr.label} : null} {addr.address} handleSetDefault(addr.id)}> Establecer como predeterminada openEditAddressForm(addr)}> handleDeleteAddress(addr.id)}> ))} )} Añadir más )} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: colors.background, }, loadingText: { textAlign: 'center', marginTop: spacing.xxl, color: colors.textSecondary, }, authPrompt: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: spacing.xl, }, authTitle: { fontSize: 24, fontWeight: 'bold', color: colors.text, marginTop: spacing.lg, }, authSubtitle: { fontSize: 16, color: colors.textSecondary, textAlign: 'center', marginTop: spacing.sm, marginBottom: spacing.xl, }, avatarSection: { alignItems: 'center', padding: spacing.xl, backgroundColor: colors.card, }, avatarContainer: { width: 100, height: 100, borderRadius: 50, backgroundColor: colors.background, justifyContent: 'center', alignItems: 'center', overflow: 'hidden', }, avatarImage: { width: 100, height: 100, borderRadius: 50, }, avatarOverlay: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0, 0, 0, 0.5)', justifyContent: 'center', alignItems: 'center', opacity: 0.8, }, editAvatarText: { marginTop: spacing.sm, color: colors.textSecondary, fontSize: 14, }, infoSection: { padding: spacing.xl, backgroundColor: colors.card, marginTop: spacing.md, gap: spacing.md, }, infoCard: { backgroundColor: colors.background, padding: spacing.md, borderRadius: borderRadius.md, borderWidth: 1, borderColor: colors.border || '#E0E0E0', }, infoLabel: { fontSize: 12, fontWeight: '600', color: colors.textSecondary, textTransform: 'uppercase', letterSpacing: 0.05, marginBottom: 4, }, infoValue: { fontSize: 16, fontWeight: '500', color: colors.text, }, menuSection: { marginTop: spacing.md, backgroundColor: colors.card, }, menuItem: { flexDirection: 'row', alignItems: 'center', padding: spacing.md, borderBottomWidth: 1, borderBottomColor: colors.separator, }, menuText: { flex: 1, marginLeft: spacing.md, fontSize: 16, color: colors.text, }, searchHistorySection: { padding: spacing.md, backgroundColor: colors.background, borderBottomWidth: 1, borderBottomColor: colors.separator, }, searchHistoryTitle: { fontSize: 14, color: colors.textSecondary, marginBottom: spacing.sm, }, searchItem: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingVertical: spacing.sm, borderBottomWidth: 1, borderBottomColor: colors.separator, }, searchAddress: { flex: 1, fontSize: 14, color: colors.text, }, searchDelete: { padding: spacing.xs, }, button: { backgroundColor: colors.primary, borderRadius: borderRadius.md, paddingVertical: spacing.md, paddingHorizontal: spacing.xl, }, buttonText: { color: colors.textInverse, fontSize: 16, fontWeight: '600', }, linkButton: { marginTop: spacing.md, }, linkText: { color: colors.primary, fontSize: 14, }, logoutButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', margin: spacing.xl, padding: spacing.md, backgroundColor: colors.card, borderRadius: borderRadius.md, borderWidth: 1, borderColor: colors.danger, }, logoutText: { marginLeft: spacing.sm, color: colors.danger, fontSize: 16, fontWeight: '500', }, // Modal styles modalBackdrop: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.5)', justifyContent: 'flex-end', }, modalContent: { backgroundColor: colors.card, borderTopLeftRadius: 20, borderTopRightRadius: 20, maxHeight: '90%', }, modalHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', padding: spacing.lg, borderBottomWidth: 1, borderBottomColor: colors.separator, }, modalTitle: { fontSize: 18, fontWeight: 'bold', color: colors.text, }, modalBody: { padding: spacing.lg, }, modalField: { marginBottom: spacing.md, }, modalLabel: { fontSize: 13, fontWeight: '600', color: colors.textSecondary, marginBottom: spacing.xs, }, modalInput: { borderWidth: 1, borderColor: colors.border || '#E0E0E0', borderRadius: borderRadius.md, padding: spacing.md, fontSize: 16, color: colors.text, }, modalFeedback: { padding: spacing.md, borderRadius: borderRadius.md, marginBottom: spacing.md, }, modalFeedbackOk: { backgroundColor: 'rgba(0, 69, 13, 0.1)', borderWidth: 1, borderColor: 'rgba(0, 69, 13, 0.2)', }, modalFeedbackErr: { backgroundColor: 'rgba(186, 26, 26, 0.1)', borderWidth: 1, borderColor: 'rgba(186, 26, 26, 0.2)', }, modalFeedbackText: { fontSize: 14, color: colors.text, }, modalActions: { flexDirection: 'row', justifyContent: 'flex-end', gap: spacing.md, marginTop: spacing.md, }, modalCancelBtn: { paddingVertical: spacing.md, paddingHorizontal: spacing.lg, borderRadius: borderRadius.md, borderWidth: 1, borderColor: colors.border || '#E0E0E0', }, modalCancelText: { fontSize: 16, color: colors.text, }, modalSaveBtn: { backgroundColor: colors.primary, paddingVertical: spacing.md, paddingHorizontal: spacing.xl, borderRadius: borderRadius.md, minWidth: 100, alignItems: 'center', }, modalSaveBtnDisabled: { opacity: 0.6, }, modalSaveText: { color: colors.textInverse, fontSize: 16, fontWeight: '600', }, // Avatar Modal styles avatarModalContent: { backgroundColor: colors.card, borderTopLeftRadius: 20, borderTopRightRadius: 20, maxHeight: '80%', }, tabContainer: { flexDirection: 'row', borderBottomWidth: 1, borderBottomColor: colors.separator, }, tabButton: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingVertical: spacing.md, gap: spacing.xs, }, tabButtonActive: { borderBottomWidth: 2, borderBottomColor: colors.primary, }, tabText: { fontSize: 14, color: colors.textSecondary, }, tabTextActive: { color: colors.primary, fontWeight: '600', }, tabContent: { padding: spacing.lg, }, avatarGrid: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-between', gap: spacing.md, }, avatarOption: { width: '30%', aspectRatio: 1, }, avatarOptionImage: { width: '100%', height: '100%', borderRadius: borderRadius.lg, }, uploadSection: { gap: spacing.md, }, uploadOption: { flexDirection: 'row', alignItems: 'center', padding: spacing.md, backgroundColor: colors.background, borderRadius: borderRadius.md, borderWidth: 1, borderColor: colors.border || '#E5E5EA', }, uploadIconContainer: { width: 56, height: 56, borderRadius: 28, backgroundColor: colors.card, justifyContent: 'center', alignItems: 'center', marginRight: spacing.md, }, uploadOptionTitle: { fontSize: 16, fontWeight: '600', color: colors.text, flex: 1, }, uploadOptionSubtitle: { fontSize: 13, color: colors.textSecondary, flex: 1, }, // Addresses addressLoadingText: { textAlign: 'center', color: colors.textSecondary, fontSize: 14, paddingVertical: spacing.lg, }, addressList: { gap: spacing.sm, }, addressItem: { flexDirection: 'row', alignItems: 'flex-start', justifyContent: 'space-between', padding: spacing.md, backgroundColor: colors.background, borderRadius: borderRadius.md, borderWidth: 1, borderColor: colors.border || '#E0E0E0', }, addressItemDefault: { borderColor: colors.primary, backgroundColor: 'rgba(0, 69, 13, 0.03)', }, addressItemInfo: { flex: 1, gap: 2, }, addressLabel: { fontSize: 13, fontWeight: '700', color: colors.primary, textTransform: 'uppercase', }, addressText: { fontSize: 14, color: colors.text, }, addressBadge: { alignSelf: 'flex-start', backgroundColor: colors.primary, borderRadius: 12, paddingHorizontal: spacing.sm, paddingVertical: 2, marginTop: 4, }, addressBadgeText: { color: colors.textInverse, fontSize: 11, fontWeight: '700', textTransform: 'uppercase', }, addressSetDefault: { fontSize: 12, fontWeight: '600', color: colors.primary, marginTop: 4, textDecorationLine: 'underline', }, addressItemActions: { flexDirection: 'row', gap: spacing.xs, marginLeft: spacing.sm, }, addressActionBtn: { width: 32, height: 32, borderRadius: 8, justifyContent: 'center', alignItems: 'center', }, addressAddBtn: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: spacing.sm, padding: spacing.md, marginTop: spacing.md, borderWidth: 2, borderColor: colors.border || '#E0E0E0', borderStyle: 'dashed', borderRadius: borderRadius.md, }, addressAddBtnText: { fontSize: 15, fontWeight: '600', color: colors.primary, }, addressDefaultCheckbox: { flexDirection: 'row', alignItems: 'center', gap: spacing.sm, marginBottom: spacing.md, }, addressDefaultLabel: { fontSize: 14, color: colors.text, }, });