import { Ionicons } from '@expo/vector-icons' import { Block, Text, Toast } from '@share/components' import { router } from 'expo-router' 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 } from '@/lib/auth' import { isValidPhone } from '@/utils' import { openUrl } from '@/utils/webview-helper' const APP_NAME = '多米' export default function Auth() { const [phone, setPhone] = useState('') const [code, setCode] = useState('') 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) && countdown === 0 }, [phone, countdown]) // 清理倒计时定时器 useEffect(() => { return () => { if (countdownTimerRef.current) { clearInterval(countdownTimerRef.current) } } }, []) // 获取验证码 const handleSendCode = useCallback(async () => { if (!isValidPhone(phone)) { Toast.show({ title: '请输入有效的手机号' }) return } if (countdown > 0) { return } Toast.showLoading({ title: '正在获取验证码...' }) setLoading(true) try { const result = await phoneNumber.sendOtp({ phoneNumber: phone, }) if (result.error) { // 处理不同类型的错误 const errorMessage = result.error.message || '验证码发送失败' // 如果是 403 错误,可能是请求过于频繁 if (result.error.status === 403) { Toast.show({ title: '请求过于频繁,请稍后再试' }) } else { Toast.show({ title: errorMessage }) } return } Toast.show({ title: '验证码已发送,请查收短信' }) setCountdown(60) // 开始倒计时 // 倒计时逻辑 if (countdownTimerRef.current) { clearInterval(countdownTimerRef.current) } countdownTimerRef.current = setInterval(() => { setCountdown((prev) => { if (prev <= 1) { if (countdownTimerRef.current) { clearInterval(countdownTimerRef.current) countdownTimerRef.current = null } return 0 } return prev - 1 }) }, 1000) } catch (error: any) { console.error('获取验证码失败:', error) Toast.show({ title: error.message || '验证码发送失败,请稍后重试' }) } finally { Toast.hideLoading() setLoading(false) } }, [phone, countdown]) // 验证码登录/注册 const handleLogin = useCallback(async () => { if (!phone || !code) { Toast.show({ title: '请填写手机号和验证码' }) return } if (!isValidPhone(phone)) { Toast.show({ title: '请输入有效的手机号' }) return } if (code.length !== 6) { Toast.show({ title: '请输入6位验证码' }) return } if (!agreed) { Toast.show({ title: '请先阅读并同意服务条款和隐私协议' }) return } setLoading(true) Toast.showLoading({ title: '正在验证...' }) try { const result = await phoneNumber.verify({ phoneNumber: phone, code, updatePhoneNumber: true, // 如果已登录,更新手机号 }) if (result.error) { 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 } Toast.show({ title: '登录成功!' }) // 延迟跳转,确保 toast 显示 setTimeout(() => { router.replace('/(tabs)') }, 500) } catch (error: any) { console.error('登录失败:', error) Toast.show({ title: error.message || '登录失败,请稍后重试' }) } finally { Toast.hideLoading() setLoading(false) } }, [phone, code, agreed]) return ( {APP_NAME} 手机号 0 ? undefined : handleSendCode} className={`border-2 border-black px-[6px] py-[4px] ${canSendCode && countdown === 0 ? 'bg-black' : 'bg-gray-200'}`} > {countdown > 0 ? `${countdown}秒` : '获取验证码'} 验证码 setAgreed(!agreed)} className={`size-[20px] items-center justify-center border-[2px] border-black ${agreed ? 'bg-black' : 'bg-white'}`} > {agreed && } 已阅读并同意 openUrl('https://mixvideo.bowong.cc/terms', '服务条款')} > 服务条款 openUrl('https://mixvideo.bowong.cc/privacy', '隐私协议')} > 隐私协议 {loading ? ( ) : ( )} {loading ? '验证中...' : '登录/注册'} © 2025 LOOMART. All rights reserved. 插件当前版本号: {APP_VERSION} ) }