366 lines
13 KiB
TypeScript
366 lines
13 KiB
TypeScript
import { zodResolver } from '@hookform/resolvers/zod';
|
||
import { useNavigation } from '@react-navigation/native';
|
||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||
import { authApi } from 'api/auth';
|
||
import { loginPayload } from 'api/auth/type';
|
||
import { Branch, branchApi } from 'api/branch';
|
||
import AppText from 'components/AppText';
|
||
import formatPhone from 'helpers/formatPhone';
|
||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||
import { Controller, useForm } from 'react-hook-form';
|
||
import { useTranslation } from 'react-i18next';
|
||
import {
|
||
ActivityIndicator,
|
||
ImageBackground,
|
||
KeyboardAvoidingView,
|
||
Platform,
|
||
ScrollView,
|
||
TextInput,
|
||
TouchableOpacity,
|
||
View,
|
||
} from 'react-native';
|
||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||
import Logo from 'screens/../../assets/bootsplash/logo_512.png';
|
||
import { LoginFormType, loginSchema } from 'screens/auth/login/lib/form';
|
||
import LanguageSelector from 'screens/auth/select-language/SelectLang';
|
||
import ArrowDown from 'svg/ArrowDown';
|
||
import ArrowLeft from 'svg/ArrowLeft';
|
||
import ArrowUp from 'svg/ArrowUp';
|
||
import { useUserStore } from '../lib/userstore';
|
||
import { Loginstyle } from './styled';
|
||
|
||
const Filial = [
|
||
{ label: 'Toshkent shahar' },
|
||
{ label: 'Andijon viloyati' },
|
||
{ label: 'Samarqand viloyati' },
|
||
{ label: 'Toshkent viloyati' },
|
||
{ label: 'Xorazm viloyati' },
|
||
];
|
||
|
||
const Login = () => {
|
||
const { t } = useTranslation();
|
||
const passportNumberRef = useRef<TextInput>(null);
|
||
const [filialDropdownVisible, setFilialDropdownVisible] = useState(false);
|
||
const navigation = useNavigation<NativeStackNavigationProp<any>>();
|
||
const { setUser, setExpireTime } = useUserStore(state => state);
|
||
const [error, setError] = useState<string>();
|
||
const [rawPhone, setRawPhone] = useState('+998');
|
||
const { data: branchList } = useQuery({
|
||
queryKey: ['branchList'],
|
||
queryFn: branchApi.branchList,
|
||
});
|
||
// const [firebaseToken, setFirebseToken] = useState<{
|
||
// fcmToken: string;
|
||
// deviceId: string;
|
||
// deviceName: string;
|
||
// deviceType: 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(),
|
||
// deviceType: await DeviceInfo.getDeviceType(),
|
||
// };
|
||
// } catch (e) {
|
||
// console.log('Xato:', e);
|
||
// return null;
|
||
// }
|
||
// };
|
||
|
||
// useEffect(() => {
|
||
// getDeviceData().then(data => {
|
||
// setFirebseToken(data);
|
||
// });
|
||
// }, []);
|
||
|
||
const { mutate, isPending } = useMutation({
|
||
mutationFn: (payload: loginPayload) => authApi.login(payload),
|
||
onSuccess: res => {
|
||
navigation.navigate('Login-Confirm');
|
||
setExpireTime(res.data.expireTime);
|
||
},
|
||
onError: err => {
|
||
setError('Xatolik yuz berdi');
|
||
console.dir(err);
|
||
},
|
||
});
|
||
|
||
const {
|
||
control,
|
||
handleSubmit,
|
||
setValue,
|
||
formState: { errors },
|
||
} = useForm<LoginFormType>({
|
||
resolver: zodResolver(loginSchema),
|
||
defaultValues: {
|
||
phone: '',
|
||
passportSeriya: '',
|
||
passportNumber: '',
|
||
},
|
||
});
|
||
|
||
const onSubmit = (data: LoginFormType) => {
|
||
mutate({
|
||
branchId: data.branchId,
|
||
phoneNumber: data.phone,
|
||
passportSerial: `${data.passportSeriya.toUpperCase()}${
|
||
data.passportNumber
|
||
}`,
|
||
fcmToken: '',
|
||
deviceId: '',
|
||
deviceType: '',
|
||
deviceName: '',
|
||
});
|
||
// navigation.navigate('Login-Confirm');
|
||
setUser({
|
||
phoneNumber: data.phone,
|
||
});
|
||
};
|
||
|
||
const handleBackNavigation = useCallback(() => {
|
||
navigation.navigate('select-auth');
|
||
}, [navigation]);
|
||
|
||
const handlePhoneChange = useCallback((text: string) => {
|
||
const digits = text.replace(/\D/g, '');
|
||
const full = digits.startsWith('998') ? digits : `998${digits}`;
|
||
setRawPhone(`+${full}`);
|
||
setValue('phone', full, { shouldValidate: true });
|
||
}, []);
|
||
|
||
const keyboardBehavior = useMemo(
|
||
() => (Platform.OS === 'ios' ? 'padding' : 'height'),
|
||
[],
|
||
);
|
||
|
||
return (
|
||
<ImageBackground
|
||
source={Logo}
|
||
style={Loginstyle.background}
|
||
resizeMode="contain"
|
||
imageStyle={{
|
||
opacity: 0.1,
|
||
height: '100%',
|
||
width: '100%',
|
||
transform: [{ scale: 1 }],
|
||
}}
|
||
>
|
||
<SafeAreaView style={{ flex: 1 }}>
|
||
<View style={Loginstyle.langContainer}>
|
||
<TouchableOpacity onPress={handleBackNavigation}>
|
||
<ArrowLeft color={'#000'} />
|
||
</TouchableOpacity>
|
||
<LanguageSelector />
|
||
</View>
|
||
<KeyboardAvoidingView
|
||
style={Loginstyle.container}
|
||
behavior={keyboardBehavior}
|
||
>
|
||
<ScrollView style={{ flex: 1 }}>
|
||
<View style={Loginstyle.scrollContainer}>
|
||
<View style={Loginstyle.loginContainer}>
|
||
<AppText style={Loginstyle.title}>
|
||
{t('Tizimga kirish')}
|
||
</AppText>
|
||
<Controller
|
||
control={control}
|
||
name="phone"
|
||
render={({ field: { onChange } }) => {
|
||
const formatted = formatPhone(rawPhone);
|
||
return (
|
||
<View>
|
||
<AppText style={Loginstyle.label}>
|
||
{t('Telefon raqami')}
|
||
</AppText>
|
||
<TextInput
|
||
keyboardType="numeric"
|
||
placeholder="+998 90 123-45-67"
|
||
value={formatted}
|
||
onChangeText={handlePhoneChange}
|
||
style={Loginstyle.input}
|
||
placeholderTextColor="#D8DADC"
|
||
maxLength={19} // +998 90 123-45-67 bo'lishi uchun
|
||
/>
|
||
{errors.phone && (
|
||
<AppText style={Loginstyle.errorText}>
|
||
{t(errors.phone.message || '')}
|
||
</AppText>
|
||
)}
|
||
</View>
|
||
);
|
||
}}
|
||
/>
|
||
<View>
|
||
<AppText style={Loginstyle.label}>
|
||
{t('Passport seriya raqami')}
|
||
</AppText>
|
||
<View style={{ flexDirection: 'row' }}>
|
||
<Controller
|
||
control={control}
|
||
name="passportSeriya"
|
||
render={({ field: { onChange, value } }) => (
|
||
<TextInput
|
||
style={[Loginstyle.input, Loginstyle.seriyaInput]}
|
||
placeholder="AA"
|
||
maxLength={2}
|
||
autoCapitalize="characters"
|
||
value={value}
|
||
onChangeText={text => {
|
||
onChange(text);
|
||
if (text.length === 2) {
|
||
passportNumberRef.current?.focus();
|
||
}
|
||
}}
|
||
placeholderTextColor="#D8DADC"
|
||
/>
|
||
)}
|
||
/>
|
||
<Controller
|
||
control={control}
|
||
name="passportNumber"
|
||
render={({ field: { onChange, value } }) => (
|
||
<TextInput
|
||
ref={passportNumberRef}
|
||
style={[Loginstyle.input, Loginstyle.raqamInput]}
|
||
placeholder="1234567"
|
||
maxLength={7}
|
||
keyboardType="numeric"
|
||
value={value}
|
||
onChangeText={text => {
|
||
const onlyNumbers = text.replace(/[^0-9]/g, '');
|
||
onChange(onlyNumbers);
|
||
}}
|
||
placeholderTextColor="#D8DADC"
|
||
/>
|
||
)}
|
||
/>
|
||
</View>
|
||
{(errors.passportSeriya || errors.passportNumber) && (
|
||
<AppText style={Loginstyle.errorText}>
|
||
{t(errors.passportSeriya?.message || '') ||
|
||
t(errors.passportNumber?.message || '')}
|
||
</AppText>
|
||
)}
|
||
</View>
|
||
|
||
<Controller
|
||
control={control}
|
||
name="branchId"
|
||
render={({ field: { value } }) => (
|
||
<View style={{ position: 'relative' }}>
|
||
<AppText style={Loginstyle.label}>{t('Filial')}</AppText>
|
||
<View style={Loginstyle.input}>
|
||
<TouchableOpacity
|
||
style={Loginstyle.selector}
|
||
onPress={() =>
|
||
setFilialDropdownVisible(prev => !prev)
|
||
}
|
||
>
|
||
<AppText
|
||
style={
|
||
value
|
||
? { color: '#000' }
|
||
: Loginstyle.selectedText
|
||
}
|
||
>
|
||
{branchList?.find(e => e.id === value)?.name ||
|
||
t('Filialni tanlang...')}
|
||
</AppText>
|
||
{filialDropdownVisible ? (
|
||
<ArrowUp color={'#000'} />
|
||
) : (
|
||
<ArrowDown color={'#000'} />
|
||
)}
|
||
</TouchableOpacity>
|
||
</View>
|
||
{filialDropdownVisible && (
|
||
<View style={[Loginstyle.dropdown, { maxHeight: 200 }]}>
|
||
<ScrollView nestedScrollEnabled>
|
||
{branchList &&
|
||
branchList.map((item: Branch) => (
|
||
<TouchableOpacity
|
||
key={item.id}
|
||
style={Loginstyle.dropdownItem}
|
||
onPress={() => {
|
||
setValue('branchId', item.id);
|
||
setFilialDropdownVisible(false);
|
||
}}
|
||
>
|
||
<AppText style={Loginstyle.dropdownItemText}>
|
||
{item.name}
|
||
</AppText>
|
||
</TouchableOpacity>
|
||
))}
|
||
</ScrollView>
|
||
</View>
|
||
)}
|
||
{errors.branchId && (
|
||
<AppText style={Loginstyle.errorText}>
|
||
{t(errors.branchId.message || '')}
|
||
</AppText>
|
||
)}
|
||
{error && (
|
||
<AppText style={[Loginstyle.errorText]}>
|
||
{t(error)}
|
||
</AppText>
|
||
)}
|
||
</View>
|
||
)}
|
||
/>
|
||
|
||
<TouchableOpacity
|
||
onPress={handleSubmit(onSubmit)}
|
||
style={Loginstyle.button}
|
||
>
|
||
{isPending ? (
|
||
<ActivityIndicator color="#fff" />
|
||
) : (
|
||
<AppText style={Loginstyle.btnText}>
|
||
{t('Tizimga kirish')}
|
||
</AppText>
|
||
)}
|
||
</TouchableOpacity>
|
||
<View
|
||
style={{
|
||
display: 'flex',
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
}}
|
||
>
|
||
<AppText style={{ fontSize: 14, fontWeight: '500' }}>
|
||
{t('ID va kabinet yo’qmi?')}
|
||
</AppText>
|
||
<TouchableOpacity
|
||
style={Loginstyle.dropdownItem}
|
||
onPress={() => navigation.navigate('Register')}
|
||
>
|
||
<AppText
|
||
style={{
|
||
color: '#28A7E8',
|
||
fontSize: 14,
|
||
fontWeight: '500',
|
||
}}
|
||
>
|
||
{t('Ro’yxatdan o’tish')}
|
||
</AppText>
|
||
</TouchableOpacity>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
</ScrollView>
|
||
</KeyboardAvoidingView>
|
||
</SafeAreaView>
|
||
</ImageBackground>
|
||
);
|
||
};
|
||
|
||
export default Login;
|