Initial commit
This commit is contained in:
320
src/screens/auth/registeration/ui/Confirm.tsx
Normal file
320
src/screens/auth/registeration/ui/Confirm.tsx
Normal file
@@ -0,0 +1,320 @@
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { authApi } from 'api/auth';
|
||||
import { otpPayload, resendPayload } from 'api/auth/type';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
ImageBackground,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import SimpleLineIcons from 'react-native-vector-icons/SimpleLineIcons';
|
||||
import Logo from 'screens/../../assets/bootsplash/logo_512.png';
|
||||
import { useModalStore } from 'screens/auth/registeration/lib/modalStore';
|
||||
import LanguageSelector from 'screens/auth/select-language/SelectLang';
|
||||
import { RootStackParamList } from 'types/types';
|
||||
import { useUserStore } from '../lib/userstore';
|
||||
import { RegisterStyle } from './styled';
|
||||
|
||||
type VerificationCodeScreenNavigationProp = NativeStackNavigationProp<
|
||||
RootStackParamList,
|
||||
'Confirm'
|
||||
>;
|
||||
|
||||
const OTP_LENGTH = 4;
|
||||
|
||||
const Confirm = ({
|
||||
setStep,
|
||||
}: {
|
||||
setStep: React.Dispatch<React.SetStateAction<number>>;
|
||||
}) => {
|
||||
const navigation = useNavigation<VerificationCodeScreenNavigationProp>();
|
||||
const { t } = useTranslation();
|
||||
const [code, setCode] = useState<string[]>(new Array(OTP_LENGTH).fill(''));
|
||||
const [timer, setTimer] = useState(60);
|
||||
const [canResend, setCanResend] = useState(false);
|
||||
const [errorConfirm, setErrorConfirm] = useState<string | null>(null);
|
||||
const inputRefs = useRef<Array<TextInput | null>>([]);
|
||||
const { phoneNumber } = useUserStore(state => state);
|
||||
const { mutate, isPending } = useMutation({
|
||||
mutationFn: (payload: otpPayload) => authApi.verifyOtp(payload),
|
||||
onSuccess: async res => {
|
||||
await AsyncStorage.setItem('token', res.data.accessToken);
|
||||
navigation.navigate('Confirm');
|
||||
setErrorConfirm(null);
|
||||
},
|
||||
onError: (err: any) => {
|
||||
setErrorConfirm(err?.response.data.message);
|
||||
},
|
||||
});
|
||||
|
||||
const { mutate: resendMutate } = useMutation({
|
||||
mutationFn: (payload: resendPayload) => authApi.resendOtp(payload),
|
||||
onSuccess: async res => {
|
||||
setTimer(60);
|
||||
setCanResend(false);
|
||||
setCode(new Array(OTP_LENGTH).fill(''));
|
||||
inputRefs.current[0]?.focus();
|
||||
setErrorConfirm(null);
|
||||
},
|
||||
onError: (err: any) => {
|
||||
setErrorConfirm(err?.response.data.message);
|
||||
},
|
||||
});
|
||||
|
||||
const openModal = useModalStore(state => state.openModal);
|
||||
useEffect(() => {
|
||||
let interval: NodeJS.Timeout | null = null;
|
||||
if (timer > 0) {
|
||||
interval = setInterval(() => {
|
||||
setTimer(prevTimer => prevTimer - 1);
|
||||
}, 1000);
|
||||
} else {
|
||||
setCanResend(true);
|
||||
if (interval) clearInterval(interval);
|
||||
}
|
||||
return () => {
|
||||
if (interval) clearInterval(interval);
|
||||
};
|
||||
}, [timer]);
|
||||
|
||||
const handleCodeChange = (text: string, index: number) => {
|
||||
const newCode = [...code];
|
||||
newCode[index] = text;
|
||||
setCode(newCode);
|
||||
|
||||
if (text.length > 0 && index < OTP_LENGTH - 1) {
|
||||
inputRefs.current[index + 1]?.focus();
|
||||
}
|
||||
if (text.length === 0 && index > 0) {
|
||||
inputRefs.current[index - 1]?.focus();
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyPress = ({ nativeEvent }: any, index: number) => {
|
||||
if (nativeEvent.key === 'Backspace' && code[index] === '' && index > 0) {
|
||||
inputRefs.current[index - 1]?.focus();
|
||||
}
|
||||
};
|
||||
|
||||
const handleResendCode = () => {
|
||||
resendMutate({
|
||||
phoneNumber: phoneNumber,
|
||||
otpType: 'REGISTRATION',
|
||||
});
|
||||
};
|
||||
|
||||
const handleVerifyCode = () => {
|
||||
const enteredCode = code.join('');
|
||||
mutate({
|
||||
phoneNumber: phoneNumber,
|
||||
otp: String(enteredCode),
|
||||
otpType: 'REGISTRATION',
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ImageBackground
|
||||
source={Logo}
|
||||
style={RegisterStyle.background}
|
||||
resizeMode="contain"
|
||||
imageStyle={{
|
||||
opacity: 0.3,
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
transform: [{ scale: 1.5 }],
|
||||
}}
|
||||
>
|
||||
<SafeAreaView style={{ flex: 1 }}>
|
||||
<View style={styles.langContainer}>
|
||||
<TouchableOpacity onPress={() => setStep(1)}>
|
||||
<SimpleLineIcons name="arrow-left" color="#000" size={20} />
|
||||
</TouchableOpacity>
|
||||
<LanguageSelector />
|
||||
</View>
|
||||
<KeyboardAvoidingView
|
||||
style={styles.container}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
>
|
||||
<View style={styles.content}>
|
||||
<Text style={styles.title}>{t('Tasdiqlash kodini kiriting')}</Text>
|
||||
<Text style={styles.message}>
|
||||
{phoneNumber} {t('raqamiga yuborilgan')} {OTP_LENGTH}{' '}
|
||||
{t('xonali kodni kiriting.')}
|
||||
</Text>
|
||||
<View style={styles.otpContainer}>
|
||||
{code.map((digit, index) => (
|
||||
<TextInput
|
||||
key={index}
|
||||
ref={ref => {
|
||||
inputRefs.current[index] = ref;
|
||||
}}
|
||||
style={styles.otpInput}
|
||||
keyboardType="number-pad"
|
||||
maxLength={1}
|
||||
onChangeText={text => handleCodeChange(text, index)}
|
||||
onKeyPress={e => handleKeyPress(e, index)}
|
||||
value={digit}
|
||||
autoFocus={index === 0}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
{errorConfirm !== null && (
|
||||
<Text style={styles.errorText}>{errorConfirm}</Text>
|
||||
)}
|
||||
<View style={styles.resendContainer}>
|
||||
{canResend ? (
|
||||
<TouchableOpacity
|
||||
onPress={handleResendCode}
|
||||
style={styles.resendButton}
|
||||
>
|
||||
<Text style={styles.resendButtonText}>
|
||||
{t('Kodni qayta yuborish')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
<Text style={styles.timerText}>
|
||||
{t('Kodni qayta yuborish vaqti')} ({timer}s)
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
style={styles.verifyButton}
|
||||
onPress={handleVerifyCode}
|
||||
>
|
||||
{isPending ? (
|
||||
<ActivityIndicator color="#fff" />
|
||||
) : (
|
||||
<Text
|
||||
style={[
|
||||
RegisterStyle.btnText,
|
||||
isPending && RegisterStyle.buttonTextDisabled,
|
||||
]}
|
||||
>
|
||||
{t('Tasdiqlash')}
|
||||
</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</SafeAreaView>
|
||||
</ImageBackground>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
safeArea: {
|
||||
flex: 1,
|
||||
backgroundColor: '#f8f9fa',
|
||||
},
|
||||
errorText: {
|
||||
color: 'red',
|
||||
fontSize: 14,
|
||||
fontWeight: '500',
|
||||
marginTop: 10,
|
||||
textAlign: 'center',
|
||||
},
|
||||
langContainer: {
|
||||
width: '100%',
|
||||
marginTop: 10,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
color: '#333',
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
padding: 20,
|
||||
},
|
||||
content: {
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
},
|
||||
title: {
|
||||
fontSize: 24,
|
||||
fontWeight: 'bold',
|
||||
color: '#333',
|
||||
marginBottom: 10,
|
||||
textAlign: 'center',
|
||||
},
|
||||
message: {
|
||||
fontSize: 15,
|
||||
color: '#666',
|
||||
textAlign: 'center',
|
||||
marginBottom: 30,
|
||||
lineHeight: 22,
|
||||
},
|
||||
otpContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
gap: 10,
|
||||
},
|
||||
otpInput: {
|
||||
width: 45,
|
||||
height: 55,
|
||||
borderWidth: 1,
|
||||
borderColor: '#ccc',
|
||||
borderRadius: 8,
|
||||
textAlign: 'center',
|
||||
fontSize: 22,
|
||||
fontWeight: 'bold',
|
||||
color: '#333',
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
resendContainer: {
|
||||
marginBottom: 30,
|
||||
},
|
||||
timerText: {
|
||||
fontSize: 15,
|
||||
color: '#999',
|
||||
},
|
||||
resendButton: {
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 20,
|
||||
borderRadius: 8,
|
||||
},
|
||||
resendButtonText: {
|
||||
fontSize: 15,
|
||||
color: '#007bff',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
verifyButton: {
|
||||
backgroundColor: '#007bff',
|
||||
paddingVertical: 15,
|
||||
paddingHorizontal: 40,
|
||||
borderRadius: 8,
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
elevation: 3,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.2,
|
||||
shadowRadius: 4,
|
||||
},
|
||||
verifyButtonText: {
|
||||
color: '#fff',
|
||||
fontSize: 18,
|
||||
fontWeight: '600',
|
||||
},
|
||||
});
|
||||
|
||||
export default Confirm;
|
||||
Reference in New Issue
Block a user