diff --git a/app/auth.tsx b/app/auth.tsx index a65273d..8b3015d 100644 --- a/app/auth.tsx +++ b/app/auth.tsx @@ -1,13 +1,13 @@ import { Ionicons } from '@expo/vector-icons' import { Block, Text, Toast } from '@share/components' import { router } from 'expo-router' -import React, { useCallback, useMemo, useState } from 'react' +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { TextInput } from 'react-native' import { KeyboardAwareScrollView } from 'react-native-keyboard-controller' import { APP_VERSION } from '@/app.config' import BannerSection from '@/components/BannerSection' -import { phoneNumber, setAuthToken } from '@/lib/auth' +import { phoneNumber } from '@/lib/auth' import { isValidPhone } from '@/utils' import { openUrl } from '@/utils/webview-helper' @@ -19,30 +19,49 @@ export default function Auth() { const [loading, setLoading] = useState(false) const [countdown, setCountdown] = useState(0) const [agreed, setAgreed] = useState(false) + const countdownTimerRef = useRef | null>(null) const canSendCode = useMemo(() => { - return isValidPhone(phone) - }, [phone]) + return isValidPhone(phone) && countdown === 0 + }, [phone, countdown]) - // TODO: 获取验证码接口还未实现,需要后端提供手机号验证码发送接口 + // 清理倒计时定时器 + useEffect(() => { + return () => { + if (countdownTimerRef.current) { + clearInterval(countdownTimerRef.current) + } + } + }, []) + + // 获取验证码 const handleSendCode = useCallback(async () => { - if (!canSendCode) { + if (!isValidPhone(phone)) { Toast.show({ title: '请输入有效的手机号' }) return } - Toast.showLoading({ title: '正在发送验证码...' }) + if (countdown > 0) { + return + } + + Toast.showLoading({ title: '正在获取验证码...' }) setLoading(true) try { - // TODO: 调用获取验证码接口 - // 当前使用 better-auth 的 phoneNumber.sendOtp,但后端可能还未实现 - // 如果后端未实现,这里会报错,需要后端提供手机号验证码发送接口 const result = await phoneNumber.sendOtp({ phoneNumber: phone, }) if (result.error) { - Toast.show({ title: result.error.message || '验证码发送失败,请检查后端接口是否已实现' }) + // 处理不同类型的错误 + const errorMessage = result.error.message || '验证码发送失败' + + // 如果是 403 错误,可能是请求过于频繁 + if (result.error.status === 403) { + Toast.show({ title: '请求过于频繁,请稍后再试' }) + } else { + Toast.show({ title: errorMessage }) + } return } @@ -50,23 +69,31 @@ export default function Auth() { setCountdown(60) // 开始倒计时 // 倒计时逻辑 - const timer = setInterval(() => { + if (countdownTimerRef.current) { + clearInterval(countdownTimerRef.current) + } + countdownTimerRef.current = setInterval(() => { setCountdown((prev) => { if (prev <= 1) { - clearInterval(timer) + if (countdownTimerRef.current) { + clearInterval(countdownTimerRef.current) + countdownTimerRef.current = null + } return 0 } return prev - 1 }) }, 1000) } catch (error: any) { - Toast.show({ title: error.message || '验证码发送失败' }) + console.error('获取验证码失败:', error) + Toast.show({ title: error.message || '验证码发送失败,请稍后重试' }) } finally { Toast.hideLoading() setLoading(false) } - }, [phone, canSendCode]) + }, [phone, countdown]) + // 验证码登录/注册 const handleLogin = useCallback(async () => { if (!phone || !code) { Toast.show({ title: '请填写手机号和验证码' }) @@ -78,15 +105,19 @@ export default function Auth() { return } + if (code.length !== 6) { + Toast.show({ title: '请输入6位验证码' }) + return + } + if (!agreed) { Toast.show({ title: '请先阅读并同意服务条款和隐私协议' }) return } setLoading(true) + Toast.showLoading({ title: '正在验证...' }) try { - // 使用 better-auth 的 phoneNumber.verify 进行验证和登录 - // verify 方法会自动创建会话,如果用户不存在会自动注册 const result = await phoneNumber.verify({ phoneNumber: phone, code, @@ -94,21 +125,35 @@ export default function Auth() { }) if (result.error) { - Toast.show({ title: result.error.message || '验证码错误或已过期' }) + const errorStatus = result.error.status + const errorMessage = result.error.message || '验证失败' + + if (errorStatus === 403) { + Toast.show({ title: '验证码尝试次数过多,请重新获取验证码' }) + setCode('') // 清空验证码 + setCountdown(0) // 重置倒计时,允许重新发送 + if (countdownTimerRef.current) { + clearInterval(countdownTimerRef.current) + countdownTimerRef.current = null + } + } else if (errorStatus === 401) { + Toast.show({ title: '手机号未验证,请先验证手机号' }) + } else { + Toast.show({ title: errorMessage }) + } return } - - // 获取认证 token(如果后端返回) - // better-auth 会自动处理 session,但如果有自定义 token,需要手动设置 - if (result.data && 'token' in result.data && result.data.token) { - await setAuthToken(result.data.token) - } - Toast.show({ title: '登录成功!' }) - router.replace('/(tabs)') + + // 延迟跳转,确保 toast 显示 + setTimeout(() => { + router.replace('/(tabs)') + }, 500) } catch (error: any) { - Toast.show({ title: error.message || '登录失败' }) + console.error('登录失败:', error) + Toast.show({ title: error.message || '登录失败,请稍后重试' }) } finally { + Toast.hideLoading() setLoading(false) } }, [phone, code, agreed]) @@ -160,7 +205,7 @@ export default function Auth() { className={`border-2 border-black px-[6px] py-[4px] ${canSendCode && countdown === 0 ? 'bg-black' : 'bg-gray-200'}`} > - {countdown > 0 ? `${countdown}秒` : '发送验证码'} + {countdown > 0 ? `${countdown}秒` : '获取验证码'} @@ -225,7 +270,7 @@ export default function Auth() { ) : ( )} - {loading ? '处理中...' : '登录'} + {loading ? '验证中...' : '登录/注册'} diff --git a/lib/auth.ts b/lib/auth.ts index 292b059..8bcde29 100644 --- a/lib/auth.ts +++ b/lib/auth.ts @@ -127,9 +127,5 @@ export const { phoneNumber, } = authClient -// TODO: 手机号验证码发送接口还未实现 -// 需要后端提供手机号验证码发送接口,类似于 emailOtp.sendVerificationOtp -// 预期接口:phoneOtp.sendVerificationOtp({ phone, type: 'phone-verification' }) -// 当前在 app/auth.tsx 中的 handleSendCode 函数中使用了临时模拟代码,需要替换为真实 API 调用 export const subscription: ISubscription = Reflect.get(authClient, 'subscription')