import { API_ENDPOINTS, buildApiUrl } from '@/assets/config/api'; import { useAuth } from '@/assets/contexts/useAuth'; import ChevronRightIcon from '@/assets/icons/chevron-right.svg'; import UserDeleteIcon from '@/assets/icons/user-xmark-solid-full.svg'; import LogoutIcon from '@/assets/icons/right-from-bracket-solid-full.svg'; import UserIcon from '@/assets/icons/user-solid-full.svg'; import { colors } from '@/assets/styles/colors'; import { UserInfoResponse } from '@/assets/types'; import styles from '@/styles/screens/tabs/perfil.styles'; import { type Href, router } from 'expo-router'; import { useEffect, useMemo, useState } from 'react'; import { LoadingSpinner } from '@/assets/components/LoadingSpinner'; import { Alert, Image, KeyboardAvoidingView, Modal, Platform, Pressable, RefreshControl, ScrollView, Text, TextInput, View, } from 'react-native'; import Animated, { Easing, useAnimatedStyle, useSharedValue, withTiming, } from 'react-native-reanimated'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; const ANIM_DURATION = 280; const formatDisplayName = (nome?: string, apelido?: string) => { const parts = [nome?.trim(), apelido?.trim()].filter((p): p is string => !!p); return parts.length > 0 ? parts.join(' ') : null; }; export default function Perfil() { const insets = useSafeAreaInsets(); const { token, user: authUser, logout, updateProfile, isLoading } = useAuth(); const [userInfo, setUserInfo] = useState(null); const [isLoadingProfile, setIsLoadingProfile] = useState(true); const [refreshing, setRefreshing] = useState(false); const [isEditingOpen, setIsEditingOpen] = useState(false); const [newPassword, setNewPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); const [isDeletingAccount, setIsDeletingAccount] = useState(false); const [confirmModal, setConfirmModal] = useState(null); const progress = useSharedValue(0); const contentHeight = useSharedValue(0); const [measuredHeight, setMeasuredHeight] = useState(0); useEffect(() => { if (measuredHeight > 0) { contentHeight.value = measuredHeight; } }, [measuredHeight, contentHeight]); useEffect(() => { progress.value = withTiming(isEditingOpen ? 1 : 0, { duration: ANIM_DURATION, easing: Easing.out(Easing.cubic), }); }, [isEditingOpen, progress]); const editBodyAnimatedStyle = useAnimatedStyle(() => ({ height: contentHeight.value * progress.value, opacity: progress.value, })); const chevronAnimatedStyle = useAnimatedStyle(() => ({ transform: [{ rotate: `${progress.value * 90}deg` }], })); const getUserInfo = async () => { if (!token) { setUserInfo(null); setIsLoadingProfile(false); return; } setIsLoadingProfile(true); try { const formData = new FormData(); formData.append('token', token); const response = await fetch(buildApiUrl(API_ENDPOINTS.USER_INFO), { method: 'POST', body: formData, }); const data: UserInfoResponse = await response.json(); setUserInfo(data); } catch { Alert.alert('Erro', 'Nao foi possivel carregar os dados do perfil.'); } finally { setIsLoadingProfile(false); } }; const displayName = useMemo(() => { const fromApi = formatDisplayName(userInfo?.user?.nome, userInfo?.user?.apelido); if (fromApi) return fromApi; return formatDisplayName(authUser?.nome, authUser?.apelido); }, [userInfo, authUser]); const displayEmail = userInfo?.user?.email?.trim() || authUser?.email?.trim() || null; useEffect(() => { getUserInfo(); }, [token]); const onRefresh = async () => { setRefreshing(true); await getUserInfo(); setRefreshing(false); }; const handleSave = async () => { if (newPassword || confirmPassword) { if (!newPassword || !confirmPassword) { Alert.alert('Erro', 'Preenche os dois campos de palavra-passe.'); return; } if (newPassword !== confirmPassword) { Alert.alert('Erro', 'As palavras-passe nao coincidem.'); return; } } setIsSubmitting(true); try { await updateProfile( userInfo?.user?.nome, userInfo?.user?.apelido, undefined, newPassword || undefined, ); Alert.alert('Sucesso', 'Perfil atualizado com sucesso.'); setNewPassword(''); setConfirmPassword(''); setIsEditingOpen(false); await getUserInfo(); } catch (err) { Alert.alert('Erro', err instanceof Error ? err.message : 'Nao foi possivel atualizar o perfil.'); } finally { setIsSubmitting(false); } }; const handleLogout = () => { setConfirmModal('logout'); }; const handleDeleteAccount = () => { setConfirmModal('delete'); }; const confirmDeleteAccount = () => { Alert.alert( 'Confirmacao final', 'Todos os dados serao removidos permanentemente. Confirmar eliminacao?', [ { text: 'Cancelar', style: 'cancel' }, { text: 'Eliminar', style: 'destructive', onPress: async () => { if (!token) return; setIsDeletingAccount(true); try { const formData = new FormData(); formData.append('token', token); const response = await fetch(buildApiUrl(API_ENDPOINTS.DELETE_ACCOUNT), { method: 'POST', body: formData, }); const data = await response.json(); if (response.ok && data?.status === 200) { await logout(); router.replace('/login' as Href); Alert.alert('Conta eliminada', 'A sua conta foi eliminada com sucesso.'); } else { Alert.alert('Erro', data?.message || 'Nao foi possivel eliminar a conta.'); } } catch { Alert.alert('Erro', 'Falha ao contactar o servidor.'); } finally { setIsDeletingAccount(false); } }, }, ] ); }; const confirmModalAction = async () => { if (confirmModal === 'logout') { try { await logout(); setConfirmModal(null); router.replace('/login' as Href); } catch { Alert.alert('Erro', 'Nao foi possivel terminar sessao.'); } return; } if (confirmModal === 'delete') { setConfirmModal(null); confirmDeleteAccount(); } }; const keyboardVerticalOffset = Platform.OS === 'ios' ? insets.top + 56 : 0; return ( }> O meu Perfil {isLoadingProfile && !displayName ? ( ) : ( {displayName || 'Utilizador'} )} {displayEmail || '—'} setIsEditingOpen((prev) => !prev)}> Editar Perfil { const h = Math.ceil(e.nativeEvent.layout.height); if (h > 0) setMeasuredHeight(h); }}> Nome setUserInfo((prev) => prev?.user ? { ...prev, user: { ...prev.user, nome: text } } : prev, ) } style={styles.input} placeholder="Nome" /> Apelido setUserInfo((prev) => prev?.user ? { ...prev, user: { ...prev.user, apelido: text } } : prev, ) } style={styles.input} placeholder="Apelido" /> Nova Palavra-passe Confirmar Nova Palavra-passe {isSubmitting ? ( ) : ( <> Guardar Alteracoes )} Log out {isDeletingAccount ? 'A eliminar...' : 'Eliminar Conta'} {confirmModal && ( setConfirmModal(null)}> {confirmModal === 'delete' ? ( ) : ( )} {confirmModal === 'delete' ? 'Eliminar Conta' : 'Fazer Log out'} {confirmModal === 'delete' ? 'Tens a certeza que queres eliminar a tua conta?' : 'Tens a certeza que queres sair da tua conta?'} {confirmModal === 'delete' ? (isDeletingAccount ? 'A eliminar...' : 'Eliminar') : 'Sair'} setConfirmModal(null)}> Cancelar )} ); }