import React, { useState, useEffect } from 'react'; import { View, Text, StyleSheet, TouchableOpacity, Alert, TextInput, ScrollView, Image, ActivityIndicator, Modal, Pressable, useWindowDimensions } 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 { useThemeContext } from '../../components/ThemeProvider'; import { useThemeStore, ThemeMode } from '../../store/themeStore'; import { spacing, borderRadius } from '../../constants/theme'; import api from '../../services/api'; const TABLET_MIN_WIDTH = 768; 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'), ]; function resolveAvatarUrl(url: string | null | undefined): number | null { if (!url) return null; const matchPreset = url.match(/^preset_avatar_(\d+)$/); if (matchPreset) { const idx = parseInt(matchPreset[1], 10); if (idx >= 1 && idx <= 6) return AVATARS[idx - 1]; } const matchColor = url.match(/^color_circle_(\d+)$/); if (matchColor) { const idx = parseInt(matchColor[1], 10); if (idx >= 1 && idx <= 6) return COLOR_CIRCLES[idx - 1]; } return null; } 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; } const THEME_OPTIONS: { mode: ThemeMode; label: string; icon: string }[] = [ { mode: 'system', label: 'Sistema', icon: 'phone-portrait-outline' }, { mode: 'light', label: 'Claro', icon: 'sunny-outline' }, { mode: 'dark', label: 'Oscuro', icon: 'moon-outline' }, ]; export default function ProfileScreen() { const router = useRouter(); const { width } = useWindowDimensions(); const isTablet = width >= TABLET_MIN_WIDTH; const { user, isAuthenticated, isLoading, logout, isAdmin } = useAuth(); const { colors, isDark } = useThemeContext(); const themeMode = useThemeStore((s) => s.mode); const setThemeMode = useThemeStore((s) => s.setMode); const initialResolved = resolveAvatarUrl(user?.avatar_url); const [firstName, setFirstName] = useState(user?.first_name || ''); const [lastName, setLastName] = useState(user?.last_name || ''); const [avatarUrl, setAvatarUrl] = useState(initialResolved ? '' : (user?.avatar_url || '')); const [avatarLocalSource, setAvatarLocalSource] = useState(initialResolved); 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 || ''); const resolved = resolveAvatarUrl(user?.avatar_url); if (resolved) { setAvatarLocalSource(resolved); setAvatarUrl(''); } else { setAvatarLocalSource(null); setAvatarUrl(user?.avatar_url || ''); } }, [user]); async function loadSearchHistory() { try { const res = await api.get('/search-history'); setSearchHistory(res.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 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, }); const updated = res.data; 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, base64: true, }); if (!result.canceled && result.assets[0]) { const asset = result.assets[0]; if (asset.base64) { const dataUri = `data:${asset.mimeType};base64,${asset.base64}`; setAvatarLocalSource(null); setAvatarUrl(dataUri); setShowAvatarModal(false); try { await api.put('/users/me', { avatar_url: dataUri }); } 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, base64: true, }); if (!result.canceled && result.assets[0]) { const asset = result.assets[0]; if (asset.base64) { const dataUri = `data:${asset.mimeType};base64,${asset.base64}`; setAvatarLocalSource(null); setAvatarUrl(dataUri); setShowAvatarModal(false); try { await api.put('/users/me', { avatar_url: dataUri }); } catch (err) { console.error('Error saving avatar:', err); } } } } async function handleSelectPresetAvatar(index: number) { const avatarSource = AVATARS[index]; setAvatarUrl(''); setAvatarLocalSource(avatarSource); setShowAvatarModal(false); try { await api.put('/users/me', { avatar_url: `preset_avatar_${index + 1}` }); } catch (err) { console.error('Error saving avatar:', err); } } async function handleSelectColor(index: number) { const colorSource = COLOR_CIRCLES[index]; setAvatarUrl(''); setAvatarLocalSource(colorSource); setShowAvatarModal(false); try { await api.put('/users/me', { avatar_url: `color_circle_${index + 1}` }); } catch (err) { console.error('Error saving avatar:', err); } } async function handleDeleteSearch(id: number) { try { await api.delete(`/search-history/${id}`); 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 api.get('/addresses'); setAddresses(res.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'); } }, ] ); }; const getThemeModeLabel = () => { const opt = THEME_OPTIONS.find(o => o.mode === themeMode); return opt?.label || 'Sistema'; }; const getThemeModeIcon = (): 'phone-portrait-outline' | 'sunny-outline' | 'moon-outline' => { if (themeMode === 'light') return 'sunny-outline'; if (themeMode === 'dark') return 'moon-outline'; return 'phone-portrait-outline'; }; 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)}> {avatarLocalSource ? ( ) : avatarUrl ? ( ) : ( )} Toca para cambiar foto {/* Read-only Name Section */} Nombre {firstName || '—'} Apellidos {lastName || '—'} {/* Menu Section */} {/* Theme Toggle */} Modo de pantalla {THEME_OPTIONS.map((opt) => ( setThemeMode(opt.mode)} > {opt.label} ))} 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, }, loadingText: { textAlign: 'center', marginTop: spacing.xxl, }, authPrompt: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: spacing.xl, }, authTitle: { fontSize: 24, fontWeight: 'bold', marginTop: spacing.lg, }, authTitleTablet: { fontSize: 32, }, authSubtitle: { fontSize: 16, textAlign: 'center', marginTop: spacing.sm, marginBottom: spacing.xl, }, authSubtitleTablet: { fontSize: 18, maxWidth: 400, }, avatarSection: { alignItems: 'center', padding: spacing.xl, }, avatarSectionTablet: { paddingVertical: spacing.xxl, }, avatarContainer: { width: 100, height: 100, borderRadius: 50, justifyContent: 'center', alignItems: 'center', overflow: 'hidden', }, avatarContainerTablet: { width: 140, height: 140, borderRadius: 70, }, avatarImage: { width: 100, height: 100, borderRadius: 50, }, avatarImageTablet: { width: 140, height: 140, borderRadius: 70, }, avatarOverlay: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0, 0, 0, 0.4)', justifyContent: 'center', alignItems: 'center', opacity: 0.8, }, editAvatarText: { marginTop: spacing.sm, fontSize: 14, }, infoSection: { padding: spacing.lg, marginTop: spacing.sm, gap: spacing.md, }, infoSectionTablet: { maxWidth: 600, alignSelf: 'center', width: '100%', }, infoCard: { padding: spacing.md, borderRadius: borderRadius.lg, borderWidth: 1, }, infoLabel: { fontSize: 12, fontWeight: '600', textTransform: 'uppercase', letterSpacing: 0.05, marginBottom: 4, }, infoValue: { fontSize: 16, fontWeight: '500', }, menuSection: { marginTop: spacing.sm, }, menuSectionTablet: { maxWidth: 600, alignSelf: 'center', width: '100%', }, menuItem: { flexDirection: 'row', alignItems: 'center', padding: spacing.md, borderBottomWidth: 1, }, menuText: { flex: 1, marginLeft: spacing.md, fontSize: 16, }, // Theme section themeSection: { padding: spacing.md, borderBottomWidth: 1, }, themeHeader: { flexDirection: 'row', alignItems: 'center', gap: spacing.md, marginBottom: spacing.md, }, themeTitle: { fontSize: 16, fontWeight: '500', }, themeOptions: { flexDirection: 'row', gap: spacing.sm, }, themeOption: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: spacing.xs, paddingVertical: spacing.sm + 2, paddingHorizontal: spacing.sm, borderRadius: borderRadius.md, borderWidth: 1, }, themeOptionText: { fontSize: 13, fontWeight: '500', }, themeOptionTextActive: { fontWeight: '700', }, searchHistorySection: { padding: spacing.md, borderBottomWidth: 1, }, searchHistoryTitle: { fontSize: 14, marginBottom: spacing.sm, }, searchItem: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingVertical: spacing.sm, borderBottomWidth: 1, }, searchAddress: { flex: 1, fontSize: 14, }, searchDelete: { padding: spacing.xs, }, button: { backgroundColor: '#7fbf8f', borderRadius: borderRadius.lg, paddingVertical: spacing.md, paddingHorizontal: spacing.xl, }, buttonTablet: { paddingVertical: spacing.lg, paddingHorizontal: spacing.xl * 1.5, }, buttonText: { color: '#ffffff', fontSize: 16, fontWeight: '600', }, buttonTextTablet: { fontSize: 18, }, linkButton: { marginTop: spacing.md, }, linkText: { fontSize: 14, }, logoutButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', margin: spacing.xl, padding: spacing.md, borderRadius: borderRadius.lg, borderWidth: 1, }, logoutButtonTablet: { maxWidth: 600, alignSelf: 'center', width: '100%', }, logoutText: { marginLeft: spacing.sm, fontSize: 16, fontWeight: '500', }, // Modal styles modalBackdrop: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.5)', justifyContent: 'flex-end', }, modalContent: { borderTopLeftRadius: 20, borderTopRightRadius: 20, maxHeight: '90%', }, modalHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', padding: spacing.lg, borderBottomWidth: 1, }, modalTitle: { fontSize: 18, fontWeight: 'bold', }, modalBody: { padding: spacing.lg, }, modalField: { marginBottom: spacing.md, }, modalLabel: { fontSize: 13, fontWeight: '600', marginBottom: spacing.xs, }, modalInput: { borderWidth: 1, borderRadius: borderRadius.lg, padding: spacing.md, fontSize: 16, }, modalFeedback: { padding: spacing.md, borderRadius: borderRadius.lg, marginBottom: spacing.md, borderWidth: 1, }, modalFeedbackText: { fontSize: 14, }, modalActions: { flexDirection: 'row', justifyContent: 'flex-end', gap: spacing.md, marginTop: spacing.md, }, modalCancelBtn: { paddingVertical: spacing.md, paddingHorizontal: spacing.lg, borderRadius: borderRadius.lg, borderWidth: 1, }, modalCancelText: { fontSize: 16, }, modalSaveBtn: { backgroundColor: '#7fbf8f', paddingVertical: spacing.md, paddingHorizontal: spacing.xl, borderRadius: borderRadius.lg, minWidth: 100, alignItems: 'center', }, modalSaveBtnDisabled: { opacity: 0.6, }, modalSaveText: { color: '#ffffff', fontSize: 16, fontWeight: '600', }, // Avatar Modal styles avatarModalContent: { borderTopLeftRadius: 20, borderTopRightRadius: 20, maxHeight: '80%', }, tabContainer: { flexDirection: 'row', borderBottomWidth: 1, }, tabButton: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingVertical: spacing.md, gap: spacing.xs, }, tabText: { fontSize: 14, }, 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, borderRadius: borderRadius.lg, borderWidth: 1, }, uploadIconContainer: { width: 56, height: 56, borderRadius: 28, justifyContent: 'center', alignItems: 'center', marginRight: spacing.md, }, uploadOptionTitle: { fontSize: 16, fontWeight: '600', flex: 1, }, uploadOptionSubtitle: { fontSize: 13, flex: 1, }, // Addresses addressLoadingText: { textAlign: 'center', fontSize: 14, paddingVertical: spacing.lg, }, addressList: { gap: spacing.sm, }, addressItem: { flexDirection: 'row', alignItems: 'flex-start', justifyContent: 'space-between', padding: spacing.md, borderRadius: borderRadius.lg, borderWidth: 1, }, addressItemInfo: { flex: 1, gap: 2, }, addressLabel: { fontSize: 13, fontWeight: '700', textTransform: 'uppercase', }, addressText: { fontSize: 14, }, addressBadge: { alignSelf: 'flex-start', borderRadius: borderRadius.full, paddingHorizontal: spacing.sm, paddingVertical: 2, marginTop: 4, }, addressBadgeText: { color: '#ffffff', fontSize: 11, fontWeight: '700', textTransform: 'uppercase', }, addressSetDefault: { fontSize: 12, fontWeight: '600', 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, borderStyle: 'dashed', borderRadius: borderRadius.lg, }, addressAddBtnText: { fontSize: 15, fontWeight: '600', }, addressDefaultCheckbox: { flexDirection: 'row', alignItems: 'center', gap: spacing.sm, marginBottom: spacing.md, }, addressDefaultLabel: { fontSize: 14, }, });