Files
expo-popcore-app/hooks/use-update-profile.ts
imeepos bf11241d68 feat: 实现用户名和头像编辑功能
使用 Better Auth 的 updateUser API 更新用户信息,使用 SDK 的 FileController 上传头像。

## 新增功能
- 创建 useUpdateProfile hook 处理用户信息更新
- 支持选择图片上传头像(使用 expo-image-picker)
- 先上传头像到 S3,再更新用户信息

## 更新文件
- lib/auth.ts: 导出 updateUser 方法
- hooks/use-update-profile.ts: 新建更新资料 hook
- hooks/index.ts: 导出 useUpdateProfile
- components/drawer/EditProfileDrawer.tsx:
  - 添加头像选择功能(点击相机按钮)
  - 调用 updateProfile API 保存更改
  - 添加加载状态和禁用状态
  - 更新 onSave 回调参数类型
- app/(tabs)/my.tsx:
  - 传递 initialAvatar 给编辑抽屉
  - 显示用户真实头像(如有)
  - 更新 onSave 回调处理

## 功能流程
1. 点击相机按钮选择图片
2. 调用 uploadFile 上传到 S3
3. 调用 updateUser 更新用户信息
4. 保存成功后刷新 session

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-23 19:42:13 +08:00

105 lines
2.6 KiB
TypeScript

import { useState, useCallback } from 'react'
import { ImagePickerAsset, launchImageLibraryAsync, MediaTypeOptions } from 'expo-image-picker'
import Toast from '@/components/ui/Toast'
import { updateUser } from '@/lib/auth'
import { uploadFile } from '@/lib/uploadFile'
import { type ApiError } from '@/lib/types'
export interface UpdateProfileParams {
name?: string
image?: ImagePickerAsset
}
export function useUpdateProfile() {
const [loading, setLoading] = useState(false)
const [error, setError] = useState<ApiError | null>(null)
/**
* 选择图片
*/
const pickImage = useCallback(async (): Promise<ImagePickerAsset | null> => {
try {
const result = await launchImageLibraryAsync({
mediaTypes: MediaTypeOptions.Images,
allowsEditing: true,
aspect: [1, 1],
quality: 0.8,
})
if (result.canceled) {
return null
}
return result.assets[0] || null
} catch (err) {
console.error('选择图片失败:', err)
Toast.show('选择图片失败')
return null
}
}, [])
/**
* 更新用户资料
*/
const updateProfile = useCallback(async (params: UpdateProfileParams) => {
setLoading(true)
setError(null)
try {
const updateData: Record<string, any> = {}
// 如果有新头像,先上传图片
if (params.image) {
Toast.showLoading({ title: '上传头像中...' })
try {
const imageUrl = await uploadFile({
uri: params.image.uri,
mimeType: params.image.mimeType || 'image/jpeg',
fileName: `avatar_${Date.now()}.jpg`,
})
updateData.image = imageUrl
Toast.hideLoading()
} catch (err) {
Toast.hideLoading()
throw new Error('头像上传失败')
}
}
// 更新用户名
if (params.name) {
updateData.name = params.name
}
// 如果有数据需要更新
if (Object.keys(updateData).length > 0) {
Toast.showLoading({ title: '保存中...' })
const result = await updateUser(updateData)
Toast.hideLoading()
if (result.error) {
throw result.error
}
Toast.show('保存成功')
return { data: result.data, error: null }
}
return { data: null, error: null }
} catch (err) {
const errorObj = err as ApiError
setError(errorObj)
Toast.show(errorObj.message || '保存失败,请稍后重试')
return { data: null, error: errorObj }
} finally {
setLoading(false)
}
}, [])
return {
loading,
error,
pickImage,
updateProfile,
}
}