feat: 验证码组件
This commit is contained in:
parent
1f7a101fc5
commit
f7146adf13
@ -1,10 +1,11 @@
|
|||||||
import Error from "@/assets/icons/svg/error.svg";
|
import Error from "@/assets/icons/svg/error.svg";
|
||||||
import { fetchApi } from "@/lib/server-api-util";
|
import { fetchApi } from "@/lib/server-api-util";
|
||||||
import { User } from "@/types/user";
|
import { User } from "@/types/user";
|
||||||
|
import OTPInputView from '@twotalltotems/react-native-otp-input';
|
||||||
import { router } from "expo-router";
|
import { router } from "expo-router";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { ActivityIndicator, Animated, TextInput as RNTextInput, TextInput, TouchableOpacity, View } from "react-native";
|
import { Animated, TextInput as RNTextInput, StyleSheet, TouchableOpacity, View } from "react-native";
|
||||||
import { useAuth } from "../../contexts/auth-context";
|
import { useAuth } from "../../contexts/auth-context";
|
||||||
import { ThemedText } from "../ThemedText";
|
import { ThemedText } from "../ThemedText";
|
||||||
|
|
||||||
@ -18,7 +19,7 @@ const Code = ({ phone }: CodeProps) => {
|
|||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const refs = useRef<Array<RNTextInput | null>>(Array(6).fill(null));
|
const refs = useRef<Array<RNTextInput | null>>(Array(6).fill(null));
|
||||||
const shakeAnim = useRef(new Animated.Value(0)).current;
|
const shakeAnim = useRef(new Animated.Value(0)).current;
|
||||||
const [code, setCode] = useState<string[]>(['', '', '', '', '', '']);
|
const [code, setCode] = useState<string[]>([]);
|
||||||
const [error, setError] = useState<string>('');
|
const [error, setError] = useState<string>('');
|
||||||
|
|
||||||
const focusNext = (index: number, value: string) => {
|
const focusNext = (index: number, value: string) => {
|
||||||
@ -111,7 +112,7 @@ const Code = ({ phone }: CodeProps) => {
|
|||||||
}, [countdown]);
|
}, [countdown]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className="flex-1 bg-white px-6">
|
<View className="flex-1 bg-white px-2">
|
||||||
<View className="flex-1 justify-center">
|
<View className="flex-1 justify-center">
|
||||||
<View className="items-center mb-8">
|
<View className="items-center mb-8">
|
||||||
<ThemedText className="text-2xl font-semibold mb-2 text-gray-900">
|
<ThemedText className="text-2xl font-semibold mb-2 text-gray-900">
|
||||||
@ -125,60 +126,28 @@ const Code = ({ phone }: CodeProps) => {
|
|||||||
</ThemedText>
|
</ThemedText>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Animated.View
|
<OTPInputView
|
||||||
style={{
|
pinCount={6} // 验证码长度
|
||||||
transform: [{ translateX: shakeAnim }],
|
onCodeChanged={(code) => {
|
||||||
display: 'flex',
|
setCode([code]);
|
||||||
flexDirection: 'row',
|
|
||||||
gap: 24,
|
|
||||||
marginBottom: 16,
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
}}
|
}}
|
||||||
>
|
onCodeFilled={(code) => {
|
||||||
{code.map((digit, index) => (
|
handleTelLogin()
|
||||||
<TextInput
|
}}
|
||||||
key={index}
|
code={code.join('')}
|
||||||
ref={(ref) => {
|
autoFocusOnLoad={false}
|
||||||
if (ref) {
|
codeInputFieldStyle={styles.underlineStyleBase}
|
||||||
refs.current[index] = ref;
|
codeInputHighlightStyle={styles.underlineStyleHighLighted}
|
||||||
}
|
style={styles.otpContainer}
|
||||||
}}
|
placeholderCharacter="-"
|
||||||
style={{ width: 40, height: 40 }}
|
placeholderTextColor="#AC7E35"
|
||||||
className="bg-[#FFF8DE] rounded-xl text-textTertiary text-3xl text-center"
|
/>
|
||||||
keyboardType="number-pad"
|
|
||||||
maxLength={6}
|
|
||||||
value={digit}
|
|
||||||
onChangeText={text => handleCodeChange(text, index)}
|
|
||||||
onKeyPress={({ nativeEvent }) => focusPrevious(index, nativeEvent.key)}
|
|
||||||
selectTextOnFocus
|
|
||||||
caretHidden={true}
|
|
||||||
autoFocus={index === 0}
|
|
||||||
textContentType="oneTimeCode"
|
|
||||||
autoComplete="one-time-code"
|
|
||||||
importantForAutofill="yes"
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Animated.View>
|
|
||||||
<View className={`w-full flex-row justify-end mb-[1rem] items-center ${error ? 'opacity-100' : 'opacity-0'}`}>
|
<View className={`w-full flex-row justify-end mb-[1rem] items-center ${error ? 'opacity-100' : 'opacity-0'}`}>
|
||||||
<Error />
|
<Error />
|
||||||
<ThemedText className="text-base font-medium !text-buttonFill ml-2">
|
<ThemedText className="text-base font-medium !text-buttonFill ml-2">
|
||||||
{error}
|
{error}
|
||||||
</ThemedText>
|
</ThemedText>
|
||||||
</View>
|
</View>
|
||||||
<TouchableOpacity
|
|
||||||
className="bg-buttonFill py-3 rounded-full items-center justify-center"
|
|
||||||
onPress={handleTelLogin}
|
|
||||||
disabled={isLoading}
|
|
||||||
>
|
|
||||||
{isLoading ? (
|
|
||||||
<ActivityIndicator color="#ffffff" />
|
|
||||||
) : (
|
|
||||||
<ThemedText className="!text-white font-medium text-base">
|
|
||||||
{t("auth.telLogin.continue", { ns: 'login' })}
|
|
||||||
</ThemedText>
|
|
||||||
)}
|
|
||||||
</TouchableOpacity>
|
|
||||||
|
|
||||||
<View className="flex-row justify-center mt-4">
|
<View className="flex-row justify-center mt-4">
|
||||||
<ThemedText className="!text-textPrimary">
|
<ThemedText className="!text-textPrimary">
|
||||||
@ -201,5 +170,32 @@ const Code = ({ phone }: CodeProps) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: 20,
|
||||||
|
},
|
||||||
|
otpContainer: {
|
||||||
|
width: '100%',
|
||||||
|
height: 100
|
||||||
|
},
|
||||||
|
underlineStyleBase: {
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
borderWidth: 0,
|
||||||
|
borderRadius: 16,
|
||||||
|
fontSize: 18,
|
||||||
|
color: '#000',
|
||||||
|
textAlign: 'center',
|
||||||
|
backgroundColor: '#FFF8DE'
|
||||||
|
},
|
||||||
|
underlineStyleHighLighted: {
|
||||||
|
borderColor: '#E2793F',
|
||||||
|
backgroundColor: '#FFF8DE',
|
||||||
|
borderWidth: 2,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export default Code
|
export default Code
|
||||||
Loading…
x
Reference in New Issue
Block a user