Files
expo-popcore-app/lib/auth.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

129 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'reflect-metadata'
import { expoClient } from '@better-auth/expo/client'
import { createSkerClientPlugin } from '@repo/sdk'
import {
adminClient,
emailOTPClient,
genericOAuthClient,
jwtClient,
organizationClient,
phoneNumberClient,
usernameClient,
} from 'better-auth/client/plugins'
import { createAuthClient } from 'better-auth/react'
import { fetchWithLogger, TOKEN_KEY } from './fetch-logger'
import type { Subscription } from './plugins/stripe'
import { stripeClient } from './plugins/stripe-plugin'
import { storage } from './storage'
import type { ApiError } from './types'
// 商户ID配置 - 从环境变量或应用配置中读取
export const OWNER_ID = 't0m9cketSQdCA6cHXI9mXQLJPM9LDIw5'
export interface CreditBalance {
tokenUsage: number
remainingTokenBalance: number
currentCreditBalance: number
unitPrice: number
}
export interface SubscriptionListItem extends Subscription {
type?: 'metered' | 'licenced'
limits?: Record<string, number>
creditBalance?: CreditBalance
highestTierPlan?: string
highestTierCredits?: number
}
export interface SubscriptionListParams {
query?: {
referenceId?: string
}
}
export interface SubscriptionRestoreParams {
referenceId?: string
subscriptionId?: string
}
export interface SubscriptionRestoreResponse {
success: boolean
subscription: {
id: string
stripeSubscriptionId: string
cancelAtPeriodEnd: boolean
periodEnd?: Date
status: string
}
}
export interface BillingPortalParams {
locale?: string
referenceId?: string
returnUrl?: string
}
export interface BillingPortalResponse {
url: string
redirect: boolean
}
export interface ISubscription {
list: (params?: SubscriptionListParams) => Promise<{ data?: SubscriptionListItem[]; error?: ApiError }>
restore: (params?: SubscriptionRestoreParams) => Promise<{ data?: SubscriptionRestoreResponse; error?: ApiError }>
billingPortal: (params?: BillingPortalParams) => Promise<{ data?: BillingPortalResponse; error?: ApiError }>
}
// 统一的 Token 存储键名(与 fetch-logger.ts 保持一致)
export const getAuthToken = async () => (await storage.getItem(TOKEN_KEY)) || ''
export const setAuthToken = async (token: string) => {
await storage.setItem(TOKEN_KEY, token)
}
export const authClient = createAuthClient({
baseURL: 'https://api.mixvideo.bowong.cc',
trustedOrigins: ['duooomi://', 'https://api.mixvideo.bowong.cc'],
storage,
scheme: 'duooomi',
fetchOptions: {
headers: {
'Content-Type': 'application/json',
// x-ownerid: 商户ID如果后端需要
// 如果 MERCHANT_ID 为空,则不添加此 header
...(OWNER_ID && { 'x-ownerid': OWNER_ID }),
},
auth: {
type: 'Bearer',
token: async () => {
const Authorization = await getAuthToken()
return Authorization
},
},
customFetchImpl: fetchWithLogger as typeof fetch,
},
plugins: [
createSkerClientPlugin(),
stripeClient(),
usernameClient(),
phoneNumberClient(),
emailOTPClient(),
genericOAuthClient(),
jwtClient(),
adminClient(),
organizationClient(),
expoClient({
storage: storage as any,
}),
],
})
export const { signIn, signUp, signOut, useSession, $Infer, admin, forgetPassword, resetPassword, emailOtp, changePassword, updateUser } =
authClient
// 导出 loomart API来自 createSkerClientPlugin
export const loomart = Reflect.get(authClient, 'loomart')
// 导出 subscription API
export const subscription: ISubscription = Reflect.get(authClient, 'subscription')