import { Input } from "@/components/Input"; import { BORDER_RADIUS, COLORS, SPACING } from "@/constants/theme"; import { useAuth } from "@/contexts/AuthContext"; import { getSetting, setSetting } from "@/services/db"; import { api } from "@/server/api"; import { File, Paths } from "expo-file-system"; import * as ImagePicker from "expo-image-picker"; import { router } from "expo-router"; import React, { useEffect, useState } from "react"; import { ActivityIndicator, Alert, Image, KeyboardAvoidingView, Platform, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View, } from "react-native"; import { SafeAreaView } from "react-native-safe-area-context"; export default function PerfilPage() { const { user, updateUser } = useAuth(); const [name, setName] = useState(user?.name ?? ""); const [bio, setBio] = useState(""); const [photoUri, setPhotoUri] = useState(null); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); useEffect(() => { async function loadProfile() { try { // Carrega local primeiro (rápido, offline) const [savedPhoto, savedBio] = await Promise.all([ getSetting("profilePhoto"), getSetting("profileBio"), ]); if (savedPhoto) setPhotoUri(savedPhoto); if (savedBio) setBio(savedBio); // Atualiza com dados do backend const { data } = await api.get("/users/profile"); const profile = data.data ?? data; if (profile.name) setName(profile.name); if (profile.bio) setBio(profile.bio); if (profile.profilePhoto) setPhotoUri(profile.profilePhoto); } catch { // fica com os dados locais se offline } finally { setLoading(false); } } loadProfile(); }, []); const handlePickPhoto = async () => { const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync(); if (status !== "granted") { Alert.alert("Permissão negada", "Permita o acesso à galeria nas configurações do celular."); return; } const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: "images", allowsEditing: true, aspect: [1, 1], quality: 0.8, }); if (result.canceled || !result.assets[0]) return; const picked = result.assets[0].uri; try { const dest = new File(Paths.document, `avatar_${user?.id ?? "user"}.jpg`); new File(picked).copy(dest); setPhotoUri(dest.uri); } catch { setPhotoUri(picked); } }; const handleSave = async () => { const nameTrimmed = name.trim(); if (!nameTrimmed) { Alert.alert("Nome inválido", "O nome não pode estar vazio."); return; } try { setSaving(true); // Persiste localmente await Promise.all([ updateUser({ name: nameTrimmed }), setSetting("profileBio", bio.trim()), photoUri ? setSetting("profilePhoto", photoUri) : Promise.resolve(), ]); // Envia ao backend await api.patch("/users/profile", { name: nameTrimmed, bio: bio.trim(), profilePhoto: photoUri ?? undefined, }); Alert.alert("Salvo", "Perfil atualizado com sucesso.", [ { text: "OK", onPress: () => router.back() }, ]); } catch (e: any) { const msg = e?.response?.data?.error ?? e?.message ?? "Não foi possível salvar."; Alert.alert("Erro", msg); } finally { setSaving(false); } }; const initials = (user?.name ?? "U") .split(" ") .slice(0, 2) .map((w) => w.charAt(0).toUpperCase()) .join(""); if (loading) { return ( ); } return ( router.back()} style={styles.backButton} activeOpacity={0.7}> Voltar Editar Perfil {/* Avatar */} {photoUri ? ( ) : ( {initials} )} ✏️ Toque para alterar a foto {/* Campos */} Nome E-mail {user?.email} 🔒 O e-mail não pode ser alterado. Bio {bio.length}/200 caracteres {saving ? ( ) : ( Salvar Alterações )} ); } const AVATAR_SIZE = 110; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: COLORS.background }, loadingContainer: { flex: 1, justifyContent: "center", alignItems: "center" }, header: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", paddingHorizontal: SPACING.lg, paddingVertical: SPACING.md, borderBottomWidth: 1, borderBottomColor: COLORS.border, }, backButton: { flexDirection: "row", alignItems: "center", gap: SPACING.xs, width: 70, }, backIcon: { fontSize: 18, color: COLORS.text }, backLabel: { fontSize: 15, color: COLORS.textSecondary, fontWeight: "500" }, headerTitle: { fontSize: 16, fontWeight: "700", color: COLORS.text }, scroll: { padding: SPACING.lg, paddingBottom: 48 }, avatarSection: { alignItems: "center", marginTop: SPACING.xl, marginBottom: SPACING.xxl }, avatarWrapper: { position: "relative" }, avatarImage: { width: AVATAR_SIZE, height: AVATAR_SIZE, borderRadius: AVATAR_SIZE / 2, borderWidth: 3, borderColor: COLORS.borderLight, }, avatarPlaceholder: { width: AVATAR_SIZE, height: AVATAR_SIZE, borderRadius: AVATAR_SIZE / 2, backgroundColor: COLORS.surfaceLight, borderWidth: 3, borderColor: COLORS.borderLight, justifyContent: "center", alignItems: "center", }, avatarInitials: { fontSize: 38, fontWeight: "800", color: COLORS.text }, avatarEditBadge: { position: "absolute", bottom: 4, right: 4, width: 32, height: 32, borderRadius: 16, backgroundColor: COLORS.text, justifyContent: "center", alignItems: "center", borderWidth: 2, borderColor: COLORS.background, }, avatarEditIcon: { fontSize: 14 }, avatarHint: { marginTop: SPACING.md, fontSize: 13, color: COLORS.textTertiary }, form: { gap: SPACING.lg }, fieldGroup: { gap: SPACING.sm }, fieldLabel: { fontSize: 11, fontWeight: "700", color: COLORS.textTertiary, textTransform: "uppercase", letterSpacing: 0.8, }, readonlyField: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", height: 48, borderWidth: 1, borderColor: COLORS.border, backgroundColor: COLORS.surface, borderRadius: BORDER_RADIUS.md, paddingHorizontal: SPACING.md, }, readonlyText: { fontSize: 15, color: COLORS.textSecondary, flex: 1 }, lockIcon: { fontSize: 14 }, bioInput: { borderWidth: 1, borderColor: COLORS.inputBorder, backgroundColor: COLORS.inputBackground, borderRadius: BORDER_RADIUS.md, color: COLORS.inputText, fontSize: 15, paddingHorizontal: SPACING.md, paddingTop: SPACING.md, minHeight: 100, }, fieldHint: { fontSize: 11, color: COLORS.textTertiary }, saveButton: { marginTop: SPACING.xxl, backgroundColor: COLORS.text, borderRadius: BORDER_RADIUS.lg, paddingVertical: SPACING.lg, alignItems: "center", }, saveButtonDisabled: { backgroundColor: COLORS.borderLight }, saveButtonText: { fontSize: 15, fontWeight: "700", color: COLORS.background }, });