added login modal

This commit is contained in:
Samandar Turgunboyev
2025-11-24 10:52:21 +05:00
parent 9aac17072f
commit 684d09e6b5
23 changed files with 1871 additions and 1351 deletions

View File

@@ -163,6 +163,7 @@ export default function App() {
const [seen, token, lang] = await Promise.all([ const [seen, token, lang] = await Promise.all([
AsyncStorage.getItem('hasSeenOnboarding'), AsyncStorage.getItem('hasSeenOnboarding'),
AsyncStorage.getItem('token'), AsyncStorage.getItem('token'),
AsyncStorage.getItem('language'), AsyncStorage.getItem('language'),
]); ]);

View File

@@ -85,8 +85,8 @@ android {
applicationId "uz.felix.cpost" applicationId "uz.felix.cpost"
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 5 versionCode 6
versionName "0.5" versionName "0.6"
multiDexEnabled true multiDexEnabled true
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true

File diff suppressed because one or more lines are too long

View 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;

View File

@@ -245,5 +245,8 @@
"Aloqa uchun": "Для связи", "Aloqa uchun": "Для связи",
"Email": "Электронная почта", "Email": "Электронная почта",
"Telegram": "Телеграм", "Telegram": "Телеграм",
"Roziman": "Согласен" "Roziman": "Согласен",
"To'lov holati": "Статус оплаты",
"Bekor qilingan": "Отменено",
"Ma'lumotlarni to'liq kiriting": "Введите полные данные"
} }

View File

@@ -246,5 +246,8 @@
"Aloqa uchun": "Aloqa uchun", "Aloqa uchun": "Aloqa uchun",
"Email": "Email", "Email": "Email",
"Telegram": "Telegram", "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"
} }

View File

@@ -5,6 +5,7 @@ import { useMutation } from '@tanstack/react-query';
import { authApi } from 'api/auth'; import { authApi } from 'api/auth';
import { otpPayload, resendPayload } from 'api/auth/type'; import { otpPayload, resendPayload } from 'api/auth/type';
import AppText from 'components/AppText'; import AppText from 'components/AppText';
import ErrorNotification from 'components/ErrorNotification';
import formatPhone from 'helpers/formatPhone'; import formatPhone from 'helpers/formatPhone';
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -47,6 +48,8 @@ const Confirm = () => {
const [canResend, setCanResend] = useState(false); const [canResend, setCanResend] = useState(false);
const inputRefs = useRef<Array<TextInput | null>>([]); const inputRefs = useRef<Array<TextInput | null>>([]);
const { phoneNumber } = useUserStore(state => state); const { phoneNumber } = useUserStore(state => state);
const [visible, setVisible] = useState(false);
const [error, setError] = useState<string>('Xatolik yuz berdi');
// const app = getApp(); // const app = getApp();
// const messaging = getMessaging(app); // const messaging = getMessaging(app);
@@ -76,11 +79,11 @@ const Confirm = () => {
onSuccess: async res => { onSuccess: async res => {
await AsyncStorage.setItem('token', res.data.accessToken); await AsyncStorage.setItem('token', res.data.accessToken);
navigation.navigate('Home'); navigation.navigate('Home');
setErrorConfirm(null); setVisible(false);
console.log(res);
}, },
onError: (err: any) => { 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); setCanResend(false);
setCode(new Array(OTP_LENGTH).fill('')); setCode(new Array(OTP_LENGTH).fill(''));
inputRefs.current[0]?.focus(); inputRefs.current[0]?.focus();
setErrorConfirm(null); setVisible(false);
}, },
onError: (err: any) => { 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> </TouchableOpacity>
<LanguageSelector /> <LanguageSelector />
</View> </View>
<ErrorNotification
setVisible={setVisible}
error={error}
visible={visible}
/>
<KeyboardAvoidingView <KeyboardAvoidingView
style={styles.container} style={styles.container}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'} behavior={Platform.OS === 'ios' ? 'padding' : 'height'}

View File

@@ -5,6 +5,7 @@ import { useMutation } from '@tanstack/react-query';
import { authApi } from 'api/auth'; import { authApi } from 'api/auth';
import { loginPayload } from 'api/auth/type'; import { loginPayload } from 'api/auth/type';
import AppText from 'components/AppText'; import AppText from 'components/AppText';
import ErrorNotification from 'components/ErrorNotification';
import formatPhone from 'helpers/formatPhone'; import formatPhone from 'helpers/formatPhone';
import React, { useCallback, useMemo, useRef, useState } from 'react'; import React, { useCallback, useMemo, useRef, useState } from 'react';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
@@ -38,11 +39,11 @@ const Filial = [
const Login = () => { const Login = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const passportNumberRef = useRef<TextInput>(null); const passportNumberRef = useRef<TextInput>(null);
const [filialDropdownVisible, setFilialDropdownVisible] = useState(false);
const navigation = useNavigation<NativeStackNavigationProp<any>>(); const navigation = useNavigation<NativeStackNavigationProp<any>>();
const { setUser, setExpireTime } = useUserStore(state => state); 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 [rawPhone, setRawPhone] = useState('+998');
const [visible, setVisible] = useState(false);
// const { data: branchList } = useQuery({ // const { data: branchList } = useQuery({
// queryKey: ['branchList'], // queryKey: ['branchList'],
// queryFn: branchApi.branchList, // queryFn: branchApi.branchList,
@@ -84,9 +85,14 @@ const Login = () => {
navigation.navigate('Login-Confirm'); navigation.navigate('Login-Confirm');
setExpireTime(res.data.expireTime); setExpireTime(res.data.expireTime);
}, },
onError: err => { onError: (err: any) => {
setError('Xatolik yuz berdi'); setVisible(true);
console.dir(err);
setError(
err?.response?.data?.message ||
err?.response?.message ||
'Xatolik yuz berdi',
);
}, },
}); });
@@ -105,6 +111,18 @@ const Login = () => {
}); });
const onSubmit = (data: LoginFormType) => { 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({ mutate({
phoneNumber: data.phone, phoneNumber: data.phone,
passportSerial: `${data.passportSeriya.toUpperCase()}${ passportSerial: `${data.passportSeriya.toUpperCase()}${
@@ -115,10 +133,18 @@ const Login = () => {
deviceType: '', deviceType: '',
deviceName: '', deviceName: '',
}); });
// navigation.navigate('Login-Confirm');
setUser({ setUser({ phoneNumber: data.phone });
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(() => { const handleBackNavigation = useCallback(() => {
@@ -156,6 +182,15 @@ const Login = () => {
</TouchableOpacity> </TouchableOpacity>
<LanguageSelector /> <LanguageSelector />
</View> </View>
{/* error modal start*/}
<ErrorNotification
setVisible={setVisible}
error={error}
visible={visible}
/>
{/* error modal end*/}
<KeyboardAvoidingView <KeyboardAvoidingView
style={Loginstyle.container} style={Loginstyle.container}
behavior={keyboardBehavior} behavior={keyboardBehavior}
@@ -185,11 +220,6 @@ const Login = () => {
placeholderTextColor="#D8DADC" placeholderTextColor="#D8DADC"
maxLength={19} // +998 90 123-45-67 bo'lishi uchun maxLength={19} // +998 90 123-45-67 bo'lishi uchun
/> />
{errors.phone && (
<AppText style={Loginstyle.errorText}>
{t(errors.phone.message || '')}
</AppText>
)}
</View> </View>
); );
}} }}
@@ -239,12 +269,6 @@ const Login = () => {
)} )}
/> />
</View> </View>
{(errors.passportSeriya || errors.passportNumber) && (
<AppText style={Loginstyle.errorText}>
{t(errors.passportSeriya?.message || '') ||
t(errors.passportNumber?.message || '')}
</AppText>
)}
</View> </View>
{/* <Controller {/* <Controller
@@ -313,7 +337,7 @@ const Login = () => {
/> */} /> */}
<TouchableOpacity <TouchableOpacity
onPress={handleSubmit(onSubmit)} onPress={handleSubmit(onSubmit, onErrorSubmit)}
style={Loginstyle.button} style={Loginstyle.button}
> >
{isPending ? ( {isPending ? (
@@ -324,6 +348,7 @@ const Login = () => {
</AppText> </AppText>
)} )}
</TouchableOpacity> </TouchableOpacity>
<View <View
style={{ style={{
display: 'flex', display: 'flex',

View File

@@ -10,6 +10,11 @@ export const Loginstyle = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
paddingHorizontal: 16, paddingHorizontal: 16,
}, },
errorModal: {
width: '80%',
backgroundColor: '#fff',
padding: 20,
},
background: { background: {
flex: 1, flex: 1,
justifyContent: 'center', justifyContent: 'center',

View File

@@ -11,10 +11,10 @@ export const FirstStepSchema = z.object({
}); });
export const SecondStepSchema = z.object({ export const SecondStepSchema = z.object({
passportSeriya: z.string().length(2, '2 ta harf kerak'), passportSeriya: z.string().length(2, { message: '2 ta harf kerak' }),
birthDate: z.string().min(8, 'Majburiy maydon'), birthDate: z.string().min(8, { message: 'Majburiy maydon' }),
passportNumber: z.string().length(7, '7 ta raqam kerak'), passportNumber: z.string().length(7, { message: '7 ta raqam kerak' }),
jshshir: z.string().length(14, '14 ta raqam kerak'), jshshir: z.string().min(14, { message: '14 ta raqam kerak' }),
}); });
export type FirstStepFormType = z.infer<typeof FirstStepSchema>; export type FirstStepFormType = z.infer<typeof FirstStepSchema>;

View File

@@ -7,8 +7,9 @@ import { useMutation } from '@tanstack/react-query';
import { authApi } from 'api/auth'; import { authApi } from 'api/auth';
import { otpPayload, resendPayload } from 'api/auth/type'; import { otpPayload, resendPayload } from 'api/auth/type';
import AppText from 'components/AppText'; import AppText from 'components/AppText';
import ErrorNotification from 'components/ErrorNotification';
import formatPhone from 'helpers/formatPhone'; import formatPhone from 'helpers/formatPhone';
import React, { useEffect, useRef, useState } from 'react'; import React, { useRef, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
ActivityIndicator, ActivityIndicator,
@@ -22,7 +23,6 @@ import {
} from 'react-native'; } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import Logo from 'screens/../../assets/bootsplash/logo_512.png'; 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 LanguageSelector from 'screens/auth/select-language/SelectLang';
import ArrowLeft from 'svg/ArrowLeft'; import ArrowLeft from 'svg/ArrowLeft';
import { RootStackParamList } from 'types/types'; import { RootStackParamList } from 'types/types';
@@ -49,6 +49,8 @@ const Confirm = ({
const [errorConfirm, setErrorConfirm] = useState<string | null>(null); const [errorConfirm, setErrorConfirm] = useState<string | null>(null);
const inputRefs = useRef<Array<TextInput | null>>([]); const inputRefs = useRef<Array<TextInput | null>>([]);
const { phoneNumber } = useUserStore(state => state); const { phoneNumber } = useUserStore(state => state);
const [visible, setVisible] = useState(false);
const [error, setError] = useState<string>('Xatolik yuz berdi');
// const [firebaseToken, setFirebseToken] = useState<{ // const [firebaseToken, setFirebseToken] = useState<{
// fcmToken: string; // fcmToken: string;
// deviceId: string; // deviceId: string;
@@ -86,8 +88,8 @@ const Confirm = ({
setErrorConfirm(null); setErrorConfirm(null);
}, },
onError: (err: any) => { onError: (err: any) => {
console.dir(err); setError(err?.response?.data?.message || 'Xatolik yuz berdi');
setErrorConfirm(err?.response.data.message); setVisible(true);
}, },
}); });
@@ -101,26 +103,11 @@ const Confirm = ({
setErrorConfirm(null); setErrorConfirm(null);
}, },
onError: (err: any) => { 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 handleCodeChange = (text: string, index: number) => {
const newCode = [...code]; const newCode = [...code];
newCode[index] = text; newCode[index] = text;
@@ -179,6 +166,11 @@ const Confirm = ({
</TouchableOpacity> </TouchableOpacity>
<LanguageSelector /> <LanguageSelector />
</View> </View>
<ErrorNotification
setVisible={setVisible}
error={error}
visible={visible}
/>
<KeyboardAvoidingView <KeyboardAvoidingView
style={styles.container} style={styles.container}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'} behavior={Platform.OS === 'ios' ? 'padding' : 'height'}

View File

@@ -12,6 +12,7 @@ import { authApi } from 'api/auth';
import { registerPayload } from 'api/auth/type'; import { registerPayload } from 'api/auth/type';
import { Branch, branchApi } from 'api/branch'; import { Branch, branchApi } from 'api/branch';
import AppText from 'components/AppText'; import AppText from 'components/AppText';
import ErrorNotification from 'components/ErrorNotification';
import formatPhone from 'helpers/formatPhone'; import formatPhone from 'helpers/formatPhone';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
@@ -57,7 +58,8 @@ const recommended = [
const FirstStep = ({ onNext }: { onNext: () => void }) => { const FirstStep = ({ onNext }: { onNext: () => void }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [filialDropdownVisible, setFilialDropdownVisible] = useState(false); 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 { setUser } = useUserStore(state => state);
const { data: branchList } = useQuery({ const { data: branchList } = useQuery({
queryKey: ['branchList'], queryKey: ['branchList'],
@@ -69,8 +71,14 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
onSuccess: res => { onSuccess: res => {
onNext(); onNext();
}, },
onError: err => { onError: (err: any) => {
setError('Xatolik yuz berdi'); 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 addressRef = useRef<TextInput>(null);
const onSubmit = (data: FirstStepFormType) => { 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({ setUser({
firstName: data.firstName, firstName: data.firstName,
lastName: data.lastName, 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(() => { useEffect(() => {
if (route.params?.termsAccepted) { if (route.params?.termsAccepted) {
setTermsAccepted(true); setTermsAccepted(true);
@@ -194,6 +237,11 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
{t("Ro'yxatdan o'tish")} {t("Ro'yxatdan o'tish")}
</AppText> </AppText>
<ErrorNotification
setVisible={setVisible}
error={error}
visible={visible}
/>
{/* Ism */} {/* Ism */}
<Controller <Controller
control={control} control={control}
@@ -211,11 +259,6 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
returnKeyType="next" returnKeyType="next"
onSubmitEditing={() => lastNameRef.current?.focus()} onSubmitEditing={() => lastNameRef.current?.focus()}
/> />
{errors.firstName && (
<AppText style={RegisterStyle.errorText}>
{t(errors.firstName.message || '')}
</AppText>
)}
</View> </View>
)} )}
/> />
@@ -239,11 +282,6 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
returnKeyType="next" returnKeyType="next"
onSubmitEditing={() => phoneRef.current?.focus()} onSubmitEditing={() => phoneRef.current?.focus()}
/> />
{errors.lastName && (
<AppText style={RegisterStyle.errorText}>
{t(errors.lastName.message || '')}
</AppText>
)}
</View> </View>
)} )}
/> />
@@ -280,11 +318,6 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
() => setFilialDropdownVisible(true) // ❗ Branch select ochiladi () => setFilialDropdownVisible(true) // ❗ Branch select ochiladi
} }
/> />
{errors.phoneNumber && (
<AppText style={RegisterStyle.errorText}>
{t(errors.phoneNumber.message || '')}
</AppText>
)}
</View> </View>
); );
}} }}
@@ -354,11 +387,6 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
</ScrollView> </ScrollView>
</View> </View>
)} )}
{errors.branchId && (
<AppText style={RegisterStyle.errorText}>
{t(errors.branchId.message || '')}
</AppText>
)}
</View> </View>
)} )}
/> />
@@ -386,11 +414,6 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
() => setRecommendedDropdownVisible(true) // ❗ recommend select ochiladi () => setRecommendedDropdownVisible(true) // ❗ recommend select ochiladi
} }
/> />
{errors.address && (
<AppText style={RegisterStyle.errorText}>
{t(errors.address.message || '')}
</AppText>
)}
</View> </View>
)} )}
/> />
@@ -452,16 +475,6 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
</ScrollView> </ScrollView>
</View> </View>
)} )}
{errors.recommend && (
<AppText style={RegisterStyle.errorText}>
{t(errors.recommend.message || '')}
</AppText>
)}
{error && (
<AppText style={[RegisterStyle.errorText]}>
{t(error)}
</AppText>
)}
</View> </View>
)} )}
/> />
@@ -513,7 +526,7 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<TouchableOpacity <TouchableOpacity
onPress={handleSubmit(onSubmit)} onPress={handleSubmit(onSubmit, onErrorSubmit)}
style={[ style={[
RegisterStyle.button, RegisterStyle.button,
(!termsAccepted || isPending) && (!termsAccepted || isPending) &&

View File

@@ -11,6 +11,7 @@ import { useMutation } from '@tanstack/react-query';
import passportApi, { sendPassportPayload } from 'api/passport'; import passportApi, { sendPassportPayload } from 'api/passport';
import AppText from 'components/AppText'; import AppText from 'components/AppText';
import DatePickerInput from 'components/DatePicker'; import DatePickerInput from 'components/DatePicker';
import ErrorNotification from 'components/ErrorNotification';
import SingleFileDrop from 'components/FileDrop'; import SingleFileDrop from 'components/FileDrop';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
@@ -55,7 +56,6 @@ const SecondStep = () => {
const passportNumberRef = useRef<TextInput>(null); const passportNumberRef = useRef<TextInput>(null);
const [checkboxAnimation] = useState(new Animated.Value(1)); const [checkboxAnimation] = useState(new Animated.Value(1));
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState('');
const [error, setError] = useState<string>('');
const { firstName, lastName } = useUserStore(state => state); const { firstName, lastName } = useUserStore(state => state);
const passportSeriyaRef = useRef<TextInput>(null); const passportSeriyaRef = useRef<TextInput>(null);
const jshshirRef = useRef<TextInput>(null); const jshshirRef = useRef<TextInput>(null);
@@ -65,6 +65,8 @@ const SecondStep = () => {
const route = useRoute<RouteProp<RootStackParamList, 'Register'>>(); const route = useRoute<RouteProp<RootStackParamList, 'Register'>>();
const [selectedDate, setSelectedDate] = useState<Date | null>(null); const [selectedDate, setSelectedDate] = useState<Date | null>(null);
const [isDatePickerVisible, setDatePickerVisibility] = useState(false); const [isDatePickerVisible, setDatePickerVisibility] = useState(false);
const [visible, setVisible] = useState(false);
const [error, setError] = useState<string>('Xatolik yuz berdi');
const formatDate = (date: Date) => { const formatDate = (date: Date) => {
const day = date.getDate().toString().padStart(2, '0'); const day = date.getDate().toString().padStart(2, '0');
@@ -80,7 +82,10 @@ const SecondStep = () => {
navigation.navigate('Home'); navigation.navigate('Home');
}, },
onError: (err: any) => { 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) => { 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 [d, m, y] = data.birthDate.split('/');
const isoBirthDate = `${y}-${m.padStart(2, '0')}-${d.padStart(2, '0')}`; 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 ( return (
<ImageBackground <ImageBackground
source={Logo} source={Logo}
@@ -147,6 +168,12 @@ const SecondStep = () => {
<LanguageSelector /> <LanguageSelector />
</View> </View>
<ErrorNotification
setVisible={setVisible}
error={error}
visible={visible}
/>
<KeyboardAvoidingView <KeyboardAvoidingView
style={{ flex: 1 }} style={{ flex: 1 }}
behavior="padding" behavior="padding"
@@ -224,12 +251,6 @@ const SecondStep = () => {
)} )}
/> />
</View> </View>
{(errors.passportSeriya || errors.passportNumber) && (
<AppText style={RegisterStyle.errorText}>
{t(errors.passportSeriya?.message || '') ||
t(errors.passportNumber?.message || '')}
</AppText>
)}
</View> </View>
{/* JSHSHIR */} {/* JSHSHIR */}
<Controller <Controller
@@ -254,11 +275,6 @@ const SecondStep = () => {
} }
onSubmitEditing={() => birthDateRef.current?.focus()} onSubmitEditing={() => birthDateRef.current?.focus()}
/> />
{errors.jshshir && (
<AppText style={RegisterStyle.errorText}>
{t(errors.jshshir.message || '')}
</AppText>
)}
</View> </View>
)} )}
/> />
@@ -354,12 +370,6 @@ const SecondStep = () => {
<Calendar color="#D8DADC" width={24} height={24} /> <Calendar color="#D8DADC" width={24} height={24} />
</TouchableOpacity> </TouchableOpacity>
</View> </View>
{errors.birthDate && (
<AppText style={RegisterStyle.errorText}>
{t(errors.birthDate?.message || '')}
</AppText>
)}
</View> </View>
)} )}
/> />
@@ -418,14 +428,9 @@ const SecondStep = () => {
/> />
</View> </View>
</View> </View>
{error && (
<AppText style={[RegisterStyle.errorText, { fontSize: 14 }]}>
{error}
</AppText>
)}
{/* BUTTON */} {/* BUTTON */}
<TouchableOpacity <TouchableOpacity
onPress={handleSubmit(onSubmit)} onPress={handleSubmit(onSubmit, onErrorSubmit)}
style={[ style={[
RegisterStyle.button, RegisterStyle.button,
!termsAccepted && RegisterStyle.buttonDisabled, !termsAccepted && RegisterStyle.buttonDisabled,

View File

@@ -1,16 +1,19 @@
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import AppText from 'components/AppText'; import AppText from 'components/AppText';
import { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
Image, Image,
Linking,
Modal,
ScrollView, ScrollView,
StyleSheet, StyleSheet,
TouchableOpacity, TouchableOpacity,
View, View,
useWindowDimensions,
} from 'react-native'; } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; 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 Logo from 'screens/../../assets/bootsplash/logo_512.png';
import InfoIcon from 'svg/Info'; import InfoIcon from 'svg/Info';
import { RootStackParamList } from 'types/types'; import { RootStackParamList } from 'types/types';
@@ -23,7 +26,18 @@ type LoginScreenNavigationProp = NativeStackNavigationProp<
const SelectAuth = () => { const SelectAuth = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const navigation = useNavigation<LoginScreenNavigationProp>(); 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 ( return (
<SafeAreaView style={{ flex: 1 }}> <SafeAreaView style={{ flex: 1 }}>
@@ -60,6 +74,78 @@ const SelectAuth = () => {
<View style={styles.btnContainer}> <View style={styles.btnContainer}>
<View style={{ gap: 6 }}> <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 otganmisiz")}?
</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 royxatdan otgan bolsangiz, 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 malumotlarni kiritganingizdan song 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 <TouchableOpacity
onPress={() => navigation.navigate('Login')} onPress={() => navigation.navigate('Login')}
style={[ style={[
@@ -75,30 +161,10 @@ const SelectAuth = () => {
{t('Tizimga kirish')} {t('Tizimga kirish')}
</AppText> </AppText>
</TouchableOpacity> </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 otganmisiz")}?
</AppText>
</View>
</View> </View>
<View style={{ gap: 6 }}> <View style={{ gap: 6 }}>
<TouchableOpacity <TouchableOpacity
onPress={() => navigation.navigate('Register')} onPress={() => setNewRegisterModalVisible(true)}
style={styles.button}
>
<AppText style={styles.btnText}>
{t('Royxatdan otish')}
</AppText>
</TouchableOpacity>
<View
style={{ style={{
gap: 4, gap: 4,
flexDirection: 'row', flexDirection: 'row',
@@ -110,8 +176,70 @@ const SelectAuth = () => {
<AppText style={styles.helperText}> <AppText style={styles.helperText}>
{t("Yangi royxatdan o'tmoqchimisiz")}? {t("Yangi royxatdan o'tmoqchimisiz")}?
</AppText> </AppText>
</View> </TouchableOpacity>
<TouchableOpacity
onPress={() => navigation.navigate('Register')}
style={styles.button}
>
<AppText style={styles.btnText}>
{t('Royxatdan otish')}
</AppText>
</TouchableOpacity>
</View> </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 bolsangiz, tizimga kirishdan oldin royxatdan otishingiz kerak. Royxatdan otish jarayonida sizdan quyidagi malumotlar soraladi:',
)}
</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 malumotlarni kiritganingizdan song 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>
</View> </View>
</ScrollView> </ScrollView>
@@ -173,4 +301,34 @@ const styles = StyleSheet.create({
btnContainer: { btnContainer: {
gap: 16, 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,
},
}); });

View File

@@ -26,9 +26,15 @@ interface ModalSuccessViewProps {
visible: boolean; visible: boolean;
error: boolean; error: boolean;
setVisible: React.Dispatch<React.SetStateAction<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 navigation = useNavigation<NavigationProp>();
const { t } = useTranslation(); const { t } = useTranslation();
const [successMet, setSuccessMet] = useState(false); const [successMet, setSuccessMet] = useState(false);
@@ -103,13 +109,18 @@ const CreateModal = ({ visible, setVisible, error }: ModalSuccessViewProps) => {
{successMet ? ( {successMet ? (
<View style={styles.content}> <View style={styles.content}>
{error ? ( {error ? (
<LottieView <>
source={Warning} <LottieView
loop source={Warning}
autoPlay={true} loop
resizeMode="cover" autoPlay={true}
style={{ width: 100 * scale, height: 100 * scale }} resizeMode="cover"
/> style={{ width: 100 * scale, height: 100 * scale }}
/>
<AppText style={styles.status}>
{t(errorText!) || t('Xatolik yuz berdi')}
</AppText>
</>
) : ( ) : (
<LottieView <LottieView
source={ProgressBar} source={ProgressBar}

View File

@@ -3,6 +3,7 @@ import { useMutation } from '@tanstack/react-query';
import passportApi, { AddPassportPayload } from 'api/passport'; import passportApi, { AddPassportPayload } from 'api/passport';
import AppText from 'components/AppText'; import AppText from 'components/AppText';
import DatePickerInput from 'components/DatePicker'; import DatePickerInput from 'components/DatePicker';
import ErrorNotification from 'components/ErrorNotification';
import SingleFileDrop from 'components/FileDrop'; import SingleFileDrop from 'components/FileDrop';
import LayoutTwo from 'components/LayoutTwo'; import LayoutTwo from 'components/LayoutTwo';
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
@@ -38,9 +39,12 @@ const CreatePassword = () => {
const [isDatePickerVisible, setDatePickerVisibility] = useState(false); const [isDatePickerVisible, setDatePickerVisibility] = useState(false);
const [selectedDate, setSelectedDate] = useState<Date | null>(null); const [selectedDate, setSelectedDate] = useState<Date | null>(null);
const [success, setSuccess] = React.useState(false); 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 [frontImage, setFrontImage] = useState<FileData | null>(null);
const [backImage, setBackImage] = 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: '', // firstName: '',
// lastName: '', // lastName: '',
// birthDate: '', // birthDate: '',
@@ -58,13 +62,15 @@ const CreatePassword = () => {
const res = passportApi.addPassport(payload); const res = passportApi.addPassport(payload);
return res; return res;
}, },
onSuccess: res => { onSuccess: () => {
setSuccess(true); setSuccess(true);
}, },
onError: err => { onError: (err: any) => {
console.dir(err); console.dir(err);
setErrorVisible(true);
setError(true); setErrorText(
err?.response?.data || err?.response?.message || 'Xatolik yuz berdi',
);
}, },
}); });
@@ -100,6 +106,28 @@ const CreatePassword = () => {
}; };
const onSubmit = (data: CreatePassSchemaType) => { 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 [d, m, y] = data.birthDate.split('/');
const isoBirthDate = `${y}-${m.padStart(2, '0')}-${d.padStart(2, '0')}`; const isoBirthDate = `${y}-${m.padStart(2, '0')}-${d.padStart(2, '0')}`;
@@ -114,6 +142,22 @@ const CreatePassword = () => {
passportBackImage: backImage ? `${backImage.base64}` : '', 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 ( return (
<LayoutTwo title={t("Yangi pasport qo'shish")}> <LayoutTwo title={t("Yangi pasport qo'shish")}>
<KeyboardAvoidingView <KeyboardAvoidingView
@@ -127,6 +171,11 @@ const CreatePassword = () => {
style={PassportStyle.content} style={PassportStyle.content}
> >
<View style={PassportStyle.scrollContainer}> <View style={PassportStyle.scrollContainer}>
<ErrorNotification
setVisible={setVisible}
error={error}
visible={visible}
/>
<View style={PassportStyle.loginContainer}> <View style={PassportStyle.loginContainer}>
<Controller <Controller
control={control} control={control}
@@ -144,11 +193,6 @@ const CreatePassword = () => {
value={value} value={value}
placeholderTextColor={'#D8DADC'} placeholderTextColor={'#D8DADC'}
/> />
{errors.firstName && (
<AppText style={PassportStyle.errorText}>
{t(errors.firstName.message || '')}
</AppText>
)}
</View> </View>
)} )}
/> />
@@ -170,11 +214,6 @@ const CreatePassword = () => {
onChangeText={onChange} onChangeText={onChange}
value={value} value={value}
/> />
{errors.lastName && (
<AppText style={PassportStyle.errorText}>
{t(errors.lastName.message || '')}
</AppText>
)}
</View> </View>
)} )}
/> />
@@ -230,12 +269,6 @@ const CreatePassword = () => {
)} )}
/> />
</View> </View>
{(errors.passportSeriya || errors.passportNumber) && (
<AppText style={PassportStyle.errorText}>
{t(errors.passportSeriya?.message || '') ||
t(errors.passportNumber?.message || '')}
</AppText>
)}
</View> </View>
<View> <View>
<AppText style={PassportStyle.label}>{t('JSHSHIR')}</AppText> <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>
<View> <View>
<Controller <Controller
@@ -345,12 +373,6 @@ const CreatePassword = () => {
<Calendar color="#D8DADC" width={25} height={25} /> <Calendar color="#D8DADC" width={25} height={25} />
</TouchableOpacity> </TouchableOpacity>
</View> </View>
{errors.birthDate && (
<AppText style={PassportStyle.errorText}>
{t(errors.birthDate?.message || '')}
</AppText>
)}
</View> </View>
)} )}
/> />
@@ -405,7 +427,7 @@ const CreatePassword = () => {
</View> </View>
</View> </View>
<TouchableOpacity <TouchableOpacity
onPress={handleSubmit(onSubmit)} onPress={handleSubmit(onSubmit, onErrorSubmit)}
style={PassportStyle.button} style={PassportStyle.button}
> >
{isPending ? ( {isPending ? (
@@ -421,7 +443,12 @@ const CreatePassword = () => {
</ScrollView> </ScrollView>
</KeyboardAvoidingView> </KeyboardAvoidingView>
{success && ( {success && (
<CreateModal visible={success} setVisible={setSuccess} error={error} /> <CreateModal
visible={success}
setVisible={setSuccess}
error={errorVisible}
errorText={errorText}
/>
)} )}
</LayoutTwo> </LayoutTwo>
); );

View File

@@ -54,9 +54,9 @@ const MyPassport = ({ getMe, myPassport }: Props) => {
<View <View
style={{ style={{
display: 'flex', display: 'flex',
flexDirection: 'row', flexDirection: 'column',
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center', alignItems: 'flex-start',
}} }}
> >
<AppText style={styles.title}> <AppText style={styles.title}>
@@ -227,7 +227,7 @@ const styles = StyleSheet.create({
gap: 5, gap: 5,
}, },
statusBadge: { statusBadge: {
alignSelf: 'center', alignSelf: 'flex-end',
marginTop: 5, marginTop: 5,
marginBottom: 10, marginBottom: 10,
paddingHorizontal: 14, paddingHorizontal: 14,

View File

@@ -1,7 +1,7 @@
import Layout from 'components/Layout'; import Layout from 'components/Layout';
import LoadingScreen from 'components/LoadingScreen'; import LoadingScreen from 'components/LoadingScreen';
import React, { useCallback, useEffect, useMemo, useState } from 'react'; 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 ProfileHeader from './ProfileHeader';
import ProfilePages from './ProfilePages'; 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; export default Profile;

View File

@@ -4,7 +4,6 @@ import AppText from 'components/AppText';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
Dimensions,
Image, Image,
Linking, Linking,
StyleSheet, StyleSheet,
@@ -20,9 +19,6 @@ import Plus from 'svg/Plus';
import Telegram from 'svg/Telegram'; import Telegram from 'svg/Telegram';
import Trash from 'svg/Trash'; import Trash from 'svg/Trash';
const { width } = Dimensions.get('window');
const isSmallScreen = width < 360;
const ProfileHeader = ({ userName = 'Samandar' }: { userName?: string }) => { const ProfileHeader = ({ userName = 'Samandar' }: { userName?: string }) => {
const [imageError, setImageError] = useState(true); const [imageError, setImageError] = useState(true);
const { t } = useTranslation(); const { t } = useTranslation();
@@ -48,7 +44,6 @@ const ProfileHeader = ({ userName = 'Samandar' }: { userName?: string }) => {
queryKey: ['getMe'], queryKey: ['getMe'],
queryFn: authApi.getMe, queryFn: authApi.getMe,
}); });
const [isModalVisible, setModalVisible] = useState(false); const [isModalVisible, setModalVisible] = useState(false);
const openGallery = async () => { const openGallery = async () => {

View File

@@ -12,14 +12,14 @@ import {
import CloseIcon from 'svg/Close'; import CloseIcon from 'svg/Close';
import FilterIcon from 'svg/Filter'; import FilterIcon from 'svg/Filter';
const transportTypes = ['AUTO', 'AVIA'] as const; const transportTypes = ['AVIA', 'AUTO'] as const;
type TransportType = (typeof transportTypes)[number]; type TransportType = (typeof transportTypes)[number];
interface Props { interface Props {
transportTypes: TransportType; transportTypes: TransportType;
setTransportTypes: (val: TransportType) => void; setTransportTypes: (val: TransportType) => void;
reys: string; reys: string;
data: PacketsData; data: PacketsData | undefined;
setReys: (val: string) => void; setReys: (val: string) => void;
setSelectedData: (val: any) => void; setSelectedData: (val: any) => void;
} }
@@ -38,7 +38,7 @@ const Filter = ({
const styles = makeStyles(); const styles = makeStyles();
const newOrders = React.useMemo( const newOrders = React.useMemo(
() => data.data.filter(item => item.paymentStatus === 'NEW'), () => data?.data?.filter(item => item.paymentStatus === 'NEW') || [],
[data], [data],
); );
@@ -88,7 +88,7 @@ const Filter = ({
selectedType === type && styles.activeTypeText, selectedType === type && styles.activeTypeText,
]} ]}
> >
{type === 'AUTO' ? t('Avto') : t('Avia')} {type === 'AVIA' ? t('Avia') : t('Auto')}
</AppText> </AppText>
</TouchableOpacity> </TouchableOpacity>
)} )}

View 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;

View File

@@ -1,3 +1,4 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import packetsApi from 'api/packets'; import packetsApi from 'api/packets';
import Layout from 'components/Layout'; import Layout from 'components/Layout';
@@ -19,11 +20,13 @@ import { DataInfo } from '../lib/data';
import Filter from './Filter'; import Filter from './Filter';
import Order from './Order'; import Order from './Order';
import OrderDetailModal from './OrderDetailModal'; import OrderDetailModal from './OrderDetailModal';
import PaymentFilter from './PaymentFilter';
import Tabs from './Tabs'; import Tabs from './Tabs';
const Status = () => { const Status = () => {
const { width: screenWidth } = useWindowDimensions(); const { width: screenWidth } = useWindowDimensions();
const scale = screenWidth < 360 ? 0.85 : 1; const scale = screenWidth < 360 ? 0.85 : 1;
const [paymentStatus, setPaymentStatus] = useState('NEW');
const [filter, setFilter] = useState< const [filter, setFilter] = useState<
| 'COLLECTING' | 'COLLECTING'
@@ -38,7 +41,7 @@ const Status = () => {
const [transportTypes, setTransportTypes] = useState< const [transportTypes, setTransportTypes] = useState<
// 'all'| // 'all'|
'AUTO' | 'AVIA' 'AUTO' | 'AVIA'
>('AUTO'); >('AVIA');
const [page, setPage] = useState(0); const [page, setPage] = useState(0);
const { const {
data: statusData, data: statusData,
@@ -46,15 +49,21 @@ const Status = () => {
isLoading, isLoading,
isFetching, isFetching,
} = useQuery({ } = useQuery({
queryKey: ['status', filter, transportTypes, page], queryKey: ['status', filter, transportTypes, page, paymentStatus],
queryFn: () => queryFn: () =>
packetsApi.getPacketsStatus(filter, { packetsApi.getPacketsStatus(filter, {
page, page,
size: 10, size: 10,
cargoType: transportTypes, cargoType: transportTypes,
sort: 'id',
paymentStatus: paymentStatus,
direction: 'DESC',
}), }),
}); });
console.log(AsyncStorage.getItem('token'));
console.log('statusData', statusData);
const [modalVisible, setModalVisible] = useState(false); const [modalVisible, setModalVisible] = useState(false);
const scaleAnim = React.useRef(new Animated.Value(0.8)).current; const scaleAnim = React.useRef(new Animated.Value(0.8)).current;
const opacityAnim = React.useRef(new Animated.Value(0)).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 ( return (
<Layout> <Layout>
<Tabs filter={filter} setFilter={setFilter} /> <Tabs filter={filter} setFilter={setFilter} />
<View style={styles.controls}> <View style={styles.controls}>
<View style={{ position: 'relative' }}> <View
style={{
position: 'relative',
flexDirection: 'row',
gap: 8,
}}
>
<Filter <Filter
transportTypes={transportTypes} transportTypes={transportTypes}
setTransportTypes={setTransportTypes} setTransportTypes={setTransportTypes}
reys={reys} reys={reys}
setReys={setReys} setReys={setReys}
data={statusData!} data={statusData}
setSelectedData={setSelectedData} setSelectedData={setSelectedData}
/> />
<PaymentFilter
paymentStatus={paymentStatus}
setPaymentStatus={setPaymentStatus}
/>
</View> </View>
</View> </View>
<NoResult message={t("Hech qanday ma'lumot topilmadi")} /> <NoResult message={t("Hech qanday ma'lumot topilmadi")} />
@@ -182,7 +201,13 @@ const Status = () => {
/> />
<View style={styles.searchIcon}>{searchIcon}</View> <View style={styles.searchIcon}>{searchIcon}</View>
</View> */} </View> */}
<View style={{ position: 'relative' }}> <View
style={{
position: 'relative',
flexDirection: 'row',
gap: 8,
}}
>
<Filter <Filter
transportTypes={transportTypes} transportTypes={transportTypes}
setTransportTypes={setTransportTypes} setTransportTypes={setTransportTypes}
@@ -191,6 +216,10 @@ const Status = () => {
data={statusData!} data={statusData!}
setSelectedData={setSelectedData} setSelectedData={setSelectedData}
/> />
<PaymentFilter
paymentStatus={paymentStatus}
setPaymentStatus={setPaymentStatus}
/>
</View> </View>
</View> </View>
<Order <Order

View File

@@ -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 packetsApi from 'api/packets';
import AppText from 'components/AppText'; import AppText from 'components/AppText';
import React, { useEffect, useRef } from 'react'; import React, { useEffect, useRef } from 'react';
@@ -52,6 +53,12 @@ const ModalPay = ({
const { bottom } = useSafeAreaInsets(); const { bottom } = useSafeAreaInsets();
const [load, setLoad] = React.useState(false); const [load, setLoad] = React.useState(false);
const { t } = useTranslation(); const { t } = useTranslation();
const { data: getMe } = useQuery({
queryKey: ['getMe'],
queryFn: authApi.getMe,
});
console.log(getMe, 'getMe');
const { mutate, isPending } = useMutation({ const { mutate, isPending } = useMutation({
mutationFn: ({ id, payType }: { id: number; payType: string }) => mutationFn: ({ id, payType }: { id: number; payType: string }) =>
@@ -137,45 +144,49 @@ const ModalPay = ({
}, },
]} ]}
> >
<TouchableOpacity {getMe && !getMe.aviaCargoId.includes('CP') && (
style={[ <TouchableOpacity
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
style={[ style={[
PaymentStyle.select, styles.option,
{ {
backgroundColor: backgroundColor:
selectedId === 'card' ? '#28A7E8' : '#FFFFFF', selectedId === 'card' ? '#28A7E81A' : '#fff',
borderColor: selectedId === 'card' ? '#28A7E8' : '#383838',
}, },
]} ]}
onPress={() => setSelectedId('card')}
> >
{selectedId === 'card' && ( <View style={PaymentStyle.paymentCard}>
<Check color="#fff" width={20} height={20} /> <CreditCard
)} color={selectedId == 'card' ? '#28A7E8' : '#000000'}
</View> width={28}
</TouchableOpacity> 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' && ( {paymentType !== 'CASH' && (
<TouchableOpacity <TouchableOpacity
style={[ style={[