import { Ionicons } from '@expo/vector-icons' import { Block, ConfirmModal, Img, Input, Text, Toast } from '@share/components' import * as ImagePicker from 'expo-image-picker' import { router, Stack } from 'expo-router' import { observer } from 'mobx-react-lite' import React, { useEffect, useState } from 'react' import { ScrollView } from 'react-native' import { imgPicker } from '@/@share/apis' import ChangePasswordModal from '@/components/ChangePasswordModal' import { authClient } from '@/lib/auth' import { userStore } from '@/stores' import { uploadFile } from '@/utils' type InfoItem = { id: string label: string value: string valueGray?: boolean onPress: () => void } type EditNicknameModalProps = { initialName: string onConfirm: (name: string) => void | Promise onCancel: () => void } function EditNicknameModal({ initialName, onConfirm, onCancel }: EditNicknameModalProps) { const [name, setName] = useState(initialName) useEffect(() => { setName(initialName) }, [initialName]) return ( } title="修改昵称" onCancel={onCancel} onConfirm={() => onConfirm(name)} /> ) } export default observer(function ProfilePage() { const { user } = userStore const handleEditAvatar = async () => { try { const assets = await imgPicker({ maxImages: 1, type: ImagePicker.MediaTypeOptions.Images, resultType: 'asset', }) const asset = assets[0] as ImagePicker.ImagePickerAsset if (!asset) return Toast.showLoading({ title: '上传中...', duration: 0 }) const url = await uploadFile({ uri: asset.uri, mimeType: asset.mimeType ?? 'image/jpeg', fileName: asset.fileName ?? `avatar_${Date.now()}.jpg`, }) const res = await authClient.updateUser({ image: url }) const err = (res as { error?: { message?: string } }).error if (err) { Toast.hideLoading() Toast.show({ title: err.message || '头像更新失败,请重试' }) return } if (userStore.user) { userStore.setUser({ ...userStore.user, image: url }) } Toast.hideLoading() Toast.show({ title: '头像已更新' }) } catch (e) { Toast.hideLoading() if (e instanceof Error && e.message !== '未选择任何图片') { Toast.show({ title: '上传失败,请重试' }) } } } const handleEditNickname = () => { Toast.showModal( { const trimmed = name.trim() if (trimmed.length < 6 || trimmed.length > 12) { Toast.show({ title: '请输入6-12个字符' }) return } try { const res = await authClient.updateUser({ name: trimmed }) const err = (res as { error?: { message?: string } }).error if (err) { Toast.show({ title: err.message || '修改失败,请重试' }) return } if (userStore.user) { userStore.setUser({ ...userStore.user, name: trimmed }) } Toast.hideModal() Toast.show({ title: '昵称已更新' }) } catch (e) { Toast.show({ title: '修改失败,请重试' }) } }} onCancel={() => Toast.hideModal()} />, ) } const handleChangePassword = () => { Toast.showModal( { try { // better-auth 使用 changePassword 方法修改密码 const res = await (authClient as any).changePassword({ currentPassword, newPassword, }) const err = (res as { error?: { message?: string } }).error if (err) { Toast.show({ title: err.message || '修改失败,请重试' }) return } Toast.hideModal() Toast.show({ title: '密码已修改,请重新登录' }) // 退出登录 await userStore.signOut() // 跳转到登录页 router.replace('/auth') } catch (e) { Toast.show({ title: '修改失败,请重试' }) } }} onCancel={() => Toast.hideModal()} />, ) } const infoItems: InfoItem[] = [ { id: 'nickname', label: '昵称', value: user?.name || '未设置', onPress: handleEditNickname, }, ...(user?.emailVerified ? [ { id: 'email', label: '邮箱', value: user.email, onPress: () => { Toast.show({ title: '邮箱不可修改' }) }, }, { id: 'password', label: '密码', value: '修改密码', valueGray: true, onPress: handleChangePassword, }, ] : []), ...(user?.phoneNumber ? [ { id: 'phone', label: '手机号', value: user.phoneNumber, onPress: () => { Toast.show({ title: '手机号不可修改' }) }, }, ] : []), ] const renderHeader = () => ( router.back()}> 个人信息 ) const renderAvatarSection = () => ( {user?.image ? ( ) : ( )} 编辑 ) const renderInfoList = () => ( {infoItems.map((item) => ( {item.label} {item.value} {item.id !== 'phone' && item.id !== 'email' && } ))} ) return ( {renderHeader()} {renderAvatarSection()} {renderInfoList()} ) })