Initial commit

This commit is contained in:
Samandar Turgunboyev
2025-08-26 16:26:59 +05:00
commit fd95422447
318 changed files with 38301 additions and 0 deletions

View File

@@ -0,0 +1,327 @@
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 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,
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 { LoginFormType, loginSchema } from 'screens/auth/login/lib/form';
import LanguageSelector from 'screens/auth/select-language/SelectLang';
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 { 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
}`,
});
// 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.3,
height: '100%',
width: '100%',
transform: [{ scale: 1.5 }],
}}
>
<SafeAreaView style={{ flex: 1 }}>
<View style={Loginstyle.langContainer}>
<TouchableOpacity onPress={handleBackNavigation}>
<SimpleLineIcons name="arrow-left" color="#000" size={20} />
</TouchableOpacity>
<LanguageSelector />
</View>
<KeyboardAvoidingView
style={Loginstyle.container}
behavior={keyboardBehavior}
>
<ScrollView style={{ flex: 1 }}>
<View style={Loginstyle.scrollContainer}>
<View style={Loginstyle.loginContainer}>
<Text style={Loginstyle.title}>{t('Tizimga kirish')}</Text>
<Controller
control={control}
name="phone"
render={({ field: { onChange } }) => {
const formatted = formatPhone(rawPhone);
return (
<View>
<Text style={Loginstyle.label}>
{t('Telefon raqami')}
</Text>
<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 && (
<Text style={Loginstyle.errorText}>
{t(errors.phone.message || '')}
</Text>
)}
</View>
);
}}
/>
<View>
<Text style={Loginstyle.label}>
{t('Passport seriya raqami')}
</Text>
<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) && (
<Text style={Loginstyle.errorText}>
{t(errors.passportSeriya?.message || '') ||
t(errors.passportNumber?.message || '')}
</Text>
)}
</View>
<Controller
control={control}
name="branchId"
render={({ field: { value } }) => (
<View style={{ position: 'relative' }}>
<Text style={Loginstyle.label}>{t('Filial')}</Text>
<View style={Loginstyle.input}>
<TouchableOpacity
style={Loginstyle.selector}
onPress={() =>
setFilialDropdownVisible(prev => !prev)
}
>
<Text
style={
value
? { color: '#000' }
: Loginstyle.selectedText
}
>
{branchList?.find(e => e.id === value)?.name ||
t('Filialni tanlang...')}
</Text>
<SimpleLineIcons
name={
filialDropdownVisible ? 'arrow-up' : 'arrow-down'
}
color="#000"
size={14}
/>
</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);
}}
>
<Text style={Loginstyle.dropdownItemText}>
{item.name}
</Text>
</TouchableOpacity>
))}
</ScrollView>
</View>
)}
{errors.branchId && (
<Text style={Loginstyle.errorText}>
{t(errors.branchId.message || '')}
</Text>
)}
{error && (
<Text style={[Loginstyle.errorText]}>{t(error)}</Text>
)}
</View>
)}
/>
<TouchableOpacity
onPress={handleSubmit(onSubmit)}
style={Loginstyle.button}
>
{isPending ? (
<ActivityIndicator color="#fff" />
) : (
<Text style={Loginstyle.btnText}>
{t('Tizimga kirish')}
</Text>
)}
</TouchableOpacity>
<View
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
<Text style={{ fontSize: 14, fontWeight: '500' }}>
{t('ID va kabinet yoqmi?')}
</Text>
<TouchableOpacity
style={Loginstyle.dropdownItem}
onPress={() => navigation.navigate('Register')}
>
<Text
style={{
color: '#28A7E8',
fontSize: 14,
fontWeight: '500',
}}
>
{t('Royxatdan otish')}
</Text>
</TouchableOpacity>
</View>
</View>
</View>
</ScrollView>
</KeyboardAvoidingView>
</SafeAreaView>
</ImageBackground>
);
};
export default Login;