import Error from "@/assets/icons/svg/error.svg"; import { fetchApi } from "@/lib/server-api-util"; import { User } from "@/types/user"; import { router } from "expo-router"; import { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { ActivityIndicator, Animated, TextInput as RNTextInput, TextInput, TouchableOpacity, View } from "react-native"; import { useAuth } from "../../contexts/auth-context"; import { ThemedText } from "../ThemedText"; import { Steps } from "./phoneLogin"; interface LoginProps { setSteps: (steps: Steps) => void; phone: string; } const Code = ({ setSteps, phone }: LoginProps) => { const { t } = useTranslation(); const { login } = useAuth(); const [isLoading, setIsLoading] = useState(false); const refs = useRef>(Array(6).fill(null)); const shakeAnim = useRef(new Animated.Value(0)).current; const [code, setCode] = useState(['', '', '', '', '', '']); const [error, setError] = useState(''); const focusNext = (index: number, value: string) => { if (value && index < 5) { refs?.current?.[index + 1]?.focus(); } }; const focusPrevious = (index: number, key: string) => { if (key === 'Backspace' && index > 0 && !code[index]) { refs?.current?.[index - 1]?.focus(); } }; const handleCodeChange = (text: string, index: number) => { setError(''); const newCode = [...code]; // Handle pasted code from SMS if (text.length === 6 && /^\d{6}$/.test(text)) { const digits = text.split(''); setCode(digits); refs.current[5]?.focus(); // Focus on the last input after autofill return; } // Handle manual input if (text.length <= 1) { newCode[index] = text; setCode(newCode); if (text) { focusNext(index, text); } } }; const sendVerificationCode = async () => { try { // 发送验证码 await fetchApi(`/iam/veritification-code`, { method: 'POST', body: JSON.stringify({ phone: phone }), }) } catch (error) { // console.error(t("auth.telLogin.sendCodeError", { ns: 'login' }), error); } } const handleTelLogin = async () => { setError(''); if (!code.join('')) { setError(t("auth.telLogin.codeRequired", { ns: 'login' })); return; } // 如果验证码不是六位,提示错误 if (code.join('').length !== 6) { setError(t("auth.telLogin.codeInvalid", { ns: 'login' })); return; } setIsLoading(true); setCountdown(60); try { await fetchApi(`/iam/login/phone-login`, { method: 'POST', body: JSON.stringify({ phone: phone, code: code.join('') }), }).then((res) => { login(res, res.access_token || '') router.replace('/user-message') }).catch((error) => { // console.log(error); setError(t("auth.telLogin.codeVaild", { ns: 'login' })); }) setIsLoading(false); } catch (error) { setIsLoading(false); // console.error(t("auth.telLogin.codeVaild", { ns: 'login' }), error); } } // 60s倒计时 const [countdown, setCountdown] = useState(0); useEffect(() => { if (countdown > 0) { const timer = setTimeout(() => setCountdown(countdown - 1), 1000); return () => clearTimeout(timer); } }, [countdown]); return ( {t("auth.telLogin.title", { ns: 'login' })} {t("auth.telLogin.secondTitle", { ns: 'login' })} {phone} {code.map((digit, index) => ( { if (ref) { refs.current[index] = ref; } }} style={{ width: 40, height: 40 }} className="bg-[#FFF8DE] rounded-xl text-textTertiary text-3xl text-center" keyboardType="number-pad" maxLength={1} textContentType="oneTimeCode" // For iOS autofill autoComplete='sms-otp' // For Android autofill value={digit} onChangeText={text => handleCodeChange(text, index)} onKeyPress={({ nativeEvent }) => focusPrevious(index, nativeEvent.key)} selectTextOnFocus caretHidden={true} /> ))} {error} {isLoading ? ( ) : ( {t("auth.telLogin.continue", { ns: 'login' })} )} {t("auth.telLogin.sendAgain", { ns: 'login' })} { if (countdown > 0) { return } else { sendVerificationCode() } }}> 0 ? '!text-gray-400' : ''}`}> {countdown > 0 ? `${countdown}s${t("auth.telLogin.resend", { ns: 'login' })}` : t("auth.telLogin.resend", { ns: 'login' })} setSteps('phone')} > {t("auth.telLogin.goBack", { ns: 'login' })} ) } export default Code