added login modal
This commit is contained in:
1
App.tsx
1
App.tsx
@@ -163,6 +163,7 @@ export default function App() {
|
||||
const [seen, token, lang] = await Promise.all([
|
||||
AsyncStorage.getItem('hasSeenOnboarding'),
|
||||
AsyncStorage.getItem('token'),
|
||||
|
||||
AsyncStorage.getItem('language'),
|
||||
]);
|
||||
|
||||
|
||||
@@ -85,8 +85,8 @@ android {
|
||||
applicationId "uz.felix.cpost"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 5
|
||||
versionName "0.5"
|
||||
versionCode 6
|
||||
versionName "0.6"
|
||||
multiDexEnabled true
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
114
src/components/ErrorNotification.tsx
Normal file
114
src/components/ErrorNotification.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Animated,
|
||||
Modal,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import CloseIcon from 'svg/Close';
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
error: string;
|
||||
setVisible: (val: boolean) => void;
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
const ErrorNotification: React.FC<Props> = ({
|
||||
visible,
|
||||
error,
|
||||
setVisible,
|
||||
duration = 3000,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const fadeAnim = useRef(new Animated.Value(0)).current;
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
// fade in
|
||||
Animated.timing(fadeAnim, {
|
||||
toValue: 1,
|
||||
duration: 300,
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
|
||||
// auto-hide after duration
|
||||
const timer = setTimeout(() => {
|
||||
Animated.timing(fadeAnim, {
|
||||
toValue: 0,
|
||||
duration: 300,
|
||||
useNativeDriver: true,
|
||||
});
|
||||
}, duration);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
return (
|
||||
<Modal transparent visible={visible} animationType="none">
|
||||
<View style={styles.overlay}>
|
||||
<Animated.View style={[styles.container, { opacity: fadeAnim }]}>
|
||||
<Text style={styles.title}>{t('Xatolik yuz berdi')}</Text>
|
||||
<Text style={styles.message}>{t(error)}</Text>
|
||||
<TouchableOpacity
|
||||
style={styles.closeBtn}
|
||||
onPress={() =>
|
||||
Animated.timing(fadeAnim, {
|
||||
toValue: 0,
|
||||
duration: 200,
|
||||
useNativeDriver: true,
|
||||
}).start(() => setVisible(false))
|
||||
}
|
||||
>
|
||||
<CloseIcon color="#fff" />
|
||||
</TouchableOpacity>
|
||||
</Animated.View>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
overlay: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0,0,0,0.4)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
container: {
|
||||
width: '80%',
|
||||
backgroundColor: 'white',
|
||||
padding: 20,
|
||||
borderRadius: 12,
|
||||
shadowColor: '#000',
|
||||
shadowOpacity: 0.25,
|
||||
shadowRadius: 6,
|
||||
shadowOffset: { width: 0, height: 3 },
|
||||
elevation: 5,
|
||||
position: 'relative',
|
||||
},
|
||||
title: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
color: 'red',
|
||||
marginBottom: 8,
|
||||
},
|
||||
message: {
|
||||
fontSize: 14,
|
||||
color: '#333',
|
||||
},
|
||||
closeBtn: {
|
||||
position: 'absolute',
|
||||
top: 10,
|
||||
right: 10,
|
||||
backgroundColor: '#ff4d4f',
|
||||
padding: 5,
|
||||
borderRadius: 20,
|
||||
},
|
||||
});
|
||||
|
||||
export default ErrorNotification;
|
||||
@@ -245,5 +245,8 @@
|
||||
"Aloqa uchun": "Для связи",
|
||||
"Email": "Электронная почта",
|
||||
"Telegram": "Телеграм",
|
||||
"Roziman": "Согласен"
|
||||
"Roziman": "Согласен",
|
||||
"To'lov holati": "Статус оплаты",
|
||||
"Bekor qilingan": "Отменено",
|
||||
"Ma'lumotlarni to'liq kiriting": "Введите полные данные"
|
||||
}
|
||||
|
||||
@@ -246,5 +246,8 @@
|
||||
"Aloqa uchun": "Aloqa uchun",
|
||||
"Email": "Email",
|
||||
"Telegram": "Telegram",
|
||||
"Roziman": "Roziman"
|
||||
"Roziman": "Roziman",
|
||||
"To'lov holati": "To'lov holati",
|
||||
"Bekor qilingan": "Bekor qilingan",
|
||||
"Ma'lumotlarni to'liq kiriting": "Ma'lumotlarni to'liq kiriting"
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useMutation } from '@tanstack/react-query';
|
||||
import { authApi } from 'api/auth';
|
||||
import { otpPayload, resendPayload } from 'api/auth/type';
|
||||
import AppText from 'components/AppText';
|
||||
import ErrorNotification from 'components/ErrorNotification';
|
||||
import formatPhone from 'helpers/formatPhone';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -47,6 +48,8 @@ const Confirm = () => {
|
||||
const [canResend, setCanResend] = useState(false);
|
||||
const inputRefs = useRef<Array<TextInput | null>>([]);
|
||||
const { phoneNumber } = useUserStore(state => state);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [error, setError] = useState<string>('Xatolik yuz berdi');
|
||||
|
||||
// const app = getApp();
|
||||
// const messaging = getMessaging(app);
|
||||
@@ -76,11 +79,11 @@ const Confirm = () => {
|
||||
onSuccess: async res => {
|
||||
await AsyncStorage.setItem('token', res.data.accessToken);
|
||||
navigation.navigate('Home');
|
||||
setErrorConfirm(null);
|
||||
console.log(res);
|
||||
setVisible(false);
|
||||
},
|
||||
onError: (err: any) => {
|
||||
setErrorConfirm(err?.response?.data?.message || 'Xato yuz berdi');
|
||||
setError(err?.response?.data?.message || 'Xatolik yuz berdi');
|
||||
setVisible(true);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -91,10 +94,11 @@ const Confirm = () => {
|
||||
setCanResend(false);
|
||||
setCode(new Array(OTP_LENGTH).fill(''));
|
||||
inputRefs.current[0]?.focus();
|
||||
setErrorConfirm(null);
|
||||
setVisible(false);
|
||||
},
|
||||
onError: (err: any) => {
|
||||
setErrorConfirm(err?.response?.data?.message || 'Xato yuz berdi');
|
||||
setError(err?.response?.data?.message || 'Xatolik yuz berdi');
|
||||
setVisible(true);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -165,6 +169,11 @@ const Confirm = () => {
|
||||
</TouchableOpacity>
|
||||
<LanguageSelector />
|
||||
</View>
|
||||
<ErrorNotification
|
||||
setVisible={setVisible}
|
||||
error={error}
|
||||
visible={visible}
|
||||
/>
|
||||
<KeyboardAvoidingView
|
||||
style={styles.container}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useMutation } from '@tanstack/react-query';
|
||||
import { authApi } from 'api/auth';
|
||||
import { loginPayload } from 'api/auth/type';
|
||||
import AppText from 'components/AppText';
|
||||
import ErrorNotification from 'components/ErrorNotification';
|
||||
import formatPhone from 'helpers/formatPhone';
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
@@ -38,11 +39,11 @@ const Filial = [
|
||||
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 [error, setError] = useState<string>('Xatolik yuz berdi');
|
||||
const [rawPhone, setRawPhone] = useState('+998');
|
||||
const [visible, setVisible] = useState(false);
|
||||
// const { data: branchList } = useQuery({
|
||||
// queryKey: ['branchList'],
|
||||
// queryFn: branchApi.branchList,
|
||||
@@ -84,9 +85,14 @@ const Login = () => {
|
||||
navigation.navigate('Login-Confirm');
|
||||
setExpireTime(res.data.expireTime);
|
||||
},
|
||||
onError: err => {
|
||||
setError('Xatolik yuz berdi');
|
||||
console.dir(err);
|
||||
onError: (err: any) => {
|
||||
setVisible(true);
|
||||
|
||||
setError(
|
||||
err?.response?.data?.message ||
|
||||
err?.response?.message ||
|
||||
'Xatolik yuz berdi',
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -105,6 +111,18 @@ const Login = () => {
|
||||
});
|
||||
|
||||
const onSubmit = (data: LoginFormType) => {
|
||||
// validation xatolarini tekshirish
|
||||
if (errors.phone || errors.passportSeriya || errors.passportNumber) {
|
||||
const firstError =
|
||||
errors.phone?.message ||
|
||||
errors.passportSeriya?.message ||
|
||||
errors.passportNumber?.message ||
|
||||
'Xatolik yuz berdi';
|
||||
setError(firstError);
|
||||
setVisible(true);
|
||||
return; // navigation ishlamasligi uchun return
|
||||
}
|
||||
|
||||
mutate({
|
||||
phoneNumber: data.phone,
|
||||
passportSerial: `${data.passportSeriya.toUpperCase()}${
|
||||
@@ -115,10 +133,18 @@ const Login = () => {
|
||||
deviceType: '',
|
||||
deviceName: '',
|
||||
});
|
||||
// navigation.navigate('Login-Confirm');
|
||||
setUser({
|
||||
phoneNumber: data.phone,
|
||||
});
|
||||
|
||||
setUser({ phoneNumber: data.phone });
|
||||
};
|
||||
|
||||
const onErrorSubmit = (errors: any) => {
|
||||
const firstError =
|
||||
errors.phone?.message ||
|
||||
errors.passportSeriya?.message ||
|
||||
errors.passportNumber?.message ||
|
||||
'Xatolik yuz berdi';
|
||||
setError(firstError);
|
||||
setVisible(true);
|
||||
};
|
||||
|
||||
const handleBackNavigation = useCallback(() => {
|
||||
@@ -156,6 +182,15 @@ const Login = () => {
|
||||
</TouchableOpacity>
|
||||
<LanguageSelector />
|
||||
</View>
|
||||
|
||||
{/* error modal start*/}
|
||||
<ErrorNotification
|
||||
setVisible={setVisible}
|
||||
error={error}
|
||||
visible={visible}
|
||||
/>
|
||||
{/* error modal end*/}
|
||||
|
||||
<KeyboardAvoidingView
|
||||
style={Loginstyle.container}
|
||||
behavior={keyboardBehavior}
|
||||
@@ -185,11 +220,6 @@ const Login = () => {
|
||||
placeholderTextColor="#D8DADC"
|
||||
maxLength={19} // +998 90 123-45-67 bo'lishi uchun
|
||||
/>
|
||||
{errors.phone && (
|
||||
<AppText style={Loginstyle.errorText}>
|
||||
{t(errors.phone.message || '')}
|
||||
</AppText>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}}
|
||||
@@ -239,12 +269,6 @@ const Login = () => {
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
{(errors.passportSeriya || errors.passportNumber) && (
|
||||
<AppText style={Loginstyle.errorText}>
|
||||
{t(errors.passportSeriya?.message || '') ||
|
||||
t(errors.passportNumber?.message || '')}
|
||||
</AppText>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* <Controller
|
||||
@@ -313,7 +337,7 @@ const Login = () => {
|
||||
/> */}
|
||||
|
||||
<TouchableOpacity
|
||||
onPress={handleSubmit(onSubmit)}
|
||||
onPress={handleSubmit(onSubmit, onErrorSubmit)}
|
||||
style={Loginstyle.button}
|
||||
>
|
||||
{isPending ? (
|
||||
@@ -324,6 +348,7 @@ const Login = () => {
|
||||
</AppText>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
|
||||
<View
|
||||
style={{
|
||||
display: 'flex',
|
||||
|
||||
@@ -10,6 +10,11 @@ export const Loginstyle = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
errorModal: {
|
||||
width: '80%',
|
||||
backgroundColor: '#fff',
|
||||
padding: 20,
|
||||
},
|
||||
background: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
|
||||
@@ -11,10 +11,10 @@ export const FirstStepSchema = z.object({
|
||||
});
|
||||
|
||||
export const SecondStepSchema = z.object({
|
||||
passportSeriya: z.string().length(2, '2 ta harf kerak'),
|
||||
birthDate: z.string().min(8, 'Majburiy maydon'),
|
||||
passportNumber: z.string().length(7, '7 ta raqam kerak'),
|
||||
jshshir: z.string().length(14, '14 ta raqam kerak'),
|
||||
passportSeriya: z.string().length(2, { message: '2 ta harf kerak' }),
|
||||
birthDate: z.string().min(8, { message: 'Majburiy maydon' }),
|
||||
passportNumber: z.string().length(7, { message: '7 ta raqam kerak' }),
|
||||
jshshir: z.string().min(14, { message: '14 ta raqam kerak' }),
|
||||
});
|
||||
|
||||
export type FirstStepFormType = z.infer<typeof FirstStepSchema>;
|
||||
|
||||
@@ -7,8 +7,9 @@ import { useMutation } from '@tanstack/react-query';
|
||||
import { authApi } from 'api/auth';
|
||||
import { otpPayload, resendPayload } from 'api/auth/type';
|
||||
import AppText from 'components/AppText';
|
||||
import ErrorNotification from 'components/ErrorNotification';
|
||||
import formatPhone from 'helpers/formatPhone';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
@@ -22,7 +23,6 @@ import {
|
||||
} 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';
|
||||
@@ -49,6 +49,8 @@ const Confirm = ({
|
||||
const [errorConfirm, setErrorConfirm] = useState<string | null>(null);
|
||||
const inputRefs = useRef<Array<TextInput | null>>([]);
|
||||
const { phoneNumber } = useUserStore(state => state);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [error, setError] = useState<string>('Xatolik yuz berdi');
|
||||
// const [firebaseToken, setFirebseToken] = useState<{
|
||||
// fcmToken: string;
|
||||
// deviceId: string;
|
||||
@@ -86,8 +88,8 @@ const Confirm = ({
|
||||
setErrorConfirm(null);
|
||||
},
|
||||
onError: (err: any) => {
|
||||
console.dir(err);
|
||||
setErrorConfirm(err?.response.data.message);
|
||||
setError(err?.response?.data?.message || 'Xatolik yuz berdi');
|
||||
setVisible(true);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -101,26 +103,11 @@ const Confirm = ({
|
||||
setErrorConfirm(null);
|
||||
},
|
||||
onError: (err: any) => {
|
||||
setErrorConfirm(err?.response.data.message);
|
||||
setError(err?.response?.data?.message || 'Xatolik yuz berdi');
|
||||
setVisible(true);
|
||||
},
|
||||
});
|
||||
|
||||
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;
|
||||
@@ -179,6 +166,11 @@ const Confirm = ({
|
||||
</TouchableOpacity>
|
||||
<LanguageSelector />
|
||||
</View>
|
||||
<ErrorNotification
|
||||
setVisible={setVisible}
|
||||
error={error}
|
||||
visible={visible}
|
||||
/>
|
||||
<KeyboardAvoidingView
|
||||
style={styles.container}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { authApi } from 'api/auth';
|
||||
import { registerPayload } from 'api/auth/type';
|
||||
import { Branch, branchApi } from 'api/branch';
|
||||
import AppText from 'components/AppText';
|
||||
import ErrorNotification from 'components/ErrorNotification';
|
||||
import formatPhone from 'helpers/formatPhone';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
@@ -57,7 +58,8 @@ const recommended = [
|
||||
const FirstStep = ({ onNext }: { onNext: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const [filialDropdownVisible, setFilialDropdownVisible] = useState(false);
|
||||
const [error, setError] = useState<string>();
|
||||
const [error, setError] = useState<string>('Xatolik yuz berdi');
|
||||
const [visible, setVisible] = useState(false);
|
||||
const { setUser } = useUserStore(state => state);
|
||||
const { data: branchList } = useQuery({
|
||||
queryKey: ['branchList'],
|
||||
@@ -69,8 +71,14 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
|
||||
onSuccess: res => {
|
||||
onNext();
|
||||
},
|
||||
onError: err => {
|
||||
setError('Xatolik yuz berdi');
|
||||
onError: (err: any) => {
|
||||
setVisible(true);
|
||||
|
||||
setError(
|
||||
err?.response?.data?.message ||
|
||||
err?.response?.message ||
|
||||
'Xatolik yuz berdi',
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -104,6 +112,27 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
|
||||
const addressRef = useRef<TextInput>(null);
|
||||
|
||||
const onSubmit = (data: FirstStepFormType) => {
|
||||
if (
|
||||
errors.address ||
|
||||
errors.branchId ||
|
||||
errors.firstName ||
|
||||
errors.lastName ||
|
||||
errors.phoneNumber ||
|
||||
errors.recommend
|
||||
) {
|
||||
const firstError =
|
||||
errors.address?.message ||
|
||||
errors.branchId?.message ||
|
||||
errors.firstName?.message ||
|
||||
errors.lastName?.message ||
|
||||
errors.phoneNumber?.message ||
|
||||
errors.recommend?.message ||
|
||||
'Xatolik yuz berdi';
|
||||
setError(firstError);
|
||||
setVisible(true);
|
||||
return;
|
||||
}
|
||||
|
||||
setUser({
|
||||
firstName: data.firstName,
|
||||
lastName: data.lastName,
|
||||
@@ -123,6 +152,20 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
|
||||
});
|
||||
};
|
||||
|
||||
const onErrorSubmit = (errors: any) => {
|
||||
const firstError =
|
||||
errors.address?.message ||
|
||||
errors.branchId?.message ||
|
||||
errors.firstName?.message ||
|
||||
errors.lastName?.message ||
|
||||
errors.phoneNumber?.message ||
|
||||
errors.recommend?.message;
|
||||
setError(
|
||||
firstError ? "Ma'lumotlarni to'liq kiriting" : 'Xatolik yuz berdi',
|
||||
);
|
||||
setVisible(true);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (route.params?.termsAccepted) {
|
||||
setTermsAccepted(true);
|
||||
@@ -194,6 +237,11 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
|
||||
{t("Ro'yxatdan o'tish")}
|
||||
</AppText>
|
||||
|
||||
<ErrorNotification
|
||||
setVisible={setVisible}
|
||||
error={error}
|
||||
visible={visible}
|
||||
/>
|
||||
{/* Ism */}
|
||||
<Controller
|
||||
control={control}
|
||||
@@ -211,11 +259,6 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
|
||||
returnKeyType="next"
|
||||
onSubmitEditing={() => lastNameRef.current?.focus()}
|
||||
/>
|
||||
{errors.firstName && (
|
||||
<AppText style={RegisterStyle.errorText}>
|
||||
{t(errors.firstName.message || '')}
|
||||
</AppText>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
@@ -239,11 +282,6 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
|
||||
returnKeyType="next"
|
||||
onSubmitEditing={() => phoneRef.current?.focus()}
|
||||
/>
|
||||
{errors.lastName && (
|
||||
<AppText style={RegisterStyle.errorText}>
|
||||
{t(errors.lastName.message || '')}
|
||||
</AppText>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
@@ -280,11 +318,6 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
|
||||
() => setFilialDropdownVisible(true) // ❗ Branch select ochiladi
|
||||
}
|
||||
/>
|
||||
{errors.phoneNumber && (
|
||||
<AppText style={RegisterStyle.errorText}>
|
||||
{t(errors.phoneNumber.message || '')}
|
||||
</AppText>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}}
|
||||
@@ -354,11 +387,6 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
|
||||
</ScrollView>
|
||||
</View>
|
||||
)}
|
||||
{errors.branchId && (
|
||||
<AppText style={RegisterStyle.errorText}>
|
||||
{t(errors.branchId.message || '')}
|
||||
</AppText>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
@@ -386,11 +414,6 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
|
||||
() => setRecommendedDropdownVisible(true) // ❗ recommend select ochiladi
|
||||
}
|
||||
/>
|
||||
{errors.address && (
|
||||
<AppText style={RegisterStyle.errorText}>
|
||||
{t(errors.address.message || '')}
|
||||
</AppText>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
@@ -452,16 +475,6 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
|
||||
</ScrollView>
|
||||
</View>
|
||||
)}
|
||||
{errors.recommend && (
|
||||
<AppText style={RegisterStyle.errorText}>
|
||||
{t(errors.recommend.message || '')}
|
||||
</AppText>
|
||||
)}
|
||||
{error && (
|
||||
<AppText style={[RegisterStyle.errorText]}>
|
||||
{t(error)}
|
||||
</AppText>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
@@ -513,7 +526,7 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
onPress={handleSubmit(onSubmit)}
|
||||
onPress={handleSubmit(onSubmit, onErrorSubmit)}
|
||||
style={[
|
||||
RegisterStyle.button,
|
||||
(!termsAccepted || isPending) &&
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useMutation } from '@tanstack/react-query';
|
||||
import passportApi, { sendPassportPayload } from 'api/passport';
|
||||
import AppText from 'components/AppText';
|
||||
import DatePickerInput from 'components/DatePicker';
|
||||
import ErrorNotification from 'components/ErrorNotification';
|
||||
import SingleFileDrop from 'components/FileDrop';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
@@ -55,7 +56,6 @@ const SecondStep = () => {
|
||||
const passportNumberRef = useRef<TextInput>(null);
|
||||
const [checkboxAnimation] = useState(new Animated.Value(1));
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [error, setError] = useState<string>('');
|
||||
const { firstName, lastName } = useUserStore(state => state);
|
||||
const passportSeriyaRef = useRef<TextInput>(null);
|
||||
const jshshirRef = useRef<TextInput>(null);
|
||||
@@ -65,6 +65,8 @@ const SecondStep = () => {
|
||||
const route = useRoute<RouteProp<RootStackParamList, 'Register'>>();
|
||||
const [selectedDate, setSelectedDate] = useState<Date | null>(null);
|
||||
const [isDatePickerVisible, setDatePickerVisibility] = useState(false);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [error, setError] = useState<string>('Xatolik yuz berdi');
|
||||
|
||||
const formatDate = (date: Date) => {
|
||||
const day = date.getDate().toString().padStart(2, '0');
|
||||
@@ -80,7 +82,10 @@ const SecondStep = () => {
|
||||
navigation.navigate('Home');
|
||||
},
|
||||
onError: (err: any) => {
|
||||
setError(err.response.data);
|
||||
console.dir(err);
|
||||
|
||||
setError(err?.response?.data || 'Xatolik yuz berdi');
|
||||
setVisible(true);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -112,6 +117,17 @@ const SecondStep = () => {
|
||||
});
|
||||
|
||||
const onSubmit = async (data: SecondStepFormType) => {
|
||||
if (
|
||||
errors.birthDate?.message ||
|
||||
errors.jshshir?.message ||
|
||||
errors.passportNumber?.message ||
|
||||
errors.passportSeriya?.message
|
||||
) {
|
||||
setError("Ma'lumotlarni to'liq kiriting");
|
||||
setVisible(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const [d, m, y] = data.birthDate.split('/');
|
||||
const isoBirthDate = `${y}-${m.padStart(2, '0')}-${d.padStart(2, '0')}`;
|
||||
|
||||
@@ -127,6 +143,11 @@ const SecondStep = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const onErrorSubmit = (errors: any) => {
|
||||
setError("Ma'lumotlarni to'liq kiriting");
|
||||
setVisible(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<ImageBackground
|
||||
source={Logo}
|
||||
@@ -147,6 +168,12 @@ const SecondStep = () => {
|
||||
<LanguageSelector />
|
||||
</View>
|
||||
|
||||
<ErrorNotification
|
||||
setVisible={setVisible}
|
||||
error={error}
|
||||
visible={visible}
|
||||
/>
|
||||
|
||||
<KeyboardAvoidingView
|
||||
style={{ flex: 1 }}
|
||||
behavior="padding"
|
||||
@@ -224,12 +251,6 @@ const SecondStep = () => {
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
{(errors.passportSeriya || errors.passportNumber) && (
|
||||
<AppText style={RegisterStyle.errorText}>
|
||||
{t(errors.passportSeriya?.message || '') ||
|
||||
t(errors.passportNumber?.message || '')}
|
||||
</AppText>
|
||||
)}
|
||||
</View>
|
||||
{/* JSHSHIR */}
|
||||
<Controller
|
||||
@@ -254,11 +275,6 @@ const SecondStep = () => {
|
||||
}
|
||||
onSubmitEditing={() => birthDateRef.current?.focus()}
|
||||
/>
|
||||
{errors.jshshir && (
|
||||
<AppText style={RegisterStyle.errorText}>
|
||||
{t(errors.jshshir.message || '')}
|
||||
</AppText>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
@@ -354,12 +370,6 @@ const SecondStep = () => {
|
||||
<Calendar color="#D8DADC" width={24} height={24} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{errors.birthDate && (
|
||||
<AppText style={RegisterStyle.errorText}>
|
||||
{t(errors.birthDate?.message || '')}
|
||||
</AppText>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
@@ -418,14 +428,9 @@ const SecondStep = () => {
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
{error && (
|
||||
<AppText style={[RegisterStyle.errorText, { fontSize: 14 }]}>
|
||||
{error}
|
||||
</AppText>
|
||||
)}
|
||||
{/* BUTTON */}
|
||||
<TouchableOpacity
|
||||
onPress={handleSubmit(onSubmit)}
|
||||
onPress={handleSubmit(onSubmit, onErrorSubmit)}
|
||||
style={[
|
||||
RegisterStyle.button,
|
||||
!termsAccepted && RegisterStyle.buttonDisabled,
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import AppText from 'components/AppText';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Image,
|
||||
Linking,
|
||||
Modal,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
useWindowDimensions,
|
||||
} from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import SendIntentAndroid from 'react-native-send-intent';
|
||||
import Logo from 'screens/../../assets/bootsplash/logo_512.png';
|
||||
import InfoIcon from 'svg/Info';
|
||||
import { RootStackParamList } from 'types/types';
|
||||
@@ -23,7 +26,18 @@ type LoginScreenNavigationProp = NativeStackNavigationProp<
|
||||
const SelectAuth = () => {
|
||||
const { t } = useTranslation();
|
||||
const navigation = useNavigation<LoginScreenNavigationProp>();
|
||||
const { width } = useWindowDimensions();
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [newRegisterModalVisible, setNewRegisterModalVisible] = useState(false);
|
||||
|
||||
const openTelegram = async () => {
|
||||
const telegramUri = 'tg://resolve?domain=cpostuz';
|
||||
try {
|
||||
const success = await SendIntentAndroid.openAppWithUri(telegramUri);
|
||||
if (!success) Linking.openURL('https://t.me/cpostuz');
|
||||
} catch (error) {
|
||||
Linking.openURL('https://t.me/cpostuz');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView style={{ flex: 1 }}>
|
||||
@@ -60,6 +74,78 @@ const SelectAuth = () => {
|
||||
|
||||
<View style={styles.btnContainer}>
|
||||
<View style={{ gap: 6 }}>
|
||||
<TouchableOpacity
|
||||
onPress={() => setModalVisible(true)}
|
||||
style={{
|
||||
gap: 4,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<InfoIcon color="#000" height={18} width={18} />
|
||||
<AppText style={styles.helperText}>
|
||||
{t("Botdan ro'yxatdan o’tganmisiz")}?
|
||||
</AppText>
|
||||
</TouchableOpacity>
|
||||
<Modal
|
||||
animationType="slide"
|
||||
transparent={true}
|
||||
visible={modalVisible}
|
||||
onRequestClose={() => setModalVisible(false)}
|
||||
>
|
||||
<View style={styles.modalOverlay}>
|
||||
<View style={styles.modalContainer}>
|
||||
<ScrollView>
|
||||
<AppText style={styles.modalTitle}>
|
||||
{t('Yordam')}
|
||||
</AppText>
|
||||
<AppText style={styles.modalText}>
|
||||
{t('Agar siz oldin ')}
|
||||
|
||||
<AppText
|
||||
style={{ color: '#28A7E8' }}
|
||||
onPress={openTelegram}
|
||||
>
|
||||
@cpcargo_bot
|
||||
</AppText>
|
||||
{t(
|
||||
' orqali ro’yxatdan o’tgan bo’lsangiz, tizimga kirish uchun botda ishlatilgan telefon raqamingiz va passport seriya raqamingizni kiriting.',
|
||||
)}
|
||||
</AppText>
|
||||
<AppText style={styles.modalText}>
|
||||
{t('Masalan')}:
|
||||
</AppText>
|
||||
<AppText style={styles.modalText}>
|
||||
{t('Telefon: +998901234567')}
|
||||
</AppText>
|
||||
<AppText style={styles.modalText}>
|
||||
{t('Passport seriya: AA1234567')}
|
||||
</AppText>
|
||||
<AppText style={styles.modalText}>
|
||||
{t(
|
||||
'Shu ma’lumotlarni kiritganingizdan so’ng siz tizimga kirishingiz mumkin.',
|
||||
)}
|
||||
</AppText>
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.closeButton}
|
||||
onPress={() => setModalVisible(false)}
|
||||
>
|
||||
<AppText
|
||||
style={{
|
||||
color: '#fff',
|
||||
textAlign: 'center',
|
||||
fontWeight: 'bold',
|
||||
}}
|
||||
>
|
||||
{t('Yopish')}
|
||||
</AppText>
|
||||
</TouchableOpacity>
|
||||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
<TouchableOpacity
|
||||
onPress={() => navigation.navigate('Login')}
|
||||
style={[
|
||||
@@ -75,30 +161,10 @@ const SelectAuth = () => {
|
||||
{t('Tizimga kirish')}
|
||||
</AppText>
|
||||
</TouchableOpacity>
|
||||
<View
|
||||
style={{
|
||||
gap: 4,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<InfoIcon color="#000" height={18} width={18} />
|
||||
<AppText style={styles.helperText}>
|
||||
{t("Botdan ro'yxatdan o’tganmisiz")}?
|
||||
</AppText>
|
||||
</View>
|
||||
</View>
|
||||
<View style={{ gap: 6 }}>
|
||||
<TouchableOpacity
|
||||
onPress={() => navigation.navigate('Register')}
|
||||
style={styles.button}
|
||||
>
|
||||
<AppText style={styles.btnText}>
|
||||
{t('Ro’yxatdan o’tish')}
|
||||
</AppText>
|
||||
</TouchableOpacity>
|
||||
<View
|
||||
onPress={() => setNewRegisterModalVisible(true)}
|
||||
style={{
|
||||
gap: 4,
|
||||
flexDirection: 'row',
|
||||
@@ -110,8 +176,70 @@ const SelectAuth = () => {
|
||||
<AppText style={styles.helperText}>
|
||||
{t("Yangi ro’yxatdan o'tmoqchimisiz")}?
|
||||
</AppText>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={() => navigation.navigate('Register')}
|
||||
style={styles.button}
|
||||
>
|
||||
<AppText style={styles.btnText}>
|
||||
{t('Ro’yxatdan o’tish')}
|
||||
</AppText>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<Modal
|
||||
animationType="slide"
|
||||
transparent={true}
|
||||
visible={newRegisterModalVisible}
|
||||
onRequestClose={() => setNewRegisterModalVisible(false)}
|
||||
>
|
||||
<View style={styles.modalOverlay}>
|
||||
<View style={styles.modalContainer}>
|
||||
<ScrollView>
|
||||
<AppText style={styles.modalTitle}>{t('Yordam')}</AppText>
|
||||
|
||||
<AppText style={styles.modalText}>
|
||||
{t(
|
||||
'Agar siz yangi foydalanuvchi bo’lsangiz, tizimga kirishdan oldin ro’yxatdan o’tishingiz kerak. Ro’yxatdan o’tish jarayonida sizdan quyidagi ma’lumotlar so’raladi:',
|
||||
)}
|
||||
</AppText>
|
||||
|
||||
<AppText style={styles.modalText}>
|
||||
1. {t('Telefon raqamingiz')}
|
||||
</AppText>
|
||||
<AppText style={styles.modalText}>
|
||||
2. {t('Passport seriya va raqamingiz')}
|
||||
</AppText>
|
||||
<AppText style={styles.modalText}>
|
||||
3. {t('Passport oldi rasmi')}
|
||||
</AppText>
|
||||
<AppText style={styles.modalText}>
|
||||
4. {t('Passport orqa rasmi')}
|
||||
</AppText>
|
||||
|
||||
<AppText style={styles.modalText}>
|
||||
{t(
|
||||
'Shu ma’lumotlarni kiritganingizdan so’ng siz tizimga kirishingiz mumkin.',
|
||||
)}
|
||||
</AppText>
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.closeButton}
|
||||
onPress={() => setNewRegisterModalVisible(false)}
|
||||
>
|
||||
<AppText
|
||||
style={{
|
||||
color: '#fff',
|
||||
textAlign: 'center',
|
||||
fontWeight: 'bold',
|
||||
}}
|
||||
>
|
||||
{t('Yopish')}
|
||||
</AppText>
|
||||
</TouchableOpacity>
|
||||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
@@ -173,4 +301,34 @@ const styles = StyleSheet.create({
|
||||
btnContainer: {
|
||||
gap: 16,
|
||||
},
|
||||
modalOverlay: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
modalContainer: {
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: 12,
|
||||
padding: 20,
|
||||
width: '85%',
|
||||
maxHeight: '70%',
|
||||
},
|
||||
modalTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 10,
|
||||
},
|
||||
modalText: {
|
||||
fontSize: 14,
|
||||
marginBottom: 2,
|
||||
lineHeight: 20,
|
||||
color: '#000',
|
||||
},
|
||||
closeButton: {
|
||||
backgroundColor: '#28A7E8',
|
||||
paddingVertical: 12,
|
||||
borderRadius: 8,
|
||||
marginTop: 10,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -26,9 +26,15 @@ interface ModalSuccessViewProps {
|
||||
visible: boolean;
|
||||
error: boolean;
|
||||
setVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
errorText?: string;
|
||||
}
|
||||
|
||||
const CreateModal = ({ visible, setVisible, error }: ModalSuccessViewProps) => {
|
||||
const CreateModal = ({
|
||||
visible,
|
||||
setVisible,
|
||||
error,
|
||||
errorText,
|
||||
}: ModalSuccessViewProps) => {
|
||||
const navigation = useNavigation<NavigationProp>();
|
||||
const { t } = useTranslation();
|
||||
const [successMet, setSuccessMet] = useState(false);
|
||||
@@ -103,13 +109,18 @@ const CreateModal = ({ visible, setVisible, error }: ModalSuccessViewProps) => {
|
||||
{successMet ? (
|
||||
<View style={styles.content}>
|
||||
{error ? (
|
||||
<LottieView
|
||||
source={Warning}
|
||||
loop
|
||||
autoPlay={true}
|
||||
resizeMode="cover"
|
||||
style={{ width: 100 * scale, height: 100 * scale }}
|
||||
/>
|
||||
<>
|
||||
<LottieView
|
||||
source={Warning}
|
||||
loop
|
||||
autoPlay={true}
|
||||
resizeMode="cover"
|
||||
style={{ width: 100 * scale, height: 100 * scale }}
|
||||
/>
|
||||
<AppText style={styles.status}>
|
||||
{t(errorText!) || t('Xatolik yuz berdi')}
|
||||
</AppText>
|
||||
</>
|
||||
) : (
|
||||
<LottieView
|
||||
source={ProgressBar}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useMutation } from '@tanstack/react-query';
|
||||
import passportApi, { AddPassportPayload } from 'api/passport';
|
||||
import AppText from 'components/AppText';
|
||||
import DatePickerInput from 'components/DatePicker';
|
||||
import ErrorNotification from 'components/ErrorNotification';
|
||||
import SingleFileDrop from 'components/FileDrop';
|
||||
import LayoutTwo from 'components/LayoutTwo';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
@@ -38,9 +39,12 @@ const CreatePassword = () => {
|
||||
const [isDatePickerVisible, setDatePickerVisibility] = useState(false);
|
||||
const [selectedDate, setSelectedDate] = useState<Date | null>(null);
|
||||
const [success, setSuccess] = React.useState(false);
|
||||
const [error, setError] = useState(false);
|
||||
const [error, setError] = useState<string>('Xatolik yuz berdi');
|
||||
const [frontImage, setFrontImage] = useState<FileData | null>(null);
|
||||
const [backImage, setBackImage] = useState<FileData | null>(null);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [errorVisible, setErrorVisible] = useState(false);
|
||||
const [errorText, setErrorText] = useState('');
|
||||
// firstName: '',
|
||||
// lastName: '',
|
||||
// birthDate: '',
|
||||
@@ -58,13 +62,15 @@ const CreatePassword = () => {
|
||||
const res = passportApi.addPassport(payload);
|
||||
return res;
|
||||
},
|
||||
onSuccess: res => {
|
||||
onSuccess: () => {
|
||||
setSuccess(true);
|
||||
},
|
||||
onError: err => {
|
||||
onError: (err: any) => {
|
||||
console.dir(err);
|
||||
|
||||
setError(true);
|
||||
setErrorVisible(true);
|
||||
setErrorText(
|
||||
err?.response?.data || err?.response?.message || 'Xatolik yuz berdi',
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -100,6 +106,28 @@ const CreatePassword = () => {
|
||||
};
|
||||
|
||||
const onSubmit = (data: CreatePassSchemaType) => {
|
||||
if (
|
||||
errors.birthDate ||
|
||||
errors.firstName ||
|
||||
errors.jshshir ||
|
||||
errors.lastName ||
|
||||
errors.passportNumber ||
|
||||
errors.passportSeriya
|
||||
) {
|
||||
const firstError =
|
||||
errors.birthDate?.message ||
|
||||
errors.passportSeriya?.message ||
|
||||
errors.passportNumber?.message ||
|
||||
errors.firstName?.message ||
|
||||
errors.lastName?.message ||
|
||||
errors.jshshir?.message ||
|
||||
'Xatolik yuz berdi';
|
||||
setError(
|
||||
firstError ? "Ma'lumotlarni to'liq kiriting" : 'Xatolik yuz berdi',
|
||||
);
|
||||
setVisible(true);
|
||||
return;
|
||||
}
|
||||
const [d, m, y] = data.birthDate.split('/');
|
||||
const isoBirthDate = `${y}-${m.padStart(2, '0')}-${d.padStart(2, '0')}`;
|
||||
|
||||
@@ -114,6 +142,22 @@ const CreatePassword = () => {
|
||||
passportBackImage: backImage ? `${backImage.base64}` : '',
|
||||
});
|
||||
};
|
||||
|
||||
const onErrorSubmit = (errors: any) => {
|
||||
const firstError =
|
||||
errors.birthDate?.message ||
|
||||
errors.passportSeriya?.message ||
|
||||
errors.passportNumber?.message ||
|
||||
errors.firstName?.message ||
|
||||
errors.lastName?.message ||
|
||||
errors.jshshir?.message ||
|
||||
'Xatolik yuz berdi';
|
||||
setError(
|
||||
firstError ? "Ma'lumotlarni to'liq kiriting" : 'Xatolik yuz berdi',
|
||||
);
|
||||
setVisible(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<LayoutTwo title={t("Yangi pasport qo'shish")}>
|
||||
<KeyboardAvoidingView
|
||||
@@ -127,6 +171,11 @@ const CreatePassword = () => {
|
||||
style={PassportStyle.content}
|
||||
>
|
||||
<View style={PassportStyle.scrollContainer}>
|
||||
<ErrorNotification
|
||||
setVisible={setVisible}
|
||||
error={error}
|
||||
visible={visible}
|
||||
/>
|
||||
<View style={PassportStyle.loginContainer}>
|
||||
<Controller
|
||||
control={control}
|
||||
@@ -144,11 +193,6 @@ const CreatePassword = () => {
|
||||
value={value}
|
||||
placeholderTextColor={'#D8DADC'}
|
||||
/>
|
||||
{errors.firstName && (
|
||||
<AppText style={PassportStyle.errorText}>
|
||||
{t(errors.firstName.message || '')}
|
||||
</AppText>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
@@ -170,11 +214,6 @@ const CreatePassword = () => {
|
||||
onChangeText={onChange}
|
||||
value={value}
|
||||
/>
|
||||
{errors.lastName && (
|
||||
<AppText style={PassportStyle.errorText}>
|
||||
{t(errors.lastName.message || '')}
|
||||
</AppText>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
@@ -230,12 +269,6 @@ const CreatePassword = () => {
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
{(errors.passportSeriya || errors.passportNumber) && (
|
||||
<AppText style={PassportStyle.errorText}>
|
||||
{t(errors.passportSeriya?.message || '') ||
|
||||
t(errors.passportNumber?.message || '')}
|
||||
</AppText>
|
||||
)}
|
||||
</View>
|
||||
<View>
|
||||
<AppText style={PassportStyle.label}>{t('JSHSHIR')}</AppText>
|
||||
@@ -260,11 +293,6 @@ const CreatePassword = () => {
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.jshshir && (
|
||||
<AppText style={PassportStyle.errorText}>
|
||||
{t(errors.jshshir.message || '')}
|
||||
</AppText>
|
||||
)}
|
||||
</View>
|
||||
<View>
|
||||
<Controller
|
||||
@@ -345,12 +373,6 @@ const CreatePassword = () => {
|
||||
<Calendar color="#D8DADC" width={25} height={25} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{errors.birthDate && (
|
||||
<AppText style={PassportStyle.errorText}>
|
||||
{t(errors.birthDate?.message || '')}
|
||||
</AppText>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
@@ -405,7 +427,7 @@ const CreatePassword = () => {
|
||||
</View>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
onPress={handleSubmit(onSubmit)}
|
||||
onPress={handleSubmit(onSubmit, onErrorSubmit)}
|
||||
style={PassportStyle.button}
|
||||
>
|
||||
{isPending ? (
|
||||
@@ -421,7 +443,12 @@ const CreatePassword = () => {
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
{success && (
|
||||
<CreateModal visible={success} setVisible={setSuccess} error={error} />
|
||||
<CreateModal
|
||||
visible={success}
|
||||
setVisible={setSuccess}
|
||||
error={errorVisible}
|
||||
errorText={errorText}
|
||||
/>
|
||||
)}
|
||||
</LayoutTwo>
|
||||
);
|
||||
|
||||
@@ -54,9 +54,9 @@ const MyPassport = ({ getMe, myPassport }: Props) => {
|
||||
<View
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
alignItems: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<AppText style={styles.title}>
|
||||
@@ -227,7 +227,7 @@ const styles = StyleSheet.create({
|
||||
gap: 5,
|
||||
},
|
||||
statusBadge: {
|
||||
alignSelf: 'center',
|
||||
alignSelf: 'flex-end',
|
||||
marginTop: 5,
|
||||
marginBottom: 10,
|
||||
paddingHorizontal: 14,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Layout from 'components/Layout';
|
||||
import LoadingScreen from 'components/LoadingScreen';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { RefreshControl, ScrollView, StyleSheet } from 'react-native';
|
||||
import { RefreshControl, ScrollView } from 'react-native';
|
||||
import ProfileHeader from './ProfileHeader';
|
||||
import ProfilePages from './ProfilePages';
|
||||
|
||||
@@ -61,29 +61,4 @@ const Profile = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
addBtn: {
|
||||
backgroundColor: '#28A7E8',
|
||||
padding: 10,
|
||||
marginBottom: 10,
|
||||
textAlign: 'center',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: '95%',
|
||||
margin: 'auto',
|
||||
borderRadius: 8,
|
||||
flexDirection: 'row',
|
||||
gap: 10,
|
||||
position: 'static',
|
||||
},
|
||||
btnText: {
|
||||
color: '#FFFFFF',
|
||||
fontSize: 18,
|
||||
fontFamily: 'GolosText-Bold',
|
||||
},
|
||||
});
|
||||
|
||||
export default Profile;
|
||||
|
||||
@@ -4,7 +4,6 @@ import AppText from 'components/AppText';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Dimensions,
|
||||
Image,
|
||||
Linking,
|
||||
StyleSheet,
|
||||
@@ -20,9 +19,6 @@ import Plus from 'svg/Plus';
|
||||
import Telegram from 'svg/Telegram';
|
||||
import Trash from 'svg/Trash';
|
||||
|
||||
const { width } = Dimensions.get('window');
|
||||
const isSmallScreen = width < 360;
|
||||
|
||||
const ProfileHeader = ({ userName = 'Samandar' }: { userName?: string }) => {
|
||||
const [imageError, setImageError] = useState(true);
|
||||
const { t } = useTranslation();
|
||||
@@ -48,7 +44,6 @@ const ProfileHeader = ({ userName = 'Samandar' }: { userName?: string }) => {
|
||||
queryKey: ['getMe'],
|
||||
queryFn: authApi.getMe,
|
||||
});
|
||||
|
||||
const [isModalVisible, setModalVisible] = useState(false);
|
||||
|
||||
const openGallery = async () => {
|
||||
|
||||
@@ -12,14 +12,14 @@ import {
|
||||
import CloseIcon from 'svg/Close';
|
||||
import FilterIcon from 'svg/Filter';
|
||||
|
||||
const transportTypes = ['AUTO', 'AVIA'] as const;
|
||||
const transportTypes = ['AVIA', 'AUTO'] as const;
|
||||
type TransportType = (typeof transportTypes)[number];
|
||||
|
||||
interface Props {
|
||||
transportTypes: TransportType;
|
||||
setTransportTypes: (val: TransportType) => void;
|
||||
reys: string;
|
||||
data: PacketsData;
|
||||
data: PacketsData | undefined;
|
||||
setReys: (val: string) => void;
|
||||
setSelectedData: (val: any) => void;
|
||||
}
|
||||
@@ -38,7 +38,7 @@ const Filter = ({
|
||||
|
||||
const styles = makeStyles();
|
||||
const newOrders = React.useMemo(
|
||||
() => data.data.filter(item => item.paymentStatus === 'NEW'),
|
||||
() => data?.data?.filter(item => item.paymentStatus === 'NEW') || [],
|
||||
[data],
|
||||
);
|
||||
|
||||
@@ -88,7 +88,7 @@ const Filter = ({
|
||||
selectedType === type && styles.activeTypeText,
|
||||
]}
|
||||
>
|
||||
{type === 'AUTO' ? t('Avto') : t('Avia')}
|
||||
{type === 'AVIA' ? t('Avia') : t('Auto')}
|
||||
</AppText>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
143
src/screens/status/ui/PaymentFilter.tsx
Normal file
143
src/screens/status/ui/PaymentFilter.tsx
Normal file
@@ -0,0 +1,143 @@
|
||||
import AppText from 'components/AppText';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FlatList, StyleSheet, TouchableOpacity, View } from 'react-native';
|
||||
import CloseIcon from 'svg/Close';
|
||||
import FilterIcon from 'svg/Filter';
|
||||
|
||||
const paymentStatuses = [
|
||||
{ label: "To'lanmagan", value: 'NEW' },
|
||||
{ label: 'Kutilmoqda', value: 'PENDING' },
|
||||
{ label: "To'langan", value: 'PAYED' },
|
||||
{ label: 'Bekor qilingan', value: 'CANCELLED' },
|
||||
];
|
||||
|
||||
interface Props {
|
||||
paymentStatus: string;
|
||||
setPaymentStatus: (val: string) => void;
|
||||
}
|
||||
|
||||
const PaymentFilter = ({ paymentStatus, setPaymentStatus }: Props) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<TouchableOpacity style={styles.card} onPress={() => setOpen(p => !p)}>
|
||||
<FilterIcon color="#000000" width={18} height={18} />
|
||||
<AppText style={styles.text}>{t("To'lov")}</AppText>
|
||||
</TouchableOpacity>
|
||||
|
||||
{open && (
|
||||
<View style={styles.dropdown}>
|
||||
<View style={styles.dropdownHeader}>
|
||||
<AppText style={styles.sectionTitle}>{t("To'lov holati")}</AppText>
|
||||
<TouchableOpacity onPress={() => setOpen(false)}>
|
||||
<CloseIcon />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<FlatList
|
||||
data={paymentStatuses}
|
||||
keyExtractor={item => item.value}
|
||||
scrollEnabled={false}
|
||||
contentContainerStyle={{ gap: 8 }}
|
||||
renderItem={({ item }) => (
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.statusButton,
|
||||
paymentStatus === item.value && styles.activeStatus,
|
||||
]}
|
||||
onPress={() => {
|
||||
setPaymentStatus(item.value);
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
<AppText
|
||||
style={[
|
||||
styles.statusText,
|
||||
paymentStatus === item.value && styles.activeStatusText,
|
||||
]}
|
||||
>
|
||||
{t(item.label)}
|
||||
</AppText>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
borderRadius: 8,
|
||||
alignItems: 'flex-end',
|
||||
position: 'relative',
|
||||
zIndex: 10,
|
||||
},
|
||||
card: {
|
||||
paddingHorizontal: 12,
|
||||
height: 40,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: '#D8DADC',
|
||||
borderRadius: 8,
|
||||
flexDirection: 'row',
|
||||
gap: 4,
|
||||
},
|
||||
text: {
|
||||
color: '#000000',
|
||||
fontWeight: '500',
|
||||
fontSize: 14,
|
||||
},
|
||||
dropdown: {
|
||||
position: 'absolute',
|
||||
top: 50,
|
||||
right: 0,
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: 8,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 10,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.2,
|
||||
shadowRadius: 4,
|
||||
elevation: 5,
|
||||
zIndex: 10,
|
||||
minWidth: 200,
|
||||
},
|
||||
dropdownHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: 10,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontFamily: 'GolosText-Bold',
|
||||
marginBottom: 6,
|
||||
color: '#333',
|
||||
fontSize: 16,
|
||||
},
|
||||
statusButton: {
|
||||
flex: 1,
|
||||
backgroundColor: '#F3FAFF',
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 6,
|
||||
borderRadius: 6,
|
||||
},
|
||||
activeStatus: {
|
||||
backgroundColor: '#28A7E8',
|
||||
},
|
||||
statusText: {
|
||||
color: '#28A7E8',
|
||||
fontWeight: '500',
|
||||
fontSize: 14,
|
||||
textAlign: 'center',
|
||||
},
|
||||
activeStatusText: {
|
||||
color: '#fff',
|
||||
},
|
||||
});
|
||||
|
||||
export default PaymentFilter;
|
||||
@@ -1,3 +1,4 @@
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import packetsApi from 'api/packets';
|
||||
import Layout from 'components/Layout';
|
||||
@@ -19,11 +20,13 @@ import { DataInfo } from '../lib/data';
|
||||
import Filter from './Filter';
|
||||
import Order from './Order';
|
||||
import OrderDetailModal from './OrderDetailModal';
|
||||
import PaymentFilter from './PaymentFilter';
|
||||
import Tabs from './Tabs';
|
||||
|
||||
const Status = () => {
|
||||
const { width: screenWidth } = useWindowDimensions();
|
||||
const scale = screenWidth < 360 ? 0.85 : 1;
|
||||
const [paymentStatus, setPaymentStatus] = useState('NEW');
|
||||
|
||||
const [filter, setFilter] = useState<
|
||||
| 'COLLECTING'
|
||||
@@ -38,7 +41,7 @@ const Status = () => {
|
||||
const [transportTypes, setTransportTypes] = useState<
|
||||
// 'all'|
|
||||
'AUTO' | 'AVIA'
|
||||
>('AUTO');
|
||||
>('AVIA');
|
||||
const [page, setPage] = useState(0);
|
||||
const {
|
||||
data: statusData,
|
||||
@@ -46,15 +49,21 @@ const Status = () => {
|
||||
isLoading,
|
||||
isFetching,
|
||||
} = useQuery({
|
||||
queryKey: ['status', filter, transportTypes, page],
|
||||
queryKey: ['status', filter, transportTypes, page, paymentStatus],
|
||||
queryFn: () =>
|
||||
packetsApi.getPacketsStatus(filter, {
|
||||
page,
|
||||
size: 10,
|
||||
cargoType: transportTypes,
|
||||
sort: 'id',
|
||||
paymentStatus: paymentStatus,
|
||||
direction: 'DESC',
|
||||
}),
|
||||
});
|
||||
|
||||
console.log(AsyncStorage.getItem('token'));
|
||||
console.log('statusData', statusData);
|
||||
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const scaleAnim = React.useRef(new Animated.Value(0.8)).current;
|
||||
const opacityAnim = React.useRef(new Animated.Value(0)).current;
|
||||
@@ -144,20 +153,30 @@ const Status = () => {
|
||||
);
|
||||
}
|
||||
|
||||
if (statusData?.data.length === 0) {
|
||||
if (statusData?.data.length === 0 || statusData === undefined) {
|
||||
return (
|
||||
<Layout>
|
||||
<Tabs filter={filter} setFilter={setFilter} />
|
||||
<View style={styles.controls}>
|
||||
<View style={{ position: 'relative' }}>
|
||||
<View
|
||||
style={{
|
||||
position: 'relative',
|
||||
flexDirection: 'row',
|
||||
gap: 8,
|
||||
}}
|
||||
>
|
||||
<Filter
|
||||
transportTypes={transportTypes}
|
||||
setTransportTypes={setTransportTypes}
|
||||
reys={reys}
|
||||
setReys={setReys}
|
||||
data={statusData!}
|
||||
data={statusData}
|
||||
setSelectedData={setSelectedData}
|
||||
/>
|
||||
<PaymentFilter
|
||||
paymentStatus={paymentStatus}
|
||||
setPaymentStatus={setPaymentStatus}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<NoResult message={t("Hech qanday ma'lumot topilmadi")} />
|
||||
@@ -182,7 +201,13 @@ const Status = () => {
|
||||
/>
|
||||
<View style={styles.searchIcon}>{searchIcon}</View>
|
||||
</View> */}
|
||||
<View style={{ position: 'relative' }}>
|
||||
<View
|
||||
style={{
|
||||
position: 'relative',
|
||||
flexDirection: 'row',
|
||||
gap: 8,
|
||||
}}
|
||||
>
|
||||
<Filter
|
||||
transportTypes={transportTypes}
|
||||
setTransportTypes={setTransportTypes}
|
||||
@@ -191,6 +216,10 @@ const Status = () => {
|
||||
data={statusData!}
|
||||
setSelectedData={setSelectedData}
|
||||
/>
|
||||
<PaymentFilter
|
||||
paymentStatus={paymentStatus}
|
||||
setPaymentStatus={setPaymentStatus}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<Order
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { authApi } from 'api/auth';
|
||||
import packetsApi from 'api/packets';
|
||||
import AppText from 'components/AppText';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
@@ -52,6 +53,12 @@ const ModalPay = ({
|
||||
const { bottom } = useSafeAreaInsets();
|
||||
const [load, setLoad] = React.useState(false);
|
||||
const { t } = useTranslation();
|
||||
const { data: getMe } = useQuery({
|
||||
queryKey: ['getMe'],
|
||||
queryFn: authApi.getMe,
|
||||
});
|
||||
|
||||
console.log(getMe, 'getMe');
|
||||
|
||||
const { mutate, isPending } = useMutation({
|
||||
mutationFn: ({ id, payType }: { id: number; payType: string }) =>
|
||||
@@ -137,45 +144,49 @@ const ModalPay = ({
|
||||
},
|
||||
]}
|
||||
>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.option,
|
||||
{
|
||||
backgroundColor: selectedId === 'card' ? '#28A7E81A' : '#fff',
|
||||
},
|
||||
]}
|
||||
onPress={() => setSelectedId('card')}
|
||||
>
|
||||
<View style={PaymentStyle.paymentCard}>
|
||||
<CreditCard
|
||||
color={selectedId == 'card' ? '#28A7E8' : '#000000'}
|
||||
width={28}
|
||||
height={28}
|
||||
/>
|
||||
<AppText
|
||||
style={[
|
||||
PaymentStyle.titleMethod,
|
||||
{ color: selectedId == 'card' ? '#28A7E8' : '#000' },
|
||||
]}
|
||||
>
|
||||
{t('Bank kartasi')}
|
||||
</AppText>
|
||||
</View>
|
||||
<View
|
||||
{getMe && !getMe.aviaCargoId.includes('CP') && (
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
PaymentStyle.select,
|
||||
styles.option,
|
||||
{
|
||||
backgroundColor:
|
||||
selectedId === 'card' ? '#28A7E8' : '#FFFFFF',
|
||||
borderColor: selectedId === 'card' ? '#28A7E8' : '#383838',
|
||||
selectedId === 'card' ? '#28A7E81A' : '#fff',
|
||||
},
|
||||
]}
|
||||
onPress={() => setSelectedId('card')}
|
||||
>
|
||||
{selectedId === 'card' && (
|
||||
<Check color="#fff" width={20} height={20} />
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<View style={PaymentStyle.paymentCard}>
|
||||
<CreditCard
|
||||
color={selectedId == 'card' ? '#28A7E8' : '#000000'}
|
||||
width={28}
|
||||
height={28}
|
||||
/>
|
||||
<AppText
|
||||
style={[
|
||||
PaymentStyle.titleMethod,
|
||||
{ color: selectedId == 'card' ? '#28A7E8' : '#000' },
|
||||
]}
|
||||
>
|
||||
{t('Bank kartasi')}
|
||||
</AppText>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
PaymentStyle.select,
|
||||
{
|
||||
backgroundColor:
|
||||
selectedId === 'card' ? '#28A7E8' : '#FFFFFF',
|
||||
borderColor:
|
||||
selectedId === 'card' ? '#28A7E8' : '#383838',
|
||||
},
|
||||
]}
|
||||
>
|
||||
{selectedId === 'card' && (
|
||||
<Check color="#fff" width={20} height={20} />
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
{paymentType !== 'CASH' && (
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
|
||||
Reference in New Issue
Block a user