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, shadows } 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([]); const [showAvatarModal, setShowAvatarModal] = useState(false); const [avatarTab, setAvatarTab] = useState<'presets' | 'colors' | 'upload'>('presets'); 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); 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 {} } 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, }); setFirstName(res.data.first_name || ''); setLastName(res.data.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]?.base64) { const dataUri = `data:${result.assets[0].mimeType};base64,${result.assets[0].base64}`; setAvatarLocalSource(null); setAvatarUrl(dataUri); setShowAvatarModal(false); try { await api.put('/users/me', { avatar_url: dataUri }); } catch {} } } 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]?.base64) { const dataUri = `data:${result.assets[0].mimeType};base64,${result.assets[0].base64}`; setAvatarLocalSource(null); setAvatarUrl(dataUri); setShowAvatarModal(false); try { await api.put('/users/me', { avatar_url: dataUri }); } catch {} } } async function handleSelectPresetAvatar(index: number) { setAvatarUrl(''); setAvatarLocalSource(AVATARS[index]); setShowAvatarModal(false); try { await api.put('/users/me', { avatar_url: `preset_avatar_${index + 1}` }); } catch {} } async function handleSelectColor(index: number) { setAvatarUrl(''); setAvatarLocalSource(COLOR_CIRCLES[index]); setShowAvatarModal(false); try { await api.put('/users/me', { avatar_url: `color_circle_${index + 1}` }); } catch {} } async function handleDeleteSearch(id: number) { try { await api.delete(`/search-history/${id}`); setSearchHistory(prev => prev.filter(i => i.id !== id)); } catch {} } async function loadAddresses() { setAddressesLoading(true); try { const res = await api.get('/addresses'); setAddresses(res.data); } catch {} 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 res = await fetch(url, { method: editingAddressId ? 'PUT' : 'POST', 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'); } 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 {} } async function handleSetDefault(id: number) { try { const res = await fetch(`/api/addresses/${id}/default`, { method: 'PUT', credentials: 'include' }); if (res.ok) loadAddresses(); } catch {} } const handleLogout = () => { 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 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 ; } if (!isAuthenticated) { return ( Inicia Sesión Inicia sesión para acceder a tu perfil, notificaciones y más router.push('/auth/login')}> Iniciar Sesión ); } const displayName = [firstName, lastName].filter(Boolean).join(' ') || user?.username; return ( {/* Header card with avatar + name */} setShowAvatarModal(true)}> {avatarLocalSource ? ( ) : avatarUrl ? ( ) : ( )} {displayName} @{user?.username} {/* Info section */} {(firstName || lastName) && ( Datos personales Nombre {firstName || '—'} Apellidos {lastName || '—'} )} {/* Theme card */} Apariencia {THEME_OPTIONS.map((opt) => ( setThemeMode(opt.mode)} > {opt.label} ))} {/* Menu card */} Configuración Mis Direcciones {isAdmin && ( <> Panel Admin )} {/* Search history */} {searchHistory.length > 0 && ( Búsquedas recientes {searchHistory.map((item, i) => ( {i > 0 && } {item.address} handleDeleteSearch(item.id)} hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}> ))} )} {/* Logout */} Cerrar Sesión {/* ─── Modals ─── */} {/* Avatar Modal */} Cambiar Avatar setShowAvatarModal(false)} hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}> {(['presets', 'colors', 'upload'] as const).map((t) => ( setAvatarTab(t)}> {t === 'presets' ? 'Prediseñado' : t === 'colors' ? 'Colores' : 'Subir'} ))} {avatarTab === 'presets' && ( {AVATARS.map((av, i) => ( handleSelectPresetAvatar(i)}> ))} )} {avatarTab === 'colors' && ( {COLOR_CIRCLES.map((c, i) => ( handleSelectColor(i)}> ))} )} {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)} hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}> {[ { label: 'Nombre', value: configFirstName, onChange: setConfigFirstName, placeholder: 'Tu nombre', icon: 'person-outline' }, { label: 'Apellidos', value: configLastName, onChange: setConfigLastName, placeholder: 'Tus apellidos', icon: 'person-outline' }, { label: 'Correo electrónico', value: configEmail, onChange: setConfigEmail, placeholder: 'tu@email.com', icon: 'mail-outline', keyboard: 'email-address' as const }, { label: 'Ciudad', value: configCity, onChange: setConfigCity, placeholder: 'Tu ciudad', icon: 'business-outline' }, { label: 'Dirección', value: configAddress, onChange: setConfigAddress, placeholder: 'Calle Mayor 1, Madrid', icon: 'location-outline' }, ].map((field) => ( {field.label} ))} {configFeedback && ( {configFeedback.text} )} setShowConfig(false)} disabled={configSaving}> Cancelar {configSaving ? : Guardar} {/* Addresses Modal */} Mis Direcciones { setShowAddresses(false); setShowAddressForm(false); }} hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}> {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 ? ( ) : ( {user?.address && ( Principal {user.address} )} {addresses.map((addr) => ( {addr.label ? {addr.label} : null} {addr.address} {!addr.is_default && ( handleSetDefault(addr.id)}> Marcar como predeterminada )} openEditAddressForm(addr)}> handleDeleteAddress(addr.id)}> ))} Añadir dirección )} )} ); } const styles = StyleSheet.create({ container: { flex: 1 }, // Auth prompt authPrompt: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: spacing.xl, gap: spacing.md }, authIconCircle: { width: 100, height: 100, borderRadius: 50, justifyContent: 'center', alignItems: 'center' }, authTitle: { fontSize: 24, fontWeight: '700', marginTop: spacing.sm }, authTitleTablet: { fontSize: 32 }, authSubtitle: { fontSize: 15, textAlign: 'center', lineHeight: 22 }, authSubtitleTablet: { fontSize: 17, maxWidth: 400 }, authButton: { paddingHorizontal: spacing.xl, paddingVertical: spacing.md, borderRadius: borderRadius.lg, marginTop: spacing.sm }, authButtonText: { fontSize: 16, fontWeight: '700' }, // Header card headerCard: { alignItems: 'center', marginHorizontal: spacing.lg, marginTop: spacing.lg, borderRadius: borderRadius.xl, paddingVertical: spacing.xl, gap: spacing.xs }, avatarRing: { width: 104, height: 104, borderRadius: 52, borderWidth: 3, padding: 2, overflow: 'hidden' }, avatarImage: { width: '100%', height: '100%', borderRadius: 50 }, avatarFallback: { width: '100%', height: '100%', borderRadius: 50, justifyContent: 'center', alignItems: 'center' }, avatarEditBadge: { position: 'absolute', bottom: 2, right: 2, width: 28, height: 28, borderRadius: 14, justifyContent: 'center', alignItems: 'center', borderWidth: 2, borderColor: '#fff' }, displayName: { fontSize: 20, fontWeight: '700', marginTop: spacing.sm }, username: { fontSize: 14 }, // Cards card: { marginHorizontal: spacing.lg, marginTop: spacing.md, borderRadius: borderRadius.xl, padding: spacing.lg }, cardHeader: { flexDirection: 'row', alignItems: 'center', gap: spacing.sm, marginBottom: spacing.md }, cardTitle: { fontSize: 15, fontWeight: '700' }, // Info grid infoGrid: { flexDirection: 'row', gap: spacing.sm }, infoBox: { flex: 1, borderRadius: borderRadius.md, padding: spacing.md }, infoLabel: { fontSize: 11, fontWeight: '600', textTransform: 'uppercase', letterSpacing: 0.5, marginBottom: 2 }, infoValue: { fontSize: 15, fontWeight: '500' }, // Theme themePills: { flexDirection: 'row', borderRadius: borderRadius.md, padding: 3 }, themePill: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 4, paddingVertical: spacing.sm + 2, borderRadius: borderRadius.sm }, themePillText: { fontSize: 13, fontWeight: '500' }, // Menu menuRow: { flexDirection: 'row', alignItems: 'center', gap: spacing.md, paddingVertical: spacing.sm + 2 }, menuIconCircle: { width: 36, height: 36, borderRadius: 18, justifyContent: 'center', alignItems: 'center' }, menuLabel: { flex: 1, fontSize: 15, fontWeight: '500' }, menuDivider: { height: 1, marginVertical: spacing.xs }, // History historyRow: { flexDirection: 'row', alignItems: 'center', gap: spacing.sm, paddingVertical: spacing.sm }, historyAddress: { flex: 1, fontSize: 14 }, // Logout logoutCard: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', marginHorizontal: spacing.lg, marginTop: spacing.md, padding: spacing.md, borderRadius: borderRadius.xl, borderWidth: 1, gap: spacing.sm }, logoutText: { fontSize: 15, fontWeight: '600' }, // Modal shared modalBackdrop: { flex: 1, backgroundColor: 'rgba(0,0,0,0.45)', justifyContent: 'flex-end' }, modalSheet: { borderTopLeftRadius: 20, borderTopRightRadius: 20, maxHeight: '90%' }, modalHandle: { width: 36, height: 4, borderRadius: 2, alignSelf: 'center', marginTop: spacing.sm, marginBottom: spacing.xs }, modalHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: spacing.lg, paddingVertical: spacing.md, borderBottomWidth: 1 }, modalTitle: { fontSize: 17, fontWeight: '700' }, modalBody: { padding: spacing.lg }, modalField: { marginBottom: spacing.md }, modalLabel: { fontSize: 13, fontWeight: '600', marginBottom: spacing.xs }, modalInputRow: { flexDirection: 'row', alignItems: 'center', borderRadius: borderRadius.md, borderWidth: 1, paddingHorizontal: spacing.md }, modalInput: { flex: 1, paddingVertical: spacing.md, fontSize: 16 }, modalFeedback: { flexDirection: 'row', alignItems: 'center', gap: spacing.sm, padding: spacing.md, borderRadius: borderRadius.md, marginBottom: spacing.md, borderWidth: 1 }, modalFeedbackText: { fontSize: 14, flex: 1 }, modalActions: { flexDirection: 'row', justifyContent: 'flex-end', gap: spacing.md, marginTop: spacing.md }, modalCancelBtn: { paddingVertical: spacing.md, paddingHorizontal: spacing.lg, borderRadius: borderRadius.md, borderWidth: 1 }, modalCancelText: { fontSize: 15, fontWeight: '500' }, modalSaveBtn: { paddingVertical: spacing.md, paddingHorizontal: spacing.xl, borderRadius: borderRadius.md, minWidth: 100, alignItems: 'center' }, modalSaveText: { fontSize: 15, fontWeight: '700' }, // Avatar modal avatarTabBar: { flexDirection: 'row', borderBottomWidth: 1 }, avatarTabBtn: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: spacing.xs, paddingVertical: spacing.md, borderBottomWidth: 2, borderBottomColor: 'transparent' }, avatarTabLabel: { fontSize: 13, fontWeight: '500' }, avatarGrid: { flexDirection: 'row', flexWrap: 'wrap', gap: spacing.md }, avatarOption: { width: '30%', aspectRatio: 1 }, avatarOptionImage: { width: '100%', height: '100%', borderRadius: borderRadius.lg }, uploadCard: { flexDirection: 'row', alignItems: 'center', padding: spacing.md, borderRadius: borderRadius.lg, gap: spacing.md }, uploadIconCircle: { width: 48, height: 48, borderRadius: 24, justifyContent: 'center', alignItems: 'center' }, uploadTitle: { fontSize: 15, fontWeight: '600' }, uploadSub: { fontSize: 13, marginTop: 1 }, // Addresses addrCard: { flexDirection: 'row', alignItems: 'flex-start', padding: spacing.md, borderRadius: borderRadius.lg, borderWidth: 1 }, addrLabel: { fontSize: 12, fontWeight: '700', textTransform: 'uppercase', marginBottom: 2 }, addrBadge: { fontSize: 12, fontWeight: '700', textTransform: 'uppercase', marginBottom: 2 }, addrText: { fontSize: 14, lineHeight: 20 }, addrDefaultLink: { fontSize: 12, fontWeight: '600', marginTop: 4, textDecorationLine: 'underline' }, addrAction: { width: 32, height: 32, borderRadius: 8, justifyContent: 'center', alignItems: 'center' }, addAddrBtn: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: spacing.sm, padding: spacing.md, marginTop: spacing.sm, borderWidth: 2, borderStyle: 'dashed', borderRadius: borderRadius.lg }, addAddrText: { fontSize: 15, fontWeight: '600' }, checkboxRow: { flexDirection: 'row', alignItems: 'center', gap: spacing.sm, marginBottom: spacing.md }, checkboxLabel: { fontSize: 14 }, });