360 lines
9.6 KiB
TypeScript
360 lines
9.6 KiB
TypeScript
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
// import { getApp } from '@react-native-firebase/app';
|
|
// import { getMessaging, getToken } from '@react-native-firebase/messaging';
|
|
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 AppText from 'components/AppText';
|
|
import formatPhone from 'helpers/formatPhone';
|
|
import React, { useEffect, useRef, useState } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import {
|
|
ActivityIndicator,
|
|
ImageBackground,
|
|
KeyboardAvoidingView,
|
|
Platform,
|
|
StyleSheet,
|
|
TextInput,
|
|
TouchableOpacity,
|
|
View,
|
|
} from 'react-native';
|
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
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 ArrowLeft from 'svg/ArrowLeft';
|
|
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 [firebaseToken, setFirebseToken] = useState<{
|
|
// fcmToken: string;
|
|
// deviceId: string;
|
|
// deviceName: string;
|
|
// } | null>();
|
|
|
|
// const app = getApp();
|
|
// const messaging = getMessaging(app);
|
|
|
|
// const getDeviceData = async () => {
|
|
// try {
|
|
// const fcmToken = await getToken(messaging);
|
|
// return {
|
|
// fcmToken,
|
|
// deviceId: await DeviceInfo.getUniqueId(),
|
|
// deviceName: await DeviceInfo.getDeviceName(),
|
|
// };
|
|
// } catch (e) {
|
|
// console.log('Xato:', e);
|
|
// return null;
|
|
// }
|
|
// };
|
|
|
|
// useEffect(() => {
|
|
// getDeviceData().then(data => {
|
|
// setFirebseToken(data);
|
|
// });
|
|
// }, []);
|
|
|
|
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) => {
|
|
console.dir(err);
|
|
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,
|
|
otp: enteredCode,
|
|
otpType: 'REGISTRATION',
|
|
deviceId: '',
|
|
deviceName: '',
|
|
fcmToken: '',
|
|
});
|
|
};
|
|
|
|
return (
|
|
<ImageBackground
|
|
source={Logo}
|
|
style={RegisterStyle.background}
|
|
resizeMode="contain"
|
|
imageStyle={{
|
|
opacity: 0.1,
|
|
height: '100%',
|
|
width: '100%',
|
|
transform: [{ scale: 1 }],
|
|
}}
|
|
>
|
|
<SafeAreaView style={{ flex: 1 }}>
|
|
<View style={styles.langContainer}>
|
|
<TouchableOpacity onPress={() => setStep(1)}>
|
|
<ArrowLeft color={'#000'} />
|
|
</TouchableOpacity>
|
|
<LanguageSelector />
|
|
</View>
|
|
<KeyboardAvoidingView
|
|
style={styles.container}
|
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
|
>
|
|
<View style={styles.content}>
|
|
<AppText style={styles.title}>
|
|
{t('Tasdiqlash kodini kiriting')}
|
|
</AppText>
|
|
<AppText style={styles.message}>
|
|
{formatPhone(phoneNumber)} {t('raqamiga yuborilgan')} {OTP_LENGTH}{' '}
|
|
{t('xonali kodni kiriting.')}
|
|
</AppText>
|
|
<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 && (
|
|
<AppText style={styles.errorText}>{errorConfirm}</AppText>
|
|
)}
|
|
<View style={styles.resendContainer}>
|
|
{canResend ? (
|
|
<TouchableOpacity
|
|
onPress={handleResendCode}
|
|
style={styles.resendButton}
|
|
>
|
|
<AppText style={styles.resendButtonText}>
|
|
{t('Kodni qayta yuborish')}
|
|
</AppText>
|
|
</TouchableOpacity>
|
|
) : (
|
|
<AppText style={styles.timerText}>
|
|
{t('Kodni qayta yuborish vaqti')} ({timer}s)
|
|
</AppText>
|
|
)}
|
|
</View>
|
|
<TouchableOpacity
|
|
style={styles.verifyButton}
|
|
onPress={handleVerifyCode}
|
|
>
|
|
{isPending ? (
|
|
<ActivityIndicator color="#fff" />
|
|
) : (
|
|
<AppText
|
|
style={[
|
|
RegisterStyle.btnText,
|
|
isPending && RegisterStyle.buttonTextDisabled,
|
|
]}
|
|
>
|
|
{t('Tasdiqlash')}
|
|
</AppText>
|
|
)}
|
|
</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,
|
|
fontFamily: 'GolosText-Bold',
|
|
},
|
|
});
|
|
|
|
export default Confirm;
|