import { Ionicons } from '@expo/vector-icons' import { Block, Input, Text, Toast } from '@share/components' import { router } from 'expo-router' import * as Updates from 'expo-updates' import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { TextInput } from 'react-native' import { KeyboardAwareScrollView } from 'react-native-keyboard-controller' import { APP_VERSION, VERSION } from '@/app.config' import BannerSection from '@/components/BannerSection' import { phoneNumber, setAuthToken, signIn } from '@/lib/auth' import { isValidPhone } from '@/utils' const APP_NAME = '多米' type LoginType = 'phone' | 'email' // 判断是否为邮箱格式的辅助函数 const isEmail = (input: string) => { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input) } export default function Auth() { const [loginType, setLoginType] = useState('phone') const [phone, setPhone] = useState('') const [code, setCode] = useState('') const [username, setUsername] = useState('') const [password, setPassword] = useState('') const [showPassword, setShowPassword] = useState(false) const [loading, setLoading] = useState(false) const [countdown, setCountdown] = useState(0) const [agreed, setAgreed] = useState(false) const [currentBranch, setCurrentBranch] = useState('unknown') const countdownTimerRef = useRef | null>(null) const { availableUpdate } = Updates.useUpdates() useEffect(() => { if (!Updates.isEnabled) { setCurrentBranch('development') return } const manifest = Updates.manifest console.log('manifest--------', JSON.stringify(manifest?.extra)) console.log('manifest-------updateId-', JSON.stringify(Updates.updateId)) const branchName = manifest?.metadata?.branchName console.log('当前运行的分支:', branchName) console.log('availableUpdate-------', JSON.stringify(availableUpdate)) if (availableUpdate) { // 获取即将下载/待安装更新的分支名 const targetBranch = availableUpdate.manifest?.metadata?.branchName console.log('检测到新分支更新:', targetBranch) } if (manifest?.extra?.eas?.branch) { setCurrentBranch(manifest.extra.eas.branch) } else if (Updates.channel) { setCurrentBranch(Updates.channel) } else { setCurrentBranch('production') } }, []) 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 } setCountdown(60) // 开始倒计时 Toast.show({ title: '验证码已发送,请查收短信' }) // 倒计时逻辑 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 handlePhoneLogin = 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: code, disableSession: false }, { onSuccess: async (ctx: any) => { const authToken = ctx.response.headers.get('set-auth-token') if (authToken) { setAuthToken(authToken) } }, }, ) if (result.error) { Toast.show({ title: result.error.message || '验证失败' }) } else { Toast.show({ title: '登录成功!' }) router.replace('/(tabs)') } } catch (error: any) { console.error('登录/注册失败:', error) Toast.show({ title: error.message || '操作失败,请稍后重试' }) } finally { Toast.hideLoading() setLoading(false) } }, [phone, code, agreed]) // 邮箱登录 const handleEmailLogin = useCallback(async () => { if (!username || !password) { Toast.show({ title: '请填写账号和密码' }) return } if (!agreed) { Toast.show({ title: '请先阅读并同意服务条款和隐私协议' }) return } setLoading(true) Toast.showLoading({ title: '正在登录...' }) try { let result if (isEmail(username)) { // 如果用户名是邮箱格式,使用邮箱登录 result = await signIn.email( { email: username, password, }, { onSuccess: async (ctx) => { const authToken = ctx.response.headers.get('set-auth-token') if (authToken) { setAuthToken(authToken) } }, onError: (ctx) => { console.error(`[LOGIN] email login error`, ctx) }, }, ) } else { // 如果用户名不是邮箱格式,使用用户名登录 result = await signIn.username( { username, password, }, { onSuccess: async (ctx) => { const authToken = ctx.response.headers.get('set-auth-token') if (authToken) { setAuthToken(authToken) } }, onError: (ctx) => { console.error(`[LOGIN] username login error`, ctx) }, }, ) } if (result.error) { Toast.show({ title: '请输入正确的账号或密码' }) } else { Toast.show({ title: '登录成功!' }) router.replace('/(tabs)') } } catch (error: any) { Toast.show({ title: error.message || '登录失败' }) } finally { Toast.hideLoading() setLoading(false) } }, [username, password, agreed]) const handleLogin = loginType === 'phone' ? handlePhoneLogin : handleEmailLogin // 服务条款同意组件 const renderAgreementCheckbox = () => ( setAgreed(!agreed)} className={`size-[20px] items-center justify-center border-2 border-black ${agreed ? 'bg-black' : 'bg-white'}`} > {agreed && } 已阅读并同意 router.push('/service')}> 服务条款 router.push('/privacy')}> 隐私协议 ) // 登录按钮组件 const renderLoginButton = (buttonText: string = '登录') => ( {loading ? ( ) : ( )} {loading ? '处理中...' : buttonText} ) // 登录方式切换组件 const renderLoginTypeSwitch = () => ( {(['phone', 'email'] as const).map((type) => { const isActive = type === loginType return ( setLoginType(type)}> ) })} ) const renderLoginEmail = () => { if (loginType !== 'email') return null return ( <> 账号 密码 setShowPassword(!showPassword)} className="p-1"> {renderAgreementCheckbox()} router.push('/forgotPassword')}> 忘记密码 {renderLoginButton('登录')} ) } const renderLoginPhone = () => { if (loginType !== 'phone') return null return ( <> 手机号 0 ? undefined : handleSendCode} className={`border-2 border-black px-[6px] py-[4px] ${canSendCode && countdown === 0 ? 'bg-black' : 'bg-gray-200'}`} > {countdown > 0 ? `${countdown}秒` : '获取验证码'} 验证码 {renderAgreementCheckbox()} {renderLoginButton('登录/注册')} ) } return ( {APP_NAME} {renderLoginPhone()} {renderLoginEmail()} {renderLoginTypeSwitch()} © 2025 LOOMART. All rights reserved. 版本号: {VERSION} - {APP_VERSION} - {currentBranch} ) }