Compare commits

...

10 Commits

Author SHA1 Message Date
Samandar Turgunboyev
22dee44306 merge gitea 2025-12-19 11:36:26 +05:00
Samandar Turgunboyev
51e2d778be navihate 2025-12-11 16:10:33 +05:00
Samandar Turgunboyev
fcd704b258 mmkv bug fix 2025-12-11 15:49:53 +05:00
Samandar Turgunboyev
c426b729b9 screen bug fix 2025-12-10 11:02:58 +05:00
Samandar Turgunboyev
87bd8cdea6 mmkv added 2025-12-04 15:39:48 +05:00
Samandar Turgunboyev
684d09e6b5 added login modal 2025-11-24 10:52:21 +05:00
Samandar Turgunboyev
9aac17072f bug fix 2025-10-02 17:05:40 +05:00
Samandar Turgunboyev
38badbe3dd info plist update 2025-09-09 17:53:07 +05:00
Samandar Turgunboyev
b5f6006ae0 peemisssion language update 2025-09-09 17:27:44 +05:00
Samandar Turgunboyev
34fb3e357a delete firebase 2025-09-04 19:13:14 +05:00
62 changed files with 3365 additions and 3429 deletions

242
App.tsx
View File

@@ -5,7 +5,6 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { toastConfig } from 'components/CustomAlertModal';
import { navigationRef } from 'components/NavigationRef';
import SplashScreen from 'components/SplashScreen';
import i18n from 'i18n/i18n';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { I18nextProvider } from 'react-i18next';
@@ -13,25 +12,14 @@ import {
Animated,
Dimensions,
LogBox,
PermissionsAndroid,
Platform,
StatusBar,
StyleSheet,
View,
} from 'react-native';
import Toast from 'react-native-toast-message';
import SplashScreen from './src/components/SplashScreen';
// Screens
import notifee, { AndroidImportance } from '@notifee/react-native';
import { getApp } from '@react-native-firebase/app';
import {
getInitialNotification,
getMessaging,
getToken,
onMessage,
onNotificationOpenedApp,
} from '@react-native-firebase/messaging';
import DeviceInfo from 'react-native-device-info';
import Login from 'screens/auth/login/ui';
import Confirm from 'screens/auth/login/ui/Confirm';
import Register from 'screens/auth/registeration/ui';
@@ -46,7 +34,6 @@ import RestrictedProduct from 'screens/home/restrictedProduct/ui/RestrictedProdu
import CreatePassword from 'screens/passport/createPassport/ui/CreatePassword';
import Passport from 'screens/passport/myPassport/ui/Passport';
import Profile from 'screens/profile/myProfile/ui/Profile';
import Notifications from 'screens/profile/notifications/ui/Notifications';
import AddedLock from 'screens/profile/settings/ui/AddedLock';
import Settings from 'screens/profile/settings/ui/Settings';
import SettingsLock from 'screens/profile/settings/ui/SettingsLock';
@@ -68,107 +55,107 @@ const Stack = createNativeStackNavigator();
const screenWidth = Dimensions.get('window').width;
const queryClient = new QueryClient();
const saveNotification = async (remoteMessage: any) => {
try {
const stored = await AsyncStorage.getItem('notifications');
const notifications = stored ? JSON.parse(stored) : [];
// const saveNotification = async (remoteMessage: any) => {
// try {
// const stored = await AsyncStorage.getItem('notifications');
// const notifications = stored ? JSON.parse(stored) : [];
const newNotification = {
id: Date.now(),
title:
remoteMessage.notification?.title ||
remoteMessage.data?.title ||
'Yangi bildirishnoma',
message:
remoteMessage.notification?.body ||
remoteMessage.data?.body ||
'Matn yoq',
sentTime: remoteMessage.sentTime || Date.now(),
};
// const newNotification = {
// id: Date.now(),
// title:
// remoteMessage.notification?.title ||
// remoteMessage.data?.title ||
// 'Yangi bildirishnoma',
// message:
// remoteMessage.notification?.body ||
// remoteMessage.data?.body ||
// 'Matn yoq',
// sentTime: remoteMessage.sentTime || Date.now(),
// };
await AsyncStorage.setItem(
'notifications',
JSON.stringify([newNotification, ...notifications]),
);
} catch (e) {
console.error('Notification saqlashda xato:', e);
}
};
// await AsyncStorage.setItem(
// 'notifications',
// JSON.stringify([newNotification, ...notifications]),
// );
// } catch (e) {
// console.error('Notification saqlashda xato:', e);
// }
// };
async function onDisplayNotification(remoteMessage: any) {
const channelId = await notifee.createChannel({
id: 'default',
name: 'Umumiy bildirishnomalar',
sound: 'default',
importance: AndroidImportance.HIGH,
});
// async function onDisplayNotification(remoteMessage: any) {
// const channelId = await notifee.createChannel({
// id: 'default',
// name: 'Umumiy bildirishnomalar',
// sound: 'default',
// importance: AndroidImportance.HIGH,
// });
await notifee.displayNotification({
title:
remoteMessage.notification?.title ||
remoteMessage.data?.title ||
'Yangi xabar',
body:
remoteMessage.notification?.body ||
remoteMessage.data?.body ||
'Matn yoq',
android: {
channelId,
largeIcon: 'ic_launcher_foreground',
sound: 'default',
pressAction: {
id: 'default',
},
},
});
}
// await notifee.displayNotification({
// title:
// remoteMessage.notification?.title ||
// remoteMessage.data?.title ||
// 'Yangi xabar',
// body:
// remoteMessage.notification?.body ||
// remoteMessage.data?.body ||
// 'Matn yoq',
// android: {
// channelId,
// largeIcon: 'ic_launcher_foreground',
// sound: 'default',
// pressAction: {
// id: 'default',
// },
// },
// });
// }
async function requestNotificationPermission() {
if (Platform.OS === 'android' && Platform.Version >= 33) {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS,
);
console.log('POST_NOTIFICATIONS permission:', granted);
}
}
// async function requestNotificationPermission() {
// if (Platform.OS === 'android' && Platform.Version >= 33) {
// const granted = await PermissionsAndroid.request(
// PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS,
// );
// console.log('POST_NOTIFICATIONS permission:', granted);
// }
// }
export default function App() {
const [initialRoute, setInitialRoute] = useState<string | null>(null);
const slideAnim = useRef(new Animated.Value(0)).current;
const [isSplashVisible, setIsSplashVisible] = useState(true);
useEffect(() => {
requestNotificationPermission();
// useEffect(() => {
// requestNotificationPermission();
const messagingInstance = getMessaging();
// const messagingInstance = getMessaging();
const unsubscribe = onMessage(messagingInstance, async remoteMessage => {
console.log('Foreground message:', remoteMessage);
await saveNotification(remoteMessage);
await onDisplayNotification(remoteMessage);
});
// const unsubscribe = onMessage(messagingInstance, async remoteMessage => {
// console.log('Foreground message:', remoteMessage);
// await saveNotification(remoteMessage);
// await onDisplayNotification(remoteMessage);
// });
const unsubscribeOpened = onNotificationOpenedApp(
messagingInstance,
remoteMessage => {
console.log('Backgrounddan ochildi:', remoteMessage);
saveNotification(remoteMessage);
},
);
// const unsubscribeOpened = onNotificationOpenedApp(
// messagingInstance,
// remoteMessage => {
// console.log('Backgrounddan ochildi:', remoteMessage);
// saveNotification(remoteMessage);
// },
// );
(async () => {
const remoteMessage = await getInitialNotification(messagingInstance);
if (remoteMessage) {
console.log('Killeddan ochildi:', remoteMessage);
saveNotification(remoteMessage);
}
})();
// (async () => {
// const remoteMessage = await getInitialNotification(messagingInstance);
// if (remoteMessage) {
// console.log('Killeddan ochildi:', remoteMessage);
// saveNotification(remoteMessage);
// }
// })();
return () => {
unsubscribe();
unsubscribeOpened();
};
}, []);
// return () => {
// unsubscribe();
// unsubscribeOpened();
// };
// }, []);
useEffect(() => {
const initializeApp = async () => {
@@ -176,6 +163,7 @@ export default function App() {
const [seen, token, lang] = await Promise.all([
AsyncStorage.getItem('hasSeenOnboarding'),
AsyncStorage.getItem('token'),
AsyncStorage.getItem('language'),
]);
@@ -214,34 +202,34 @@ export default function App() {
},
[],
);
const [firebaseToken, setFirebseToken] = useState<{
fcmToken: string;
deviceId: string;
deviceName: string;
} | null>();
const app = getApp();
const messaging = getMessaging(app);
// const [firebaseToken, setFirebseToken] = useState<{
// fcmToken: string;
// deviceId: string;
// deviceName: string;
// } | null>();
// const app = getApp();
// const messaging = getMessaging(app);
const getDeviceData = async () => {
try {
const fcmToken = await getToken(messaging);
return {
fcmToken,
deviceId: await DeviceInfo.getUniqueId(),
deviceName: await DeviceInfo.getDeviceName(),
};
} catch (e) {
console.log('Xato:', e);
return null;
}
};
console.log(firebaseToken);
// const getDeviceData = async () => {
// try {
// const fcmToken = await getToken(messaging);
// return {
// fcmToken,
// deviceId: await DeviceInfo.getUniqueId(),
// deviceName: await DeviceInfo.getDeviceName(),
// };
// } catch (e) {
// console.log('Xato:', e);
// return null;
// }
// };
// console.log(firebaseToken);
useEffect(() => {
getDeviceData().then(data => {
setFirebseToken(data);
});
}, []);
// useEffect(() => {
// getDeviceData().then(data => {
// setFirebseToken(data);
// });
// }, []);
if (!initialRoute) return null;
@@ -285,9 +273,9 @@ export default function App() {
<Stack.Screen name="PaymentQrCode" component={PaymentQrCode} />
<Stack.Screen name="Profile" component={Profile} />
<Stack.Screen name="Settings" component={Settings} />
{Platform.OS === 'android' && (
{/* {Platform.OS === 'android' && (
<Stack.Screen name="Notifications" component={Notifications} />
)}
)} */}
<Stack.Screen name="Warehouses" component={Warehouses} />
<Stack.Screen name="Support" component={Support} />
<Stack.Screen name="ListBranches" component={ListBranches} />

View File

@@ -85,8 +85,8 @@ android {
applicationId "uz.felix.cpost"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 4
versionName "0.4"
versionCode 8
versionName "0.8"
multiDexEnabled true
vectorDrawables.useSupportLibrary = true

File diff suppressed because one or more lines are too long

View File

@@ -15,7 +15,7 @@ class MainActivity : ReactActivity() {
override fun getMainComponentName(): String = "first"
override fun onCreate(savedInstanceState: android.os.Bundle?) {
super.onCreate(savedInstanceState)
super.onCreate(null)
}
/**

View File

@@ -9,6 +9,7 @@ import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.facebook.react.defaults.DefaultReactNativeHost
import com.swmansion.rnscreens.RNScreensPackage
class MainApplication : Application(), ReactApplication {

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -1 +1 @@
<resources xmlns:tools="http://schemas.android.com/tools" tools:keep="@drawable/assets_bootsplash_logo_512,@drawable/assets_bootsplash_ru,@drawable/assets_bootsplash_uz,@drawable/assets_bootsplash_avia,@drawable/assets_bootsplash_auto,@drawable/assets_bootsplash_logo,@drawable/assets_bootsplash_passportsample,@drawable/assets_bootsplash_local,@drawable/assets_bootsplash_step_1,@drawable/assets_bootsplash_step_2,@drawable/assets_bootsplash_step_3,@drawable/node_modules_reactnavigation_elements_lib_module_assets_searchicon,@drawable/node_modules_reactnavigation_elements_lib_module_assets_backicon,@drawable/node_modules_reactnavigation_elements_lib_module_assets_backiconmask,@drawable/node_modules_reactnavigation_elements_lib_module_assets_clearicon,@drawable/node_modules_reactnavigation_elements_lib_module_assets_closeicon" />
<resources xmlns:tools="http://schemas.android.com/tools" tools:keep="@drawable/assets_bootsplash_logo,@drawable/assets_bootsplash_logo_512,@drawable/assets_bootsplash_ru,@drawable/assets_bootsplash_uz,@drawable/assets_bootsplash_passportsample,@drawable/assets_bootsplash_telegram,@drawable/assets_bootsplash_local,@drawable/assets_bootsplash_avia,@drawable/assets_bootsplash_auto,@drawable/assets_bootsplash_shablon,@drawable/assets_bootsplash_step_1,@drawable/assets_bootsplash_step_2,@drawable/assets_bootsplash_step_3,@drawable/node_modules_reactnavigation_elements_lib_module_assets_searchicon,@drawable/node_modules_reactnavigation_elements_lib_module_assets_backicon,@drawable/node_modules_reactnavigation_elements_lib_module_assets_backiconmask,@drawable/node_modules_reactnavigation_elements_lib_module_assets_clearicon,@drawable/node_modules_reactnavigation_elements_lib_module_assets_closeicon" />

View File

@@ -1,7 +1,8 @@
module.exports = {
presets: ['module:@react-native/babel-preset'],
plugins: [
'react-native-reanimated/plugin',
// 'react-native-reanimated/plugin',
'react-native-worklets/plugin',
'@babel/plugin-transform-export-namespace-from',
[
'module-resolver',

View File

@@ -1,41 +1,46 @@
/**
* @format
*/
import notifee, { AndroidImportance } from '@notifee/react-native';
import messaging from '@react-native-firebase/messaging';
// import notifee, { AndroidImportance } from '@notifee/react-native';
// import messaging from '@react-native-firebase/messaging';
import { AppRegistry } from 'react-native';
import { enableScreens } from 'react-native-screens';
import App from './App';
import { name as appName } from './app.json';
// 📌 Background/Killed xabarlarni ushlash
messaging().setBackgroundMessageHandler(async remoteMessage => {
console.log('Background message:', remoteMessage);
const channelId = await notifee.createChannel({
id: 'default',
name: 'Umumiy bildirishnomalar',
sound: 'default',
importance: AndroidImportance.HIGH,
});
await notifee.displayNotification({
title:
remoteMessage.notification?.title ||
remoteMessage.data?.title ||
'Yangi xabar',
body:
remoteMessage.notification?.body ||
remoteMessage.data?.body ||
'Matn yoq',
android: {
channelId,
largeIcon: 'ic_launcher_foreground',
sound: 'default',
pressAction: {
id: 'default',
},
},
});
enableScreens({
freezeOnBlur: true, // background screen componenti freeze bo'ladi
});
// messaging().setBackgroundMessageHandler(async remoteMessage => {
// console.log('Background message:', remoteMessage);
// const channelId = await notifee.createChannel({
// id: 'default',
// name: 'Umumiy bildirishnomalar',
// sound: 'default',
// importance: AndroidImportance.HIGH,
// });
// await notifee.displayNotification({
// title:
// remoteMessage.notification?.title ||
// remoteMessage.data?.title ||
// 'Yangi xabar',
// body:
// remoteMessage.notification?.body ||
// remoteMessage.data?.body ||
// 'Matn yoq',
// android: {
// channelId,
// largeIcon: 'ic_launcher_foreground',
// sound: 'default',
// pressAction: {
// id: 'default',
// },
// },
// });
// });
AppRegistry.registerComponent(appName, () => App);

View File

@@ -0,0 +1,2 @@
"NSCameraUsageDescription" = "Camera access is needed to take your passport or profile photo.";
"NSPhotoLibraryUsageDescription" = "Photo library access is needed to upload your passport or profile photo.";

View File

@@ -38,11 +38,9 @@
<true/>
</dict>
<key>NSCameraUsageDescription</key>
<string>Kamera orqali rasm olish uchun ruxsat kerak</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Biz sizning joylashuvingizni aniqlash uchun ruxsat soraymiz</string>
<string>$(NSCameraUsageDescription)</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Rasm kutubxonasidan ruxsat kerak</string>
<string>$(NSPhotoLibraryUsageDescription)</string>
<key>RCTNewArchEnabled</key>
<true/>
<key>UILaunchStoryboardName</key>

View File

@@ -0,0 +1,2 @@
"NSCameraUsageDescription" = "Для съемки фото паспорта или аватара необходимо разрешение камеры";
"NSPhotoLibraryUsageDescription" = "Для загрузки фото паспорта или аватара необходимо разрешение доступа к фотогалерее";

View File

@@ -0,0 +1,2 @@
"NSCameraUsageDescription" = "Passport yoki profil rasmini olish uchun kamera ruxsati kerak";
"NSPhotoLibraryUsageDescription" = "Passport yoki profil rasmini yuklash uchun rasm kutubxonasidan foydalanish uchun ruxsat kerak";

1458
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,7 @@
"start": "react-native start",
"test": "jest",
"pod-install": "cd ios && NO_FLIPPER=1 pod install",
"build-debug": "cd android && gradlew clean && cd .. && npx react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res && cd android && gradlew assembleDebug",
"build-debug": "cd android && gradlew clean && cd .. && npx react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res && cd android && gradlew assembleDebug",
"build-release": "cd android && gradlew clean && gradlew bundleRelease"
},
"dependencies": {
@@ -18,12 +18,11 @@
"@react-native-async-storage/async-storage": "^2.2.0",
"@react-native-clipboard/clipboard": "^1.16.3",
"@react-native-community/datetimepicker": "^8.4.2",
"@react-native-firebase/app": "^23.2.0",
"@react-native-firebase/messaging": "^23.2.0",
"@react-navigation/native": "^7.1.17",
"@react-navigation/native-stack": "^7.3.25",
"@tanstack/react-query": "^5.84.2",
"axios": "^1.11.0",
"events": "^3.3.0",
"i18next": "^25.3.2",
"lottie-react-native": "^7.3.0",
"react": "19.1.0",
@@ -33,7 +32,7 @@
"react-native-app-link": "^1.0.1",
"react-native-asset": "^2.1.1",
"react-native-biometrics": "^3.0.1",
"react-native-config": "^1.5.6",
"react-native-config": "^1.5.0",
"react-native-confirmation-code-field": "^8.0.1",
"react-native-device-info": "^14.0.4",
"react-native-dotenv": "^3.4.11",
@@ -43,7 +42,9 @@
"react-native-linear-gradient": "^2.8.3",
"react-native-localize": "^3.5.1",
"react-native-mask-input": "^1.2.3",
"react-native-mmkv": "^2.2.4",
"react-native-modal": "^14.0.0-rc.1",
"react-native-nitro-modules": "^0.31.10",
"react-native-push-notification": "^8.1.1",
"react-native-reanimated": "^4.0.2",
"react-native-safe-area-context": "^5.6.0",

View File

@@ -29,7 +29,7 @@ export interface resendPayload {
export interface loginPayload {
phoneNumber: string;
passportSerial: string;
branchId: number;
// branchId: number;
fcmToken: string;
deviceId: string;
deviceType: string;

View File

@@ -1,150 +1,188 @@
"use client"
'use client';
import React, { useEffect, useState, useCallback, useMemo } from "react"
import { type LayoutChangeEvent, View } from "react-native"
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { type LayoutChangeEvent, View } from 'react-native';
import Animated, {
useSharedValue,
useAnimatedStyle,
withRepeat,
withTiming,
Easing,
withSequence,
withDelay,
} from "react-native-reanimated"
import Auto from "svg/Auto"
import Avia from "svg/Avia"
cancelAnimation,
Easing,
useAnimatedStyle,
useSharedValue,
withRepeat,
withSequence,
withTiming,
} from 'react-native-reanimated';
import Auto from 'svg/Auto';
import Avia from 'svg/Avia';
type Props = {
type: "auto" | "avia"
}
type: 'AUTO' | 'AVIA';
};
const AnimatedIcon = ({ type }: Props) => {
const translateX = useSharedValue(0)
const translateY = useSharedValue(0)
const rotateY = useSharedValue(0)
const direction = useSharedValue(1)
const translateX = useSharedValue(0);
const translateY = useSharedValue(0);
const rotateY = useSharedValue(0);
const direction = useSharedValue(1);
const [containerWidth, setContainerWidth] = useState(0)
const iconSize = 40
const [containerWidth, setContainerWidth] = useState(0);
const iconSize = 40;
const onLayout = useCallback((event: LayoutChangeEvent) => {
const { width } = event.nativeEvent.layout
setContainerWidth(width)
}, []);
const onLayout = useCallback((event: LayoutChangeEvent) => {
const { width } = event.nativeEvent.layout;
setContainerWidth(width);
}, []);
const animationConfig = useMemo(() => ({
duration: 4000,
easing: Easing.linear,
rotationDuration: 300,
arcHeight: -30,
}), []);
const animationConfig = useMemo(
() => ({
duration: 4000,
easing: Easing.linear,
rotationDuration: 300,
arcHeight: -30,
}),
[],
);
const createXAnimation = useCallback((maxX: number) => {
return withRepeat(
withSequence(
withTiming(maxX, {
duration: animationConfig.duration,
easing: animationConfig.easing
}, () => {
direction.value = -1;
rotateY.value = withTiming(180, { duration: animationConfig.rotationDuration });
}),
withTiming(0, {
duration: animationConfig.duration,
easing: animationConfig.easing
}, () => {
direction.value = 1;
rotateY.value = withTiming(0, { duration: animationConfig.rotationDuration });
})
),
-1
);
}, [animationConfig, direction, rotateY]);
const createXAnimation = useCallback(
(maxX: number) => {
return withRepeat(
withSequence(
withTiming(
maxX,
{
duration: animationConfig.duration,
easing: animationConfig.easing,
},
() => {
direction.value = -1;
rotateY.value = withTiming(180, {
duration: animationConfig.rotationDuration,
});
},
),
withTiming(
0,
{
duration: animationConfig.duration,
easing: animationConfig.easing,
},
() => {
direction.value = 1;
rotateY.value = withTiming(0, {
duration: animationConfig.rotationDuration,
});
},
),
),
-1,
);
},
[animationConfig, direction, rotateY],
);
const createYAnimation = useCallback(() => {
if (type === "avia") {
return withRepeat(
withSequence(
withTiming(animationConfig.arcHeight, {
duration: animationConfig.duration / 2,
easing: Easing.out(Easing.quad),
}),
withTiming(0, {
duration: animationConfig.duration / 2,
easing: Easing.in(Easing.quad),
}),
withTiming(animationConfig.arcHeight, {
duration: animationConfig.duration / 2,
easing: Easing.out(Easing.quad),
}),
withTiming(0, {
duration: animationConfig.duration / 2,
easing: Easing.in(Easing.quad),
})
),
-1
);
}
return withTiming(0, { duration: 100 });
}, [type, animationConfig]);
const createYAnimation = useCallback(() => {
if (type === 'AVIA') {
return withRepeat(
withSequence(
withTiming(animationConfig.arcHeight, {
duration: animationConfig.duration / 2,
easing: Easing.out(Easing.quad),
}),
withTiming(0, {
duration: animationConfig.duration / 2,
easing: Easing.in(Easing.quad),
}),
withTiming(animationConfig.arcHeight, {
duration: animationConfig.duration / 2,
easing: Easing.out(Easing.quad),
}),
withTiming(0, {
duration: animationConfig.duration / 2,
easing: Easing.in(Easing.quad),
}),
),
-1,
);
}
return withTiming(0, { duration: 100 });
}, [type, animationConfig]);
useEffect(() => {
if (containerWidth === 0) return;
useEffect(() => {
if (containerWidth === 0) return;
const maxX = containerWidth - iconSize;
const maxX = containerWidth - iconSize;
translateX.value = createXAnimation(maxX);
translateY.value = createYAnimation();
}, [containerWidth, type, createXAnimation, createYAnimation, translateX, translateY]);
// eski animatsiyani toxtat
cancelAnimation(translateX);
cancelAnimation(translateY);
const animatedStyle = useAnimatedStyle(() => {
return {
transform: [
{ translateX: translateX.value },
{ translateY: translateY.value },
{ rotateY: `${rotateY.value}deg` },
],
}
});
// qiymatlarni 0 ga reset qil
translateX.value = 0;
translateY.value = 0;
rotateY.value = 0;
direction.value = 1;
const containerStyle = useMemo(() => ({
height: 100,
justifyContent: "center" as const,
backgroundColor: "transparent" as const,
position: "relative" as const,
}), []);
// keyin yangisini qayta boshlash
translateX.value = createXAnimation(maxX);
translateY.value = createYAnimation();
}, [containerWidth, type]); // type = activeTab dan keladigan qiymat
const trackStyle = useMemo(() => ({
height: 2,
backgroundColor: "#28A7E850",
position: "absolute" as const,
top: 25,
left: 0,
right: 0,
}), []);
const animatedStyle = useAnimatedStyle(() => {
return {
transform: [
{ translateX: translateX.value },
{ translateY: translateY.value },
{ rotateY: `${rotateY.value}deg` },
],
};
});
const iconContainerStyle = useMemo(() => ({
position: "absolute" as const,
top: 0,
height: iconSize,
width: iconSize,
}), [iconSize]);
const containerStyle = useMemo(
() => ({
height: 100,
justifyContent: 'center' as const,
backgroundColor: 'transparent' as const,
position: 'relative' as const,
}),
[],
);
const renderIcon = useMemo(() => {
if (type === "auto") {
return <Auto color="#28A7E8" width={iconSize} height={iconSize} />;
}
return <Avia color="#28A7E8" width={iconSize} height={iconSize} />;
}, [type, iconSize]);
const trackStyle = useMemo(
() => ({
height: 2,
backgroundColor: '#28A7E850',
position: 'absolute' as const,
top: 25,
left: 0,
right: 0,
}),
[],
);
return (
<View onLayout={onLayout} style={containerStyle}>
<View style={trackStyle} />
<Animated.View style={[iconContainerStyle, animatedStyle]}>
{renderIcon}
</Animated.View>
</View>
)
}
const iconContainerStyle = useMemo(
() => ({
position: 'absolute' as const,
top: 0,
height: iconSize,
width: iconSize,
}),
[iconSize],
);
const renderIcon = useMemo(() => {
if (type === 'AUTO') {
return <Auto color="#28A7E8" width={iconSize} height={iconSize} />;
}
return <Avia color="#28A7E8" width={iconSize} height={iconSize} />;
}, [type, iconSize]);
return (
<View onLayout={onLayout} style={containerStyle}>
<View style={trackStyle} />
<Animated.View style={[iconContainerStyle, animatedStyle]}>
{renderIcon}
</Animated.View>
</View>
);
};
export default AnimatedIcon;

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

@@ -1,9 +1,18 @@
import React, { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Alert, Image, StyleSheet, TouchableOpacity, View } from 'react-native';
import {
ActionSheetIOS,
Alert,
Image,
Platform,
StyleSheet,
TouchableOpacity,
View,
} from 'react-native';
import {
Asset,
ImagePickerResponse,
launchCamera,
launchImageLibrary,
MediaType,
} from 'react-native-image-picker';
@@ -20,9 +29,6 @@ export interface FileData {
export interface SingleFileDropProps {
title: string;
onFileSelected?: (file: FileData) => void;
/**
* Ruxsat berilgan MIME tipi (masalan: "image/png" yoki "image/jpeg")
*/
type?: string;
}
@@ -44,7 +50,7 @@ const SingleFileDrop: React.FC<SingleFileDropProps> = ({
const handleImagePickerResponse = useCallback(
(response: ImagePickerResponse) => {
if (response.didCancel) return; // foydalanuvchi bekor qilsa
if (response.didCancel) return;
if (response.errorCode) {
Alert.alert('Xato', response.errorMessage || 'Rasmni yuklashda xato');
return;
@@ -53,7 +59,6 @@ const SingleFileDrop: React.FC<SingleFileDropProps> = ({
const asset: Asset | undefined = response.assets?.[0];
if (!asset || !asset.uri || !asset.type || !asset.base64) return;
// faqat belgilangan tipdagi fayllarni qabul qilish
if (!asset.type.startsWith('image/')) {
Alert.alert('Xato', 'Faqat rasm fayllarni yuklashingiz mumkin');
return;
@@ -77,6 +82,32 @@ const SingleFileDrop: React.FC<SingleFileDropProps> = ({
launchImageLibrary(imagePickerOptions, handleImagePickerResponse);
}, [imagePickerOptions, handleImagePickerResponse]);
const openCamera = useCallback((): void => {
launchCamera(imagePickerOptions, handleImagePickerResponse);
}, [imagePickerOptions, handleImagePickerResponse]);
const openPicker = useCallback(() => {
if (Platform.OS === 'ios') {
ActionSheetIOS.showActionSheetWithOptions(
{
options: ['Bekor qilish', 'Kamera', 'Galereya'],
cancelButtonIndex: 0,
},
buttonIndex => {
if (buttonIndex === 1) openCamera();
else if (buttonIndex === 2) openGallery();
},
);
} else {
// Android uchun oddiy alert-style tanlov
Alert.alert(t('Fayl tanlash'), t('Qaysi usulda yuklamoqchisiz?'), [
{ text: t('Bekor qilish'), style: 'cancel' },
{ text: t('Kamera'), onPress: openCamera },
{ text: t('Galereya'), onPress: openGallery },
]);
}
}, [openCamera, openGallery]);
const UploadIcon = useMemo(
() => () =>
(
@@ -122,7 +153,7 @@ const SingleFileDrop: React.FC<SingleFileDropProps> = ({
}, [selectedImage, title, UploadIcon]);
return (
<TouchableOpacity style={styles.dropSection} onPress={openGallery}>
<TouchableOpacity style={styles.dropSection} onPress={openPicker}>
{renderContent}
</TouchableOpacity>
);

View File

@@ -5,7 +5,6 @@ import {
Dimensions,
Image,
Linking,
Platform,
StyleSheet,
TouchableOpacity,
View,
@@ -13,7 +12,6 @@ import {
import AppLink from 'react-native-app-link';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import Logo from 'screens/../../assets/bootsplash/logo.png';
import Bell from 'svg/Bell';
import Instagram from 'svg/Instagram';
import Telegram from 'svg/Telegram';
import AppText from './AppText';
@@ -97,14 +95,13 @@ const Navbar = () => {
size={iconSizes.facebook}
/>
</TouchableOpacity> */}
{Platform.OS === 'android' && (
{/* {Platform.OS === 'android' && (
<TouchableOpacity
onPress={() => navigation.navigate('Notifications')}
>
<Bell color="#fff" width={24} height={24} />
{/* <View style={styles.bellDot} /> */}
</TouchableOpacity>
)}
)} */}
</View>
</View>

0
src/helpers/event.ts Normal file
View File

View File

@@ -0,0 +1,8 @@
export const formatPrice = (amount: number | string) => {
const numericAmount = Number(amount) || 0;
const formatted = numericAmount
.toString()
.replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
return formatted;
};

View File

@@ -230,5 +230,26 @@
"Xatolik yuz berdi": "Произошла ошибка",
"Passport qo'shishda xatolik yuz berdi": "Ошибка добавления паспорта",
"Kodsiz tovarlar": "Товары без кода",
"Hech qanday ma'lumot topilmadi": "Нет данных"
"Hech qanday ma'lumot topilmadi": "Нет данных",
"Foydalanish shartlari va qoidalari": "Условия и правила использования",
"Kamera": "Камера",
"Galereya": "Галерея",
"Fayl tanlash": "Выбрать файл",
"Qaysi usulda yuklamoqchisiz?": "Каким способом вы хотите загрузить?",
"Umumiy qoidalar": "Общие положения",
"Biz sizdan Xitoy xalq Respublikasi Davlatidan sotib olingan tovarlarni olib kelishda quyidagi tovarlarni jonatmaslikka rozilik so'raymiz.": "Мы просим Вашего согласия не отправлять следующие товары при ввозе товаров, приобретенных в Китайской Народной Республике.",
"Taqiqlangan tovarlar": "Запрещённые товары",
"Taqiqlangan tovarlar matni": "В соответствии с Указом Президента Республики Узбекистан от 25 декабря 1998 года No УП-2160 запрещается ввоз в Республику Узбекистан: брошюр, фотографий, видео, аудиопродукции, агитационных материалов и печатных изданий, направленных на нанесение ущерба государству и обществу; пропаганды нарушений порядка, территориальной целостности, политической независимости и государственного суверенитета, войны, терроризма, насилия, национальной идентичности и религиозной ненависти, расизма и его многообразия (антисемитизм, фашизм), а также порнографических материалов.",
"Tovarlar": "Запрещается ввоз: взрывчатых, ядовитых, легковоспламеняющихся, радиоактивных веществ, продуктов питания, лекарственных средств, медицинских изделий и оборудования, полиграфического оборудования, мобильных телефонов, зубной пасты (порошка), рационов, USB-флешек, различных флеш-карт и других аналогичных товаров.",
"Agar sizda savollar bolsa, quyidagi aloqa manzillaridan foydalanishingiz mumkin:": "Если у вас есть вопросы, вы можете воспользоваться следующими контактами:",
"Aloqa uchun": "Для связи",
"Email": "Электронная почта",
"Telegram": "Телеграм",
"Roziman": "Согласен",
"To'lov kutilmoqda": "Ожидание оплаты",
"Reys": "Рейс",
"To'lov holati": "Статус оплаты",
"Bekor qilingan": "Отменено",
"Mahsulot nomi": "Название товара",
"Ma'lumotlarni to'liq kiriting": "Введите полные данные"
}

View File

@@ -26,6 +26,8 @@
"Familiya": "Familiya",
"Familiyangiz": "Familiyangiz",
"Bizni qaerdan topdingiz?": "Bizni qaerdan topdingiz?",
"Reys": "Reys",
"narxi": "narxi",
"Bizni kim tavsiya qildi...": "Bizni kim tavsiya qildi...",
"Foydalanish shartlari": "Foydalanish shartlari",
"bilan tanishib chiqdim!": "bilan tanishib chiqdim!",
@@ -42,13 +44,13 @@
"foydalanuvchi_majburiyatlari": "2. Foydalanuvchi majburiyatlari",
"foydalanuvchi_majburiyatlari_text": "• To'g'ri va aniq ma'lumotlar taqdim etish\n• Boshqa foydalanuvchilarning huquqlarini hurmat qilish\n• Tizimdan noto'g'ri maqsadlarda foydalanmaslik\n• Xavfsizlik qoidalariga rioya qilish",
"maxfiylik_siyosati": "3. Maxfiylik siyosati",
"maxfiylik_siyosati_text": "Sizning shaxsiy ma'lumotlaringiz maxfiylik siyosatimizga muvofiq himoyalanadi. Biz sizning ma'lumotlaringizni uchinchi shaxslarga bermaydi va xavfsiz saqlashni ta'minlaymiz.",
"maxfiylik_siyosati_text": "Sizning shaxsiy ma'lumotlaringiz maxfiylik siyosatimizga muvofiq himoyalanadi. Biz sizning ma'lumotlaringizni uchinchi shaxslarga bermaymiz va xavfsiz saqlashni ta'minlaymiz.",
"javobgarlik": "4. Javobgarlik",
"javobgarlik_text": "Kompaniya ilovadan foydalanish natijasida yuzaga kelishi mumkin bo'lgan zararlar uchun javobgar emas. Foydalanuvchi o'z harakatlari uchun to'liq javobgarlikni o'z zimmasiga oladi.",
"shartlarni_ozgartirish": "5. Shartlarni o'zgartirish",
"shartlarni_ozgartirish_text": "Kompaniya ushbu shartlarni istalgan vaqtda o'zgartirish huquqini o'zida saqlab qoladi. O'zgarishlar ilovada e'lon qilinadi va kuchga kirish sanasi ko'rsatiladi.",
"aloqa": "6. Aloqa",
"aloqa_text": "Savollar yoki takliflar bo'lsa, biz bilan quyidagi manzil orqali bog'laning:\nEmail: support@company.uz\nTelefon: +998 71 123 45 67",
"aloqa_text": "Savollar yoki takliflar bo'lsa, biz bilan quyidagi manzil orqali bog'laning:",
"oxirgi_yangilanish": "Oxirgi yangilanish:",
"roziman": "Roziman",
"Shaxsiy maʼlumotlar": "Shaxsiy maʼlumotlar",
@@ -111,7 +113,7 @@
"Filiallarimiz ro'yhati ilovada mavjud": "Filiallarimiz ro'yhati ilovada mavjud",
"Agar siz yashayotgan hududda bizning filialimiz mavjud bo'lmasa, o'zingizga eng yaqin bo'lgan filialni tanlab, ro'yhatdan o'tishingiz mumkin.": "Agar siz yashayotgan hududda bizning filialimiz mavjud bo'lmasa, o'zingizga eng yaqin bo'lgan filialni tanlab, ro'yhatdan o'tishingiz mumkin.",
"ya'ni, qancha gramm mahsulot olsangiz, shuncha og'irligi (gramm) uchun to'lov qilasiz.": "ya'ni, qancha gramm mahsulot olsangiz, shuncha og'irligi (gramm) uchun to'lov qilasiz.",
"(Bu kargo narxini osishiga olib keladi. Seriyali buyularni avto kargo orqali olib kelish arzonga tushadi)": "(Bu kargo narxini osishiga olib keladi. Seriyali buyularni avto kargo orqali olib kelish arzonga tushadi)",
"(Bu kargo narxini osishiga olib keladi. Seriyali buyularni avto kargo orqali olib kelish arzonga tushadi)": "(Bu kargo narxini osishiga olib keladi. Seriyali buyumlarni avto kargo orqali olib kelish arzonga tushadi)",
"Avto kargoda bir mahsulotdan seriyali ravishda, istalgan katta miqdorda xarid qilish mumkin. Doimiy ravishda kop yuk oluvchilar uchun maxsus chegirmali narxlarimiz mavjud.": "Avto kargoda bir mahsulotdan seriyali ravishda, istalgan katta miqdorda xarid qilish mumkin. Doimiy ravishda kop yuk oluvchilar uchun maxsus chegirmali narxlarimiz mavjud.",
"dan boshlab": "dan boshlab",
@@ -156,6 +158,7 @@
"Mahsulotlar ogirligi": "Mahsulotlar ogirligi",
"Faol": "Faol",
"Kutilmoqda": "Kutilmoqda",
"To'lov kutilmoqda": "To'lov kutilmoqda",
"Faol emas": "Faol emas",
"Xatolik yuz berdi!": "Xatolik yuz berdi!",
"Akkaunt faol emas!": "Akkaunt faol emas!",
@@ -177,10 +180,11 @@
"som": "so'm",
"Umumiy narx": "Umumiy narx",
"Yopish": "Yopish",
"Mahsulot nomi": "Mahsulot nomi",
"Passportlarim": "Passportlarim",
"Hali pasport qo'shilmagan": "Hali pasport qo'shilmagan",
"Yangi pasport qo'shish uchun tugmani bosing": "Yangi pasport qo'shish uchun tugmani bosing",
"Yangi pasport qo'shish": "Yangi pasport qo'shish",
"Yangi pasport qo'shish uchun tugmani bosing": "Yangi passport qo'shish uchun tugmani bosing",
"Yangi pasport qo'shish": "Yangi passport qo'shish",
"Passport malumotlarim": "Passport malumotlarim",
"Tez ID": "Tez ID",
"Toliq ismi": "Toliq ismi",
@@ -231,5 +235,23 @@
"Yuborish": "Yuborish",
"Passport qo'shishda xatolik yuz berdi": "Passport qo'shishda xatolik yuz berdi",
"Xatolik yuz berdi": "Xatolik yuz berdi",
"Kodsiz tovarlar": "Kodsiz tovarlar"
"Kodsiz tovarlar": "Kodsiz tovarlar",
"Kamera": "Kamera",
"Galereya": "Galereya",
"Fayl tanlash": "Fayl tanlash",
"Qaysi usulda yuklamoqchisiz?": "Qaysi usulda yuklamoqchisiz?",
"Foydalanish shartlari va qoidalari": "Foydalanish shartlari va qoidalari",
"Umumiy qoidalar": "Umumiy qoidalar",
"Biz sizdan Xitoy xalq Respublikasi Davlatidan sotib olingan tovarlarni olib kelishda quyidagi tovarlarni jonatmaslikka rozilik so'raymiz.": "Biz sizdan Xitoy xalq Respublikasi Davlatidan sotib olingan tovarlarni olib kelishda quyidagi tovarlarni jonatmaslikka rozilik so'raymiz.",
"Taqiqlangan tovarlar": "Taqiqlangan tovarlar",
"Taqiqlangan tovarlar matni": " Ozbekiston Respublikasi Prezidentining 1998-yil 25-dekabrdagi UP-2160-son Farmoniga asosan Ozbekiston Respublikasiga olib kirish qudagilar taqiqlanadi: davlat va jamiyatga putur yetkazishga qaratilgan risolalar, fotosuratlar, videolar, audiomahsulotlar, tashviqot materiallari va bosma nashrlar; tartib, hududiy yaxlitlik, siyosiy mustaqillik va davlat suverenitetini buzish, urush, terrorizm, zoravonlik, milliy oziga xoslik va diniy nafrat, irqchilik va uning xilma-xilligini (antisemitizm, fashizm), shuningdek, pornografik materiallarga targib qilish.",
"Tovarlar": "Yonuvchan, tez yonuvchi, portlovchi, zaharli, radioaktiv moddalar, oziq-ovqat mahsulotlari, dori-darmonlar, tibbiy buyumlar, tibbiy asbob-uskunalar, poligrafiya uskunalari, uyali telefonlar, tish pastasi (chang), ratsionlar, USB flesh-disk, turli xil flesh-kartalar va boshqa shunga oxshash mahsulotlarni olib kirish taqiqlanadi.",
"Agar sizda savollar bolsa, quyidagi aloqa manzillaridan foydalanishingiz mumkin:": "Agar sizda savollar bolsa, quyidagi aloqa manzillaridan foydalanishingiz mumkin:",
"Aloqa uchun": "Aloqa uchun",
"Email": "Email",
"Telegram": "Telegram",
"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,7 +5,6 @@ export const loginSchema = z.object({
phone: z.string().min(12, 'Xato raqam kiritildi'),
passportSeriya: z.string().length(2, '2 ta harf kerak'),
passportNumber: z.string().length(7, '7 ta raqam kerak'),
branchId: z.number().min(1, 'Filialni tanlang'),
});
export type LoginFormType = z.infer<typeof loginSchema>;

View File

@@ -1,12 +1,11 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import { getApp } from '@react-native-firebase/app';
import { getMessaging, getToken } from '@react-native-firebase/messaging';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useMutation } from '@tanstack/react-query';
import { authApi } from 'api/auth';
import { otpPayload, resendPayload } from 'api/auth/type';
import AppText from 'components/AppText';
import ErrorNotification from 'components/ErrorNotification';
import formatPhone from 'helpers/formatPhone';
import React, { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -20,7 +19,6 @@ import {
TouchableOpacity,
View,
} from 'react-native';
import DeviceInfo from 'react-native-device-info';
import { SafeAreaView } from 'react-native-safe-area-context';
import Logo from 'screens/../../assets/bootsplash/logo_512.png';
import LanguageSelector from 'screens/auth/select-language/SelectLang';
@@ -39,51 +37,56 @@ const OTP_LENGTH = 4;
const Confirm = () => {
const navigation = useNavigation<VerificationCodeScreenNavigationProp>();
const { t } = useTranslation();
const [firebaseToken, setFirebseToken] = useState<{
fcmToken: string;
deviceId: string;
deviceName: string;
} | null>();
// const [firebaseToken, setFirebseToken] = useState<{
// fcmToken: string;
// deviceId: string;
// deviceName: string;
// } | null>();
const [code, setCode] = useState<string[]>(new Array(OTP_LENGTH).fill(''));
const [timer, setTimer] = useState(60);
const [errorConfirm, setErrorConfirm] = useState<string | null>(null);
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);
// const app = getApp();
// const messaging = getMessaging(app);
const getDeviceData = async () => {
try {
const fcmToken = await getToken(messaging);
return {
fcmToken,
deviceId: await DeviceInfo.getUniqueId(),
deviceName: await DeviceInfo.getDeviceName(),
};
} catch (e) {
console.log('Xato:', e);
return null;
}
};
// const getDeviceData = async () => {
// try {
// const fcmToken = await getToken(messaging);
// return {
// fcmToken,
// deviceId: await DeviceInfo.getUniqueId(),
// deviceName: await DeviceInfo.getDeviceName(),
// };
// } catch (e) {
// console.log('Xato:', e);
// return null;
// }
// };
useEffect(() => {
getDeviceData().then(data => {
setFirebseToken(data);
});
}, []);
// useEffect(() => {
// getDeviceData().then(data => {
// setFirebseToken(data);
// });
// }, []);
const { mutate, isPending } = useMutation({
mutationFn: (payload: otpPayload) => authApi.verifyOtp(payload),
onSuccess: async res => {
await AsyncStorage.setItem('token', res.data.accessToken);
navigation.navigate('Home');
setErrorConfirm(null);
console.log(res);
navigation.reset({
index: 0,
routes: [{ name: 'Home' }], // login sahifasiga qaytarish
});
setVisible(false);
},
onError: (err: any) => {
setErrorConfirm(err?.response?.data?.message || 'Xato yuz berdi');
setError(err?.response?.data?.message || 'Xatolik yuz berdi');
setVisible(true);
},
});
@@ -94,10 +97,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);
},
});
@@ -139,16 +143,14 @@ const Confirm = () => {
const handleVerifyCode = () => {
const enteredCode = code.join('');
if (firebaseToken) {
mutate({
phoneNumber,
otp: enteredCode,
otpType: 'LOGIN',
deviceId: firebaseToken.deviceId,
deviceName: firebaseToken.deviceName,
fcmToken: firebaseToken.fcmToken,
});
}
mutate({
phoneNumber,
otp: enteredCode,
otpType: 'LOGIN',
deviceId: '',
deviceName: '',
fcmToken: '',
});
};
return (
@@ -170,6 +172,11 @@ const Confirm = () => {
</TouchableOpacity>
<LanguageSelector />
</View>
<ErrorNotification
setVisible={setVisible}
error={error}
visible={visible}
/>
<KeyboardAvoidingView
style={styles.container}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}

View File

@@ -1,21 +1,13 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { getApp } from '@react-native-firebase/app';
import { getMessaging, getToken } from '@react-native-firebase/messaging';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useMutation, useQuery } from '@tanstack/react-query';
import { useMutation } from '@tanstack/react-query';
import { authApi } from 'api/auth';
import { loginPayload } from 'api/auth/type';
import { Branch, branchApi } from 'api/branch';
import AppText from 'components/AppText';
import ErrorNotification from 'components/ErrorNotification';
import formatPhone from 'helpers/formatPhone';
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import React, { useCallback, useMemo, useRef, useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import {
@@ -28,14 +20,11 @@ import {
TouchableOpacity,
View,
} from 'react-native';
import DeviceInfo from 'react-native-device-info';
import { SafeAreaView } from 'react-native-safe-area-context';
import Logo from 'screens/../../assets/bootsplash/logo_512.png';
import { LoginFormType, loginSchema } from 'screens/auth/login/lib/form';
import LanguageSelector from 'screens/auth/select-language/SelectLang';
import ArrowDown from 'svg/ArrowDown';
import ArrowLeft from 'svg/ArrowLeft';
import ArrowUp from 'svg/ArrowUp';
import { useUserStore } from '../lib/userstore';
import { Loginstyle } from './styled';
@@ -50,45 +39,45 @@ 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 { data: branchList } = useQuery({
queryKey: ['branchList'],
queryFn: branchApi.branchList,
});
const [firebaseToken, setFirebseToken] = useState<{
fcmToken: string;
deviceId: string;
deviceName: string;
deviceType: string;
} | null>();
const [visible, setVisible] = useState(false);
// const { data: branchList } = useQuery({
// queryKey: ['branchList'],
// queryFn: branchApi.branchList,
// });
// const [firebaseToken, setFirebseToken] = useState<{
// fcmToken: string;
// deviceId: string;
// deviceName: string;
// deviceType: string;
// } | null>();
const app = getApp();
const messaging = getMessaging(app);
// const app = getApp();
// const messaging = getMessaging(app);
const getDeviceData = async () => {
try {
const fcmToken = await getToken(messaging);
return {
fcmToken,
deviceId: await DeviceInfo.getUniqueId(),
deviceName: await DeviceInfo.getDeviceName(),
deviceType: await DeviceInfo.getDeviceType(),
};
} catch (e) {
console.log('Xato:', e);
return null;
}
};
// const getDeviceData = async () => {
// try {
// const fcmToken = await getToken(messaging);
// return {
// fcmToken,
// deviceId: await DeviceInfo.getUniqueId(),
// deviceName: await DeviceInfo.getDeviceName(),
// deviceType: await DeviceInfo.getDeviceType(),
// };
// } catch (e) {
// console.log('Xato:', e);
// return null;
// }
// };
useEffect(() => {
getDeviceData().then(data => {
setFirebseToken(data);
});
}, []);
// useEffect(() => {
// getDeviceData().then(data => {
// setFirebseToken(data);
// });
// }, []);
const { mutate, isPending } = useMutation({
mutationFn: (payload: loginPayload) => authApi.login(payload),
@@ -96,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',
);
},
});
@@ -117,21 +111,40 @@ 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({
branchId: data.branchId,
phoneNumber: data.phone,
passportSerial: `${data.passportSeriya.toUpperCase()}${
data.passportNumber
}`,
fcmToken: firebaseToken?.fcmToken || '',
deviceId: firebaseToken?.deviceId || '',
deviceType: firebaseToken?.deviceType || '',
deviceName: firebaseToken?.deviceName || '',
});
// navigation.navigate('Login-Confirm');
setUser({
phoneNumber: data.phone,
fcmToken: '',
deviceId: '',
deviceType: '',
deviceName: '',
});
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(() => {
@@ -169,11 +182,20 @@ const Login = () => {
</TouchableOpacity>
<LanguageSelector />
</View>
{/* error modal start*/}
<ErrorNotification
setVisible={setVisible}
error={error}
visible={visible}
/>
{/* error modal end*/}
<KeyboardAvoidingView
style={Loginstyle.container}
behavior={keyboardBehavior}
>
<ScrollView style={{ flex: 1 }}>
<ScrollView style={{ flex: 1 }} keyboardShouldPersistTaps="handled">
<View style={Loginstyle.scrollContainer}>
<View style={Loginstyle.loginContainer}>
<AppText style={Loginstyle.title}>
@@ -198,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>
);
}}
@@ -252,15 +269,9 @@ const Login = () => {
)}
/>
</View>
{(errors.passportSeriya || errors.passportNumber) && (
<AppText style={Loginstyle.errorText}>
{t(errors.passportSeriya?.message || '') ||
t(errors.passportNumber?.message || '')}
</AppText>
)}
</View>
<Controller
{/* <Controller
control={control}
name="branchId"
render={({ field: { value } }) => (
@@ -323,10 +334,10 @@ const Login = () => {
)}
</View>
)}
/>
/> */}
<TouchableOpacity
onPress={handleSubmit(onSubmit)}
onPress={handleSubmit(onSubmit, onErrorSubmit)}
style={Loginstyle.button}
>
{isPending ? (
@@ -337,6 +348,7 @@ const Login = () => {
</AppText>
)}
</TouchableOpacity>
<View
style={{
display: 'flex',

View File

@@ -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',

View File

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

View File

@@ -1,14 +1,15 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import { getApp } from '@react-native-firebase/app';
import { getMessaging, getToken } from '@react-native-firebase/messaging';
// import { getApp } from '@react-native-firebase/app';
// import { getMessaging, getToken } from '@react-native-firebase/messaging';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useMutation } from '@tanstack/react-query';
import { authApi } from 'api/auth';
import { otpPayload, resendPayload } from 'api/auth/type';
import AppText from 'components/AppText';
import 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,
@@ -20,10 +21,8 @@ import {
TouchableOpacity,
View,
} from 'react-native';
import DeviceInfo from 'react-native-device-info';
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';
@@ -50,34 +49,37 @@ const Confirm = ({
const [errorConfirm, setErrorConfirm] = useState<string | null>(null);
const inputRefs = useRef<Array<TextInput | null>>([]);
const { phoneNumber } = useUserStore(state => state);
const [firebaseToken, setFirebseToken] = useState<{
fcmToken: string;
deviceId: string;
deviceName: string;
} | null>();
const [visible, setVisible] = useState(false);
const [error, setError] = useState<string>('Xatolik yuz berdi');
// const [firebaseToken, setFirebseToken] = useState<{
// fcmToken: string;
// deviceId: string;
// deviceName: string;
// } | null>();
const app = getApp();
const messaging = getMessaging(app);
// const app = getApp();
// const messaging = getMessaging(app);
const getDeviceData = async () => {
try {
const fcmToken = await getToken(messaging);
return {
fcmToken,
deviceId: await DeviceInfo.getUniqueId(),
deviceName: await DeviceInfo.getDeviceName(),
};
} catch (e) {
console.log('Xato:', e);
return null;
}
};
// const getDeviceData = async () => {
// try {
// const fcmToken = await getToken(messaging);
// return {
// fcmToken,
// deviceId: await DeviceInfo.getUniqueId(),
// deviceName: await DeviceInfo.getDeviceName(),
// };
// } catch (e) {
// console.log('Xato:', e);
// return null;
// }
// };
// useEffect(() => {
// getDeviceData().then(data => {
// setFirebseToken(data);
// });
// }, []);
useEffect(() => {
getDeviceData().then(data => {
setFirebseToken(data);
});
}, []);
const { mutate, isPending } = useMutation({
mutationFn: (payload: otpPayload) => authApi.verifyOtp(payload),
onSuccess: async res => {
@@ -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;
@@ -149,16 +136,15 @@ const Confirm = ({
const handleVerifyCode = () => {
const enteredCode = code.join('');
if (firebaseToken) {
mutate({
phoneNumber,
otp: enteredCode,
otpType: 'REGISTRATION',
deviceId: firebaseToken.deviceId,
deviceName: firebaseToken.deviceName,
fcmToken: firebaseToken.fcmToken,
});
}
mutate({
phoneNumber,
otp: enteredCode,
otpType: 'REGISTRATION',
deviceId: '',
deviceName: '',
fcmToken: '',
});
};
return (
@@ -180,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'}

View File

@@ -1,8 +1,6 @@
'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import { getApp } from '@react-native-firebase/app';
import { getMessaging, getToken } from '@react-native-firebase/messaging';
import {
type RouteProp,
useNavigation,
@@ -14,22 +12,22 @@ 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, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import {
ActivityIndicator,
Animated,
ImageBackground,
Keyboard,
KeyboardAvoidingView,
Platform,
ScrollView,
TextInput,
TouchableOpacity,
View,
} from 'react-native';
import DeviceInfo from 'react-native-device-info';
import { SafeAreaView } from 'react-native-safe-area-context';
import Logo from 'screens/../../assets/bootsplash/logo_512.png';
import {
@@ -60,51 +58,27 @@ 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'],
queryFn: branchApi.branchList,
});
const [firebaseToken, setFirebseToken] = useState<{
fcmToken: string;
deviceId: string;
deviceName: string;
deviceType: string;
} | null>();
const app = getApp();
const messaging = getMessaging(app);
const getDeviceData = async () => {
try {
const fcmToken = await getToken(messaging);
return {
fcmToken,
deviceId: await DeviceInfo.getUniqueId(),
deviceName: await DeviceInfo.getDeviceName(),
deviceType: await DeviceInfo.getDeviceType(),
};
} catch (e) {
console.log('Xato:', e);
return null;
}
};
useEffect(() => {
getDeviceData().then(data => {
setFirebseToken(data);
});
}, []);
const { mutate, isPending } = useMutation({
mutationFn: (payload: registerPayload) => authApi.register(payload),
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',
);
},
});
@@ -115,12 +89,12 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
const navigation = useNavigation<LoginScreenNavigationProp>();
const [rawPhone, setRawPhone] = useState('+998');
const route = useRoute<RouteProp<RootStackParamList, 'Register'>>();
const {
control,
handleSubmit,
setValue,
formState: { errors },
getValues,
} = useForm<FirstStepFormType>({
resolver: zodResolver(FirstStepSchema),
defaultValues: {
@@ -131,7 +105,34 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
},
});
// 🔑 Input ref'lar
const firstNameRef = useRef<TextInput>(null);
const lastNameRef = useRef<TextInput>(null);
const phoneRef = useRef<TextInput>(null);
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,
@@ -144,13 +145,27 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
recommend: data.recommend,
branchId: data.branchId,
address: data.address,
fcmToken: firebaseToken?.fcmToken || '',
deviceId: firebaseToken?.deviceId || '',
deviceType: firebaseToken?.deviceType || '',
deviceName: firebaseToken?.deviceId || '',
fcmToken: '',
deviceId: '',
deviceType: '',
deviceName: '',
});
};
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);
@@ -196,7 +211,7 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
imageStyle={{
opacity: 0.1,
height: '100%',
width: '100%',
width: '90%',
transform: [{ scale: 1 }],
}}
>
@@ -208,12 +223,13 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
<LanguageSelector />
</View>
<KeyboardAvoidingView
style={RegisterStyle.container}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={{ flex: 1 }}
behavior="padding"
keyboardVerticalOffset={50}
>
<ScrollView
showsVerticalScrollIndicator={false}
style={RegisterStyle.content}
contentContainerStyle={{ flexGrow: 1 }}
keyboardShouldPersistTaps="handled"
>
<View style={RegisterStyle.scrollContainer}>
<View style={RegisterStyle.loginContainer}>
@@ -221,50 +237,56 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
{t("Ro'yxatdan o'tish")}
</AppText>
<ErrorNotification
setVisible={setVisible}
error={error}
visible={visible}
/>
{/* Ism */}
<Controller
control={control}
name="firstName"
render={({ field: { onChange, value } }) => (
<View>
<AppText style={RegisterStyle.label}>{t('Ism')} </AppText>
<AppText style={RegisterStyle.label}>{t('Ism')}</AppText>
<TextInput
ref={firstNameRef}
style={RegisterStyle.input}
placeholder={t('Ismingiz')}
onChangeText={onChange}
value={value}
placeholderTextColor={'#D8DADC'}
returnKeyType="next"
onSubmitEditing={() => lastNameRef.current?.focus()}
/>
{errors.firstName && (
<AppText style={RegisterStyle.errorText}>
{t(errors.firstName.message || '')}
</AppText>
)}
</View>
)}
/>
{/* Familiya */}
<Controller
control={control}
name="lastName"
render={({ field: { onChange, value } }) => (
<View>
<AppText style={RegisterStyle.label}>
{t('Familiya')}{' '}
{t('Familiya')}
</AppText>
<TextInput
ref={lastNameRef}
style={RegisterStyle.input}
placeholder={t('Familiyangiz')}
placeholderTextColor={'#D8DADC'}
onChangeText={onChange}
value={value}
returnKeyType="next"
onSubmitEditing={() => phoneRef.current?.focus()}
/>
{errors.lastName && (
<AppText style={RegisterStyle.errorText}>
{t(errors.lastName.message || '')}
</AppText>
)}
</View>
)}
/>
{/* Telefon raqami */}
<Controller
control={control}
name="phoneNumber"
@@ -276,6 +298,7 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
{t('Telefon raqami')}
</AppText>
<TextInput
ref={phoneRef}
keyboardType="numeric"
placeholder="+998 __ ___-__-__"
value={formatted}
@@ -290,30 +313,32 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
style={RegisterStyle.input}
placeholderTextColor="#D8DADC"
maxLength={17}
returnKeyType="next"
onSubmitEditing={
() => setFilialDropdownVisible(true) // ❗ Branch select ochiladi
}
/>
{errors.phoneNumber && (
<AppText style={RegisterStyle.errorText}>
{t(errors.phoneNumber.message || '')}
</AppText>
)}
</View>
);
}}
/>
{/* Filial (dropdown) */}
<Controller
control={control}
name="branchId"
render={({ field: { value } }) => (
<View style={{ position: 'relative' }}>
<AppText style={RegisterStyle.label}>
{t('Filial')}{' '}
{t('Filial')}
</AppText>
<View style={RegisterStyle.input}>
<TouchableOpacity
style={RegisterStyle.selector}
onPress={() =>
setFilialDropdownVisible(prev => !prev)
}
onPress={() => {
setFilialDropdownVisible(prev => !prev);
Keyboard.dismiss();
}}
>
<AppText
style={
@@ -345,6 +370,11 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
onPress={() => {
setValue('branchId', item.id);
setFilialDropdownVisible(false);
// keyingi inputga focus
setTimeout(
() => addressRef.current?.focus(),
200,
);
}}
>
<AppText
@@ -357,14 +387,11 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
</ScrollView>
</View>
)}
{errors.branchId && (
<AppText style={RegisterStyle.errorText}>
{t(errors.branchId.message || '')}
</AppText>
)}
</View>
)}
/>
{/* Manzil */}
<Controller
control={control}
name="address"
@@ -374,6 +401,7 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
{t('Manzilingizni kiriting')}
</AppText>
<TextInput
ref={addressRef}
style={RegisterStyle.input}
placeholder={t(
"Toshkent Shahri, Mirzo Ulug'bek tumani...",
@@ -381,15 +409,16 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
placeholderTextColor={'#D8DADC'}
onChangeText={onChange}
value={value}
returnKeyType="done"
onSubmitEditing={
() => setRecommendedDropdownVisible(true) // ❗ recommend select ochiladi
}
/>
{errors.lastName && (
<AppText style={RegisterStyle.errorText}>
{t(errors.lastName.message || '')}
</AppText>
)}
</View>
)}
/>
{/* Recommend (dropdown) */}
<Controller
control={control}
name="recommend"
@@ -446,19 +475,11 @@ 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>
)}
/>
{/* Terms */}
<View style={RegisterStyle.termsContainer}>
<TouchableOpacity
style={RegisterStyle.checkboxContainer}
@@ -504,9 +525,8 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
</View>
</TouchableOpacity>
</View>
<TouchableOpacity
onPress={handleSubmit(onSubmit)}
onPress={handleSubmit(onSubmit, onErrorSubmit)}
style={[
RegisterStyle.button,
(!termsAccepted || isPending) &&

View File

@@ -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';
@@ -21,8 +22,8 @@ import {
Dimensions,
Image,
ImageBackground,
Keyboard,
KeyboardAvoidingView,
Platform,
ScrollView,
TextInput,
TouchableOpacity,
@@ -56,12 +57,16 @@ const SecondStep = () => {
const [checkboxAnimation] = useState(new Animated.Value(1));
const [inputValue, setInputValue] = useState('');
const { firstName, lastName } = useUserStore(state => state);
const passportSeriyaRef = useRef<TextInput>(null);
const jshshirRef = useRef<TextInput>(null);
const birthDateRef = useRef<TextInput>(null);
const navigation =
useNavigation<NativeStackNavigationProp<RootStackParamList, 'Login'>>();
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');
@@ -74,10 +79,14 @@ const SecondStep = () => {
mutationFn: (payload: sendPassportPayload) =>
passportApi.sendPassport(payload),
onSuccess: res => {
navigation.navigate('Home');
navigation.reset({
index: 0,
routes: [{ name: 'Home' }],
});
},
onError: err => {
console.dir(err);
onError: (err: any) => {
setError(err?.response?.data || 'Xatolik yuz berdi');
setVisible(true);
},
});
@@ -109,6 +118,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')}`;
@@ -124,6 +144,11 @@ const SecondStep = () => {
});
};
const onErrorSubmit = (errors: any) => {
setError("Ma'lumotlarni to'liq kiriting");
setVisible(true);
};
return (
<ImageBackground
source={Logo}
@@ -144,11 +169,21 @@ const SecondStep = () => {
<LanguageSelector />
</View>
<ErrorNotification
setVisible={setVisible}
error={error}
visible={visible}
/>
<KeyboardAvoidingView
style={RegisterStyle.container}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={{ flex: 1 }}
behavior="padding"
keyboardVerticalOffset={50}
>
<ScrollView style={RegisterStyle.content}>
<ScrollView
contentContainerStyle={{ flexGrow: 1 }}
keyboardShouldPersistTaps="handled"
>
<View style={RegisterStyle.scrollContainer}>
<View style={RegisterStyle.loginContainer}>
<AppText style={RegisterStyle.title}>
@@ -170,6 +205,7 @@ const SecondStep = () => {
name="passportSeriya"
render={({ field: { onChange, value } }) => (
<TextInput
ref={passportSeriyaRef}
style={[
RegisterStyle.input,
RegisterStyle.seriyaInput,
@@ -178,12 +214,16 @@ const SecondStep = () => {
maxLength={2}
autoCapitalize="characters"
value={value}
returnKeyType="next"
onChangeText={text => {
onChange(text);
if (text.length === 2)
passportNumberRef.current?.focus();
}}
placeholderTextColor="#D8DADC"
onSubmitEditing={() =>
passportNumberRef.current?.focus()
}
/>
)}
/>
@@ -201,21 +241,17 @@ const SecondStep = () => {
maxLength={7}
keyboardType="numeric"
value={value}
returnKeyType="next"
onChangeText={text => {
const onlyNumbers = text.replace(/[^0-9]/g, '');
onChange(onlyNumbers);
}}
onSubmitEditing={() => jshshirRef.current?.focus()}
placeholderTextColor="#D8DADC"
/>
)}
/>
</View>
{(errors.passportSeriya || errors.passportNumber) && (
<AppText style={RegisterStyle.errorText}>
{t(errors.passportSeriya?.message || '') ||
t(errors.passportNumber?.message || '')}
</AppText>
)}
</View>
{/* JSHSHIR */}
<Controller
@@ -228,20 +264,18 @@ const SecondStep = () => {
</AppText>
<TextInput
style={RegisterStyle.input}
ref={jshshirRef}
placeholder="12345678901234"
placeholderTextColor="#D8DADC"
keyboardType="numeric"
maxLength={14}
returnKeyType="next"
value={value}
onChangeText={text =>
onChange(text.replace(/[^0-9]/g, ''))
}
onSubmitEditing={() => birthDateRef.current?.focus()}
/>
{errors.jshshir && (
<AppText style={RegisterStyle.errorText}>
{t(errors.jshshir.message || '')}
</AppText>
)}
</View>
)}
/>
@@ -267,10 +301,12 @@ const SecondStep = () => {
RegisterStyle.input,
{ flex: 1, borderWidth: 0 },
]}
ref={birthDateRef}
placeholder="dd/mm/yyyy"
placeholderTextColor="#D8DADC"
keyboardType="numeric"
value={value}
returnKeyType="done"
onChangeText={text => {
let cleaned = text
.replace(/[^\d]/g, '')
@@ -326,6 +362,7 @@ const SecondStep = () => {
setValue('birthDate', formatted);
}}
onSubmitEditing={handleSubmit(onSubmit)}
/>
<TouchableOpacity
onPress={() => setDatePickerVisibility(true)}
@@ -334,12 +371,6 @@ const SecondStep = () => {
<Calendar color="#D8DADC" width={24} height={24} />
</TouchableOpacity>
</View>
{errors.birthDate && (
<AppText style={RegisterStyle.errorText}>
{t(errors.birthDate?.message || '')}
</AppText>
)}
</View>
)}
/>
@@ -377,7 +408,10 @@ const SecondStep = () => {
>
<SingleFileDrop
title={t('Old tomon')}
onFileSelected={setFrontImage}
onFileSelected={file => {
setFrontImage(file);
Keyboard.dismiss();
}}
/>
</View>
<View
@@ -388,14 +422,16 @@ const SecondStep = () => {
>
<SingleFileDrop
title={t('Orqa tomon')}
onFileSelected={setBackImage}
onFileSelected={file => {
setBackImage(file);
Keyboard.dismiss();
}}
/>
</View>
</View>
{/* BUTTON */}
<TouchableOpacity
onPress={handleSubmit(onSubmit)}
onPress={handleSubmit(onSubmit, onErrorSubmit)}
style={[
RegisterStyle.button,
!termsAccepted && RegisterStyle.buttonDisabled,

View File

@@ -3,7 +3,14 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import AppText from 'components/AppText';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { ScrollView, StyleSheet, TouchableOpacity, View } from 'react-native';
import {
Linking,
ScrollView,
StyleSheet,
TouchableOpacity,
View,
} from 'react-native';
import AppLink from 'react-native-app-link';
import { SafeAreaView } from 'react-native-safe-area-context';
import ArrowLeft from 'svg/ArrowLeft';
import { RootStackParamList } from 'types/types';
@@ -16,11 +23,21 @@ type TermsScreenNavigationProp = NativeStackNavigationProp<
const TermsAndConditions = () => {
const navigation = useNavigation<TermsScreenNavigationProp>();
const { t } = useTranslation();
const handleAgree = () => {
navigation.goBack();
};
const openTelegram = React.useCallback(async () => {
try {
await AppLink.maybeOpenURL('tg://resolve?domain=cpost_admin', {
appName: 'Telegram',
appStoreId: 686449807,
appStoreLocale: 'us',
playStoreId: 'org.telegram.messenger',
});
} catch (err) {
Linking.openURL('https://t.me/cpost_admin');
}
}, []);
return (
<SafeAreaView style={styles.container}>
<View style={styles.header}>
@@ -35,16 +52,24 @@ const TermsAndConditions = () => {
<ScrollView style={styles.content} showsVerticalScrollIndicator={false}>
<AppText style={styles.title}>
{t('foydalanish_shartlari_va_qoidalari')}
{t('Foydalanish shartlari va qoidalari')}
</AppText>
<AppText style={styles.sectionTitle}>{t('umumiy_qoidalar')}</AppText>
<AppText style={styles.text}>{t('umumiy_qoidalar_text')}</AppText>
<AppText style={styles.text}>
{t(
"Biz sizdan Xitoy xalq Respublikasi Davlatidan sotib olingan tovarlarni olib kelishda quyidagi tovarlarni jonatmaslikka rozilik so'raymiz.",
)}
</AppText>
<AppText style={styles.sectionTitle}>1. {t('Umumiy qoidalar')}</AppText>
<AppText style={styles.text}>{t('Taqiqlangan tovarlar matni')}</AppText>
<AppText style={styles.text}>{t('Tovarlar')}</AppText>
<AppText style={styles.sectionTitle}>
{t('foydalanuvchi_majburiyatlari')}
</AppText>
<AppText style={styles.text}>
<AppText style={[styles.text, { textAlign: 'left' }]}>
{t('foydalanuvchi_majburiyatlari_text')}
</AppText>
@@ -63,17 +88,30 @@ const TermsAndConditions = () => {
<AppText style={styles.sectionTitle}>{t('aloqa')}</AppText>
<AppText style={styles.text}>{t('aloqa_text')}</AppText>
<TouchableOpacity
onPress={() => Linking.openURL('mailto:info@cpost.uz')}
>
<AppText style={styles.text}>{t('Email')}: info@cpost.uz</AppText>
</TouchableOpacity>
<TouchableOpacity onPress={() => Linking.openURL('tel:+998951264477')}>
<AppText style={styles.text}>
{t('Telefon')}: +998 (95) 126 44 77
</AppText>
</TouchableOpacity>
<TouchableOpacity onPress={openTelegram}>
<AppText style={styles.text}>{t('Telegram')}: @cpost_admin</AppText>
</TouchableOpacity>
<View style={styles.footer}>
<AppText style={styles.footerText}>
{t('oxirgi_yangilanish')} {new Date().toLocaleDateString('uz-UZ')}
{t('oxirgi_yangilanish')} 01/09/2025
</AppText>
</View>
</ScrollView>
<View style={styles.bottomContainer}>
<TouchableOpacity style={styles.agreeButton} onPress={handleAgree}>
<AppText style={styles.agreeButtonText}>{t('roziman')}</AppText>
<AppText style={styles.agreeButtonText}>{t('Roziman')}</AppText>
</TouchableOpacity>
</View>
</SafeAreaView>

View File

@@ -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 }}>
@@ -41,10 +55,16 @@ const SelectAuth = () => {
styles.logoImage,
{
width: 180,
height: 180,
height: 150,
marginLeft: 35,
},
]}
/>
<AppText
style={[styles.logoText, { fontSize: 52, fontWeight: '900' }]}
>
CPOST
</AppText>
{/* <AppText style={[styles.logoText, { fontSize: 32 }]}>CPOST</AppText> */}
</View>
@@ -54,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 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
onPress={() => navigation.navigate('Login')}
style={[
@@ -69,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 otganmisiz")}?
</AppText>
</View>
</View>
<View style={{ gap: 6 }}>
<TouchableOpacity
onPress={() => navigation.navigate('Register')}
style={styles.button}
>
<AppText style={styles.btnText}>
{t('Royxatdan otish')}
</AppText>
</TouchableOpacity>
<View
onPress={() => setNewRegisterModalVisible(true)}
style={{
gap: 4,
flexDirection: 'row',
@@ -104,8 +176,70 @@ const SelectAuth = () => {
<AppText style={styles.helperText}>
{t("Yangi royxatdan o'tmoqchimisiz")}?
</AppText>
</View>
</TouchableOpacity>
<TouchableOpacity
onPress={() => navigation.navigate('Register')}
style={styles.button}
>
<AppText style={styles.btnText}>
{t('Royxatdan otish')}
</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 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>
</ScrollView>
@@ -167,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,
},
});

View File

@@ -6,7 +6,14 @@ import AppText from 'components/AppText';
import LayoutTwo from 'components/LayoutTwo';
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { ScrollView, StyleSheet, TouchableOpacity, View } from 'react-native';
import {
Linking,
ScrollView,
StyleSheet,
TouchableOpacity,
View,
} from 'react-native';
import AppLink from 'react-native-app-link';
import Tabs from '../../home/ui/Tabs';
interface CargoPricesProps {}
@@ -25,6 +32,19 @@ const CargoPrices = (props: CargoPricesProps) => {
refetch();
}, [activeTab]);
const openTelegram = React.useCallback(async () => {
try {
await AppLink.maybeOpenURL('tg://resolve?domain=cpost_admin', {
appName: 'Telegram',
appStoreId: 686449807,
appStoreLocale: 'us',
playStoreId: 'org.telegram.messenger',
});
} catch (err) {
Linking.openURL('https://t.me/cpost_admin');
}
}, []);
return (
<LayoutTwo title={t('Kargo narxlari')}>
<ScrollView style={{ flex: 1 }}>
@@ -34,7 +54,7 @@ const CargoPrices = (props: CargoPricesProps) => {
<View style={{ marginTop: 10, gap: 10, marginBottom: 20 }}>
{data &&
data.map(ref => (
<View style={styles.cardWhite}>
<View style={styles.cardWhite} key={ref.id}>
<View style={styles.priceCard}>
<AppText style={styles.titleBlack}>{ref.title}</AppText>
<AppText style={[styles.titleBlack, { fontSize: 16 }]}>
@@ -105,7 +125,10 @@ const CargoPrices = (props: CargoPricesProps) => {
</AppText>
</View>
<AppText style={[styles.desc, { color: '#000000' }]}>
{t('Batafsil')}: @CPcargo_admin
{t('Batafsil')}:{' '}
<AppText style={{ color: '#28A7E8' }} onPress={openTelegram}>
@cpost_admin
</AppText>
</AppText>
</View>
<View style={[styles.card]}>
@@ -129,7 +152,7 @@ const CargoPrices = (props: CargoPricesProps) => {
<View style={{ marginTop: 20, gap: 10, marginBottom: 20 }}>
{data &&
data.map(ref => (
<View style={styles.cardWhite}>
<View style={styles.cardWhite} key={ref.id}>
<View style={styles.priceCard}>
<AppText style={styles.titleBlack}>{ref.title}</AppText>
<AppText style={[styles.titleBlack, { fontSize: 16 }]}>
@@ -193,7 +216,10 @@ const CargoPrices = (props: CargoPricesProps) => {
</AppText>
</View>
<AppText style={[styles.desc, { color: '#000000' }]}>
{t('Batafsil')}: @CPcargo_admin
{t('Batafsil')}:{' '}
<AppText style={{ color: '#28A7E8' }} onPress={openTelegram}>
@cpost_admin
</AppText>
</AppText>
</View>
<View style={[styles.card]}>

View File

@@ -75,7 +75,11 @@ const Home = () => {
removeClippedSubviews={true}
keyboardShouldPersistTaps="handled"
>
<PartyCarousel autoData={autoData} aviaData={aviaData} />
<PartyCarousel
autoData={autoData}
aviaData={aviaData}
activeTab={activeTab}
/>
<Tabs setActiveTab={setActiveTab} activeTab={activeTab} />
{activeTabContent}
<Pages />

View File

@@ -1,6 +1,6 @@
import AnimatedIcon from 'components/AnimatedIcon';
import AppText from 'components/AppText';
import React, { useMemo, useRef, useState } from 'react';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
FlatList,
@@ -10,6 +10,11 @@ import {
useWindowDimensions,
View,
} from 'react-native';
import Animated, {
useAnimatedStyle,
useSharedValue,
withTiming,
} from 'react-native-reanimated';
import Auto from 'svg/Auto';
import Avia from 'svg/Avia';
import BoxCreate from 'svg/BoxCreate';
@@ -18,16 +23,35 @@ import { HomeStyle } from './styled';
const PartyCarousel = ({
aviaData,
activeTab,
autoData,
}: {
autoData: any;
aviaData: any;
activeTab: 'AUTO' | 'AVIA';
}) => {
const { width: screenWidth } = useWindowDimensions();
const cardWidth = screenWidth * 0.95;
const styles = useMemo(() => HomeStyle(), []);
const { t } = useTranslation();
// animatsiya
const opacity = useSharedValue(0);
const translateY = useSharedValue(20);
useEffect(() => {
opacity.value = 1;
translateY.value = 0;
opacity.value = withTiming(1, { duration: 500 });
translateY.value = withTiming(0, { duration: 500 });
}, [activeTab]);
const animatedStyle = useAnimatedStyle(() => ({
opacity: opacity.value,
transform: [{ translateY: translateY.value }],
}));
// status config
const statusConfig: any = {
COLLECTING: {
backgroundColor: '#28A7E8',
@@ -56,7 +80,7 @@ const PartyCarousel = ({
},
};
// calendarList tayyorlash
// calendarList faqat activeTab uchun
const calendarList = useMemo(() => {
const data: any[] = [];
const weekdays = [
@@ -89,10 +113,14 @@ const PartyCarousel = ({
});
};
prepareList(autoData, 'auto');
prepareList(aviaData, 'avia');
if (activeTab === 'AUTO') {
prepareList(autoData, 'auto');
} else if (activeTab === 'AVIA') {
prepareList(aviaData, 'avia');
}
return data;
}, [autoData, aviaData]);
}, [autoData, aviaData, activeTab]);
const flatListRef = useRef<FlatList>(null);
const today = useMemo(() => {
@@ -102,6 +130,7 @@ const PartyCarousel = ({
}, []);
const [selectedItem, setSelectedItem] = useState<any>(null);
const [modalVisible, setModalVisible] = useState(false);
const renderItem = ({ item, index }: { item: any; index: number }) => {
const isLast = index === calendarList.length - 1;
return (
@@ -171,7 +200,7 @@ const PartyCarousel = ({
{item.cargo.toUpperCase()}
</AppText>
<View style={styles.animatedIconWrapper}>
<AnimatedIcon type={item.cargo} />
<AnimatedIcon type={activeTab} />
</View>
</View>
</>
@@ -181,9 +210,8 @@ const PartyCarousel = ({
};
return (
<>
<Animated.View style={animatedStyle}>
<FlatList
ref={flatListRef}
data={calendarList}
keyExtractor={(_, index) => index.toString()}
renderItem={renderItem}
@@ -233,7 +261,7 @@ const PartyCarousel = ({
</View>
</View>
</Modal>
</>
</Animated.View>
);
};

View File

@@ -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}

View File

@@ -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';
@@ -12,7 +13,6 @@ import {
ActivityIndicator,
Dimensions,
KeyboardAvoidingView,
Platform,
ScrollView,
TextInput,
TouchableOpacity,
@@ -39,22 +39,38 @@ 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: '',
// passportSeriya: '',
// passportNumber: '',
// jshshir: '',
const firstNameRef = useRef<TextInput>(null);
const lastNameRef = useRef<TextInput>(null);
const birthDateRef = useRef<TextInput>(null);
const passportSeriyaRef = useRef<TextInput>(null);
const jshshirRef = useRef<TextInput>(null);
const { mutate, isPending } = useMutation({
mutationFn: (payload: AddPassportPayload) => {
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',
);
},
});
@@ -90,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')}`;
@@ -104,17 +142,40 @@ 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="Yangi pasport qo'shish">
<LayoutTwo title={t("Yangi pasport qo'shish")}>
<KeyboardAvoidingView
style={PassportStyle.container}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
behavior={'padding'}
keyboardVerticalOffset={30}
>
<ScrollView
keyboardShouldPersistTaps="handled"
showsVerticalScrollIndicator={false}
style={PassportStyle.content}
>
<View style={PassportStyle.scrollContainer}>
<ErrorNotification
setVisible={setVisible}
error={error}
visible={visible}
/>
<View style={PassportStyle.loginContainer}>
<Controller
control={control}
@@ -123,17 +184,15 @@ const CreatePassword = () => {
<View>
<AppText style={PassportStyle.label}>{t('Ism')}</AppText>
<TextInput
ref={firstNameRef}
returnKeyType="next"
onSubmitEditing={() => lastNameRef.current?.focus()}
style={PassportStyle.input}
placeholder={t('Ismingiz')}
onChangeText={onChange}
value={value}
placeholderTextColor={'#D8DADC'}
/>
{errors.firstName && (
<AppText style={PassportStyle.errorText}>
{t(errors.firstName.message || '')}
</AppText>
)}
</View>
)}
/>
@@ -146,17 +205,15 @@ const CreatePassword = () => {
{t('Familiya')}
</AppText>
<TextInput
ref={lastNameRef}
returnKeyType="next"
onSubmitEditing={() => passportSeriyaRef.current?.focus()}
style={PassportStyle.input}
placeholder={t('Familiyangiz')}
placeholderTextColor={'#D8DADC'}
onChangeText={onChange}
value={value}
/>
{errors.lastName && (
<AppText style={PassportStyle.errorText}>
{t(errors.lastName.message || '')}
</AppText>
)}
</View>
)}
/>
@@ -170,6 +227,11 @@ const CreatePassword = () => {
name="passportSeriya"
render={({ field: { onChange, value } }) => (
<TextInput
ref={passportSeriyaRef}
returnKeyType="next"
onSubmitEditing={() =>
passportNumberRef.current?.focus()
}
style={[PassportStyle.input, PassportStyle.seriyaInput]}
placeholder="AA"
maxLength={2}
@@ -191,6 +253,8 @@ const CreatePassword = () => {
render={({ field: { onChange, value } }) => (
<TextInput
ref={passportNumberRef}
returnKeyType="next"
onSubmitEditing={() => jshshirRef.current?.focus()}
style={[PassportStyle.input, PassportStyle.raqamInput]}
placeholder="1234567"
maxLength={7}
@@ -205,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>
@@ -220,6 +278,9 @@ const CreatePassword = () => {
render={({ field: { onChange, value } }) => (
<TextInput
style={PassportStyle.input}
ref={jshshirRef}
returnKeyType="next"
onSubmitEditing={() => birthDateRef.current?.focus()}
placeholder="12345678901234"
placeholderTextColor={'#D8DADC'}
keyboardType="numeric"
@@ -232,11 +293,6 @@ const CreatePassword = () => {
/>
)}
/>
{errors.jshshir && (
<AppText style={PassportStyle.errorText}>
{t(errors.jshshir.message || '')}
</AppText>
)}
</View>
<View>
<Controller
@@ -250,6 +306,8 @@ const CreatePassword = () => {
<View style={[PassportStyle.inputContainer]}>
<TextInput
ref={birthDateRef}
returnKeyType="done"
style={[
PassportStyle.input,
{ flex: 1, borderWidth: 0, padding: 0 },
@@ -315,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>
)}
/>
@@ -375,7 +427,7 @@ const CreatePassword = () => {
</View>
</View>
<TouchableOpacity
onPress={handleSubmit(onSubmit)}
onPress={handleSubmit(onSubmit, onErrorSubmit)}
style={PassportStyle.button}
>
{isPending ? (
@@ -391,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>
);

View File

@@ -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,

View File

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

View File

@@ -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 () => {

View File

@@ -7,14 +7,12 @@ import { useTranslation } from 'react-i18next';
import {
Alert,
Linking,
Platform,
StyleSheet,
TouchableOpacity,
View,
} from 'react-native';
import AppLink from 'react-native-app-link';
import ArrowRightUnderline from 'svg/ArrowRightUnderline';
import Bell from 'svg/Bell';
import Location from 'svg/Location';
import Logout from 'svg/LogOut';
import Setting from 'svg/Setting';
@@ -53,15 +51,14 @@ const ProfilePages = (props: componentNameProps) => {
const openTelegram = React.useCallback(async () => {
try {
await AppLink.maybeOpenURL('tg://resolve?domain=cpostuz', {
await AppLink.maybeOpenURL('tg://resolve?domain=cpost_admin', {
appName: 'Telegram',
appStoreId: 686449807,
appStoreLocale: 'us',
playStoreId: 'org.telegram.messenger',
});
} catch (err) {
// Agar ilovani ham, storeni ham ochib bolmasa, fallback URL
Linking.openURL('https://t.me/cpostuz');
Linking.openURL('https://t.me/cpost_admin');
}
}, []);
@@ -80,7 +77,7 @@ const ProfilePages = (props: componentNameProps) => {
</View>
<ArrowRightUnderline color="#373737" width={24} height={24} />
</TouchableOpacity>
{Platform.OS === 'android' && (
{/* {Platform.OS === 'android' && (
<TouchableOpacity
style={[
styles.card,
@@ -94,7 +91,7 @@ const ProfilePages = (props: componentNameProps) => {
</View>
<ArrowRightUnderline color="#373737" width={24} height={24} />
</TouchableOpacity>
)}
)} */}
<TouchableOpacity
style={[
styles.card,

View File

@@ -75,7 +75,7 @@ const TabsAutoWarehouses = () => {
};
const { data } = useQuery({
queryKey: ['warhouses'],
queryKey: ['warhouses_auto'],
queryFn: () => warhouses_api.getWarhouses({ cargoType: 'AUTO' }),
});

View File

@@ -75,7 +75,7 @@ const TabsAviaWarehouses = () => {
};
const { data } = useQuery({
queryKey: ['warhouses'],
queryKey: ['warhouses_avia'],
queryFn: () => warhouses_api.getWarhouses({ cargoType: 'AVIA' }),
});
@@ -108,7 +108,7 @@ const TabsAviaWarehouses = () => {
<View style={[styles.card, { marginRight: isLast ? 0 : 10 }]}>
<View style={styles.titleCard}>
<Kitay width={24 * scale} height={24 * scale} />
<AppText style={styles.title}>China (Auto)</AppText>
<AppText style={styles.title}>China (AVIA)</AppText>
</View>
<View style={styles.infoId}>
<View style={{ gap: 4 * scale, width: '90%' }}>

View File

@@ -100,15 +100,14 @@ Cargo Idsi: ${getMe?.aviaCargoId}
);
const openTelegram = React.useCallback(async () => {
try {
await AppLink.maybeOpenURL('tg://resolve?domain=cpostuz', {
await AppLink.maybeOpenURL('tg://resolve?domain=cpost_admin', {
appName: 'Telegram',
appStoreId: 686449807,
appStoreLocale: 'us',
playStoreId: 'org.telegram.messenger',
});
} catch (err) {
// Agar ilovani ham, storeni ham ochib bolmasa, fallback URL
Linking.openURL('https://t.me/cpostuz');
Linking.openURL('https://t.me/cpost_admin');
}
}, []);
return (

View File

@@ -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>
)}

View File

@@ -1,15 +1,15 @@
import { PacketsData } from 'api/packets';
import AppText from 'components/AppText';
import { formatPrice } from 'helpers/formatPrice';
import React, { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { FlatList, StyleSheet, TouchableOpacity, View } from 'react-native';
import Auto from 'svg/Auto';
import Avia from 'svg/Avia';
import BagIcon from 'svg/BagIcon';
import BoxIcon from 'svg/Box';
import Store from 'svg/Store';
import SuccessIcon from 'svg/SuccessIcon';
import TrunkIcon from 'svg/TrunkIcon';
import Wallet from 'svg/Wallet';
import { DataInfo } from '../lib/data';
const statusColorMap: Record<string, string> = {
@@ -30,13 +30,14 @@ const statuses = [
'DELIVERED',
'PAID',
] as const;
type FilterType = (typeof statuses)[number];
const tabList: { label: string; value: FilterType }[] = [
{ label: "Yig'ilmoqda", value: 'COLLECTING' },
{ label: "Yo'lda", value: 'ON_THE_WAY' },
{ label: 'Bojxonada', value: 'IN_CUSTOMS' },
{ label: 'Toshkent omboriga yetib keldi', value: 'IN_WAREHOUSE' },
{ label: 'Omborga yetib keldi', value: 'IN_WAREHOUSE' },
{ label: 'Topshirish punktida', value: 'DELIVERED' },
{ label: 'Qabul qilingan', value: 'PAID' },
];
@@ -51,20 +52,18 @@ const Order = ({ data, openModal, selectedData }: Props) => {
const { t } = useTranslation();
const styles = useMemo(() => makeStyles(), []);
const createIcons = useCallback(
(cargo: string) => [
/** 🔥 SUCCESS ICON faqat paymentStatus ga qarab ozgaradi */
const createIcons = useCallback((cargo: string, paymentStatus?: string) => {
const isPaid = paymentStatus === 'PAYED';
return [
{
status: 'COLLECTING',
icon: <BoxIcon width={24} height={24} color="" />,
},
{
status: 'ON_THE_WAY',
icon:
cargo === 'avia' ? (
<Avia width={24} height={24} color="" />
) : (
<Auto width={24} height={24} color="" view="-4" />
),
icon: <Avia width={24} height={24} color="" />,
},
{
status: 'IN_CUSTOMS',
@@ -79,29 +78,33 @@ const Order = ({ data, openModal, selectedData }: Props) => {
icon: <TrunkIcon width={24} height={24} color="" />,
},
{
status: 'PAID',
icon: <SuccessIcon width={24} height={24} color="" />,
status: 'PENDING',
icon: (
<Wallet
width={24}
height={24}
color={isPaid ? '#28A7E8' : '#C0C0C0'}
/>
),
},
],
[],
);
];
}, []);
const handleItemPress = useCallback(
(item: DataInfo) => {
openModal(item);
},
(item: DataInfo) => openModal(item),
[openModal],
);
const renderOrderItem = useCallback(
({ item, index }: { item: DataInfo; index: number }) => {
({ item }: { item: DataInfo }) => {
const currentStatusIndex = statuses.indexOf(
item.deliveryStatus as FilterType,
);
const icons = createIcons(item.deliveryStatus);
const icons = createIcons(item.deliveryStatus, item.paymentStatus);
return (
<TouchableOpacity key={index} onPress={() => handleItemPress(item)}>
<TouchableOpacity onPress={() => handleItemPress(item)}>
<View style={styles.card}>
<FlatList
data={icons}
@@ -109,14 +112,42 @@ const Order = ({ data, openModal, selectedData }: Props) => {
numColumns={6}
contentContainerStyle={styles.statusCard}
renderItem={({ item: iconItem, index: i }) => {
const iconColor = i <= currentStatusIndex ? '#28A7E8' : '#000';
const viewColor =
i <= currentStatusIndex ? '#28A7E81A' : '#0000001A';
let iconColor = '#000';
let bgColor = '#0000001A';
if (
iconItem.status === 'DELIVERED' &&
item.deliveryStatus === 'DELIVERED'
) {
iconColor = '#28A7E8';
bgColor = '#28A7E81A';
} else if (iconItem.status === 'PENDING') {
if (item.paymentStatus === 'PAYED') {
iconColor = '#28A7E8'; // blue
bgColor = '#28A7E81A';
} else if (item.paymentStatus === 'PENDING') {
iconColor = '#FFA500'; // orange
bgColor = '#FFA5001A';
} else {
iconColor = '#C0C0C0'; // gray
bgColor = '#C0C0C01A';
}
} else if (i <= currentStatusIndex) {
iconColor = '#28A7E8'; // blue
bgColor = '#28A7E81A';
}
return (
<View style={styles.iconWrapper}>
<View
style={[styles.circle, { backgroundColor: viewColor }]}
style={[
styles.circle,
{
backgroundColor: bgColor,
justifyContent: 'center',
alignItems: 'center',
},
]}
>
{React.cloneElement(iconItem.icon, { color: iconColor })}
</View>
@@ -133,29 +164,62 @@ const Order = ({ data, openModal, selectedData }: Props) => {
}}
/>
{/* Status Label */}
<View
style={[
styles.statusLabelWrapper,
{
backgroundColor: `${statusColorMap[item.deliveryStatus]}1A`,
flexDirection: 'row',
justifyContent: 'space-between',
width: '100%',
},
]}
>
<AppText
style={[
styles.statusText,
{ color: statusColorMap[item.deliveryStatus] },
styles.statusLabelWrapper,
{
color: statusColorMap[item.deliveryStatus],
backgroundColor: `${statusColorMap[item.deliveryStatus]}1A`,
},
]}
>
{t(
tabList.find(tab => tab.value === item.deliveryStatus)
?.label || '',
tabList.find(el => el.value === item.deliveryStatus)?.label ||
'',
)}
</AppText>
<AppText
style={[
styles.statusText,
styles.statusLabelWrapper,
{
color:
item.paymentStatus === 'NEW'
? '#D32F2F'
: item.paymentStatus === 'PENDING'
? '#FFA500'
: item.paymentStatus === 'PAYED'
? '#32CD32'
: '#D32F2F',
backgroundColor:
item.paymentStatus === 'NEW'
? '#D32F2F1A'
: item.paymentStatus === 'PENDING'
? '#FFA5001A'
: item.paymentStatus === 'PAYED'
? '#32CD321A'
: '#D32F2F',
},
]}
>
{item.paymentStatus === 'NEW'
? t("To'lanmagan")
: item.paymentStatus === 'PENDING'
? t("To'lov kutilmoqda")
: item.paymentStatus === 'PAYED' && t("To'langan")}
</AppText>
</View>
{/* Info */}
<View style={styles.infoCard}>
<AppText style={styles.infoTitle}>{t('Reys raqami')}</AppText>
<AppText
@@ -164,16 +228,70 @@ const Order = ({ data, openModal, selectedData }: Props) => {
{item.packetName}
</AppText>
</View>
{item.items.map(e => (
<View
key={e.trekId}
style={{
...styles.infoCard,
flexDirection: 'column',
gap: 4,
marginTop: 4,
}}
>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
gap: 4,
}}
>
<AppText style={styles.infoTitle}>
{t('Mahsulot nomi')}
</AppText>
<AppText style={styles.infoText}>{e.name}</AppText>
</View>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
gap: 4,
}}
>
<AppText style={styles.infoTitle}>{t('Trek ID')}</AppText>
<AppText style={styles.infoText}>{e.trekId}</AppText>
</View>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
gap: 4,
}}
>
<AppText style={styles.infoTitle}>{t('Ogirligi')}</AppText>
<AppText style={styles.infoText}>{e.weight} kg</AppText>
</View>
</View>
))}
<View style={styles.infoCard}>
<AppText style={styles.infoTitle}>
{t('Mahsulotlar ogirligi')}
</AppText>
<AppText style={styles.infoText}>{item.weight}</AppText>
</View>
<View style={styles.infoCard}>
<AppText style={styles.infoTitle}>{t('Umumiy narxi')}</AppText>
<AppText style={styles.infoText}>{item.totalPrice}</AppText>
<AppText style={styles.infoText}>{item.weight} kg</AppText>
</View>
{(item.deliveryStatus === 'DELIVERED' ||
item.paymentStatus === 'PAID') && (
<View style={styles.infoCard}>
<AppText style={styles.infoTitle}>{t('Umumiy narxi')}</AppText>
<AppText style={styles.infoText}>
{formatPrice(item.totalPrice)} {t('som')}
</AppText>
</View>
)}
</View>
</TouchableOpacity>
);
@@ -207,49 +325,37 @@ const Order = ({ data, openModal, selectedData }: Props) => {
const makeStyles = () =>
StyleSheet.create({
container: {
width: '95%',
margin: 'auto',
borderRadius: 10,
},
container: { width: '95%', margin: 'auto', borderRadius: 10 },
count: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginTop: 10,
},
title: {
fontSize: 16,
fontWeight: '500',
color: '#333',
},
title: { fontSize: 16, fontWeight: '500', color: '#333' },
card: {
backgroundColor: '#fff',
margin: 1,
marginTop: 8,
padding: 15,
borderRadius: 10,
// iOS
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.2,
shadowRadius: 2,
// Android
elevation: 2,
},
statusCard: {
marginBottom: 10,
},
statusCard: { marginBottom: 10 },
circle: {
padding: 8,
borderRadius: 50,
alignItems: 'center',
justifyContent: 'center',
},
divider: {
width: 20,
borderBottomWidth: 2,
borderStyle: 'dashed',
divider: { width: 20, borderBottomWidth: 2, borderStyle: 'dashed' },
iconWrapper: {
flexDirection: 'row',
alignItems: 'center',
flex: 1,
marginBottom: 10,
},
statusLabelWrapper: {
borderRadius: 8,
@@ -258,31 +364,14 @@ const makeStyles = () =>
paddingHorizontal: 10,
marginBottom: 12,
},
statusText: {
fontFamily: 'GolosText-Bold',
fontSize: 16,
},
statusText: { fontFamily: 'GolosText-Bold', fontSize: 16 },
infoCard: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 6,
},
iconWrapper: {
flexDirection: 'row',
alignItems: 'center',
flex: 1,
marginBottom: 10,
},
infoTitle: {
fontSize: 16,
color: '#979797',
fontWeight: '500',
},
infoText: {
fontSize: 14,
color: '#28A7E8',
fontWeight: '500',
},
infoTitle: { fontSize: 16, color: '#979797', fontWeight: '500' },
infoText: { fontSize: 14, color: '#28A7E8', fontWeight: '500' },
});
export default Order;

View File

@@ -1,4 +1,5 @@
import AppText from 'components/AppText';
import { formatPrice } from 'helpers/formatPrice';
import React, { useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import {
@@ -22,14 +23,6 @@ const OrderDetailModal = ({ visible, setVisible, selectedOrder }: Props) => {
const translateY = useRef(new Animated.Value(50)).current;
const { t } = useTranslation();
const parsePrice = (priceStr: string) => {
return Number(priceStr.replace(/[^\d]/g, ''));
};
const parseWeight = (weightStr: string) => {
return Number(weightStr.replace(/[^\d.]/g, ''));
};
const closeModal = () => {
Animated.timing(opacity, {
toValue: 0,
@@ -82,10 +75,6 @@ const OrderDetailModal = ({ visible, setVisible, selectedOrder }: Props) => {
style={{ maxHeight: 250 }}
>
{selectedOrder.items.map((product, index) => {
const totalPrice = product.totalPrice;
const weight = product.weight;
const pricePerKg = Math.ceil(weight ? totalPrice / weight : 0);
return (
<View key={product.trekId + index} style={styles.productItem}>
<View style={styles.row}>
@@ -100,13 +89,12 @@ const OrderDetailModal = ({ visible, setVisible, selectedOrder }: Props) => {
{t('Ogirligi')}: {product.weight}
</AppText>
<AppText style={styles.detail}>
{t('Narxi')}: 1kg * {pricePerKg.toLocaleString('uz-UZ')}{' '}
{t("so'm")}
{t('Narxi')}: {product.price}$
</AppText>
</View>
<View style={styles.rowRight}>
<AppText style={styles.total}>
{t('Umumiy narxi')}: {product.totalPrice} {t('som')}
{t('Umumiy narxi')}: {product.totalPrice}$
</AppText>
</View>
</View>
@@ -117,7 +105,7 @@ const OrderDetailModal = ({ visible, setVisible, selectedOrder }: Props) => {
<View style={styles.totalRow}>
<AppText style={styles.totalLabel}>{t('Umumiy narx')}:</AppText>
<AppText style={styles.totalValue}>
{selectedOrder.totalPrice}
{formatPrice(selectedOrder.totalPrice)} {t('som')}
</AppText>
</View>

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

@@ -14,16 +14,15 @@ import {
View,
useWindowDimensions,
} from 'react-native';
import Serach from 'svg/Serach';
import { DataInfo } from '../lib/data';
import Filter from './Filter';
import Order from './Order';
import OrderDetailModal from './OrderDetailModal';
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 +37,7 @@ const Status = () => {
const [transportTypes, setTransportTypes] = useState<
// 'all'|
'AUTO' | 'AVIA'
>('AUTO');
>('AVIA');
const [page, setPage] = useState(0);
const {
data: statusData,
@@ -46,12 +45,14 @@ const Status = () => {
isLoading,
isFetching,
} = useQuery({
queryKey: ['status', filter, transportTypes, page],
queryKey: ['status', transportTypes, page, paymentStatus],
queryFn: () =>
packetsApi.getPacketsStatus(filter, {
packetsApi.getPackets({
page,
size: 10,
cargoType: transportTypes,
sort: 'id',
direction: 'DESC',
}),
});
@@ -131,11 +132,6 @@ const Status = () => {
[refreshing, isFetching, onRefresh],
);
const searchIcon = useMemo(
() => <Serach color="#D8DADC" width={20 * scale} height={20 * scale} />,
[scale],
);
if (isLoading || isFetching) {
return (
<Layout>
@@ -144,20 +140,30 @@ const Status = () => {
);
}
if (statusData?.data.length === 0) {
if (statusData?.data.length === 0 || statusData === undefined) {
return (
<Layout>
<Tabs filter={filter} setFilter={setFilter} />
{/* <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")} />
@@ -172,7 +178,7 @@ const Status = () => {
refreshControl={refreshControl}
removeClippedSubviews={true}
>
<Tabs filter={filter} setFilter={setFilter} />
{/* <Tabs filter={filter} setFilter={setFilter} /> */}
<View style={styles.controls}>
{/* <View style={styles.searchContainer}>
<TextInput
@@ -182,7 +188,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 +203,10 @@ const Status = () => {
data={statusData!}
setSelectedData={setSelectedData}
/>
{/* <PaymentFilter
paymentStatus={paymentStatus}
setPaymentStatus={setPaymentStatus}
/> */}
</View>
</View>
<Order

View File

@@ -4,7 +4,7 @@ import { PacketsData } from 'api/packets';
import AppText from 'components/AppText';
import React, { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { TouchableOpacity, View } from 'react-native';
import { StyleSheet, TouchableOpacity, View } from 'react-native';
import { PaymentStyle } from './style';
type WalletStackParamList = {
@@ -22,6 +22,7 @@ interface Props {
const Payment = ({ packets }: Props) => {
const navigation = useNavigation<LoginScreenNavigationProp>();
const { t } = useTranslation();
const styles = useMemo(() => makeStyles(), []);
const handlePaymentPress = useCallback(
(item: any) => {
@@ -52,10 +53,17 @@ const Payment = ({ packets }: Props) => {
const renderPaymentCard = useCallback(
(item: any) => {
const isPaid =
item.paymentStatus === 'PAYED' || item.paymentStatus === 'PENDING';
item.paymentStatus === 'PAYED' || item.paymentStatus === 'PAID';
const cardStyle = [
PaymentStyle.card,
{ borderColor: isPaid ? '#4CAF50' : '#D32F2F', borderWidth: 1.5 },
{
borderColor: isPaid
? '#4CAF50'
: item.paymentStatus === 'PENDING'
? '#FFA500'
: '#D32F2F',
borderWidth: 1.5,
},
];
return (
@@ -70,6 +78,20 @@ const Payment = ({ packets }: Props) => {
<AppText style={PaymentStyle.title}>{item.packetName}</AppText>
{isPaid ? (
<AppText style={PaymentStyle.badge}>{t("To'langan")}</AppText>
) : item.paymentStatus === 'PENDING' ? (
<AppText
style={{
backgroundColor: '#FFA500',
color: '#fff',
fontSize: 12,
paddingHorizontal: 8,
paddingVertical: 2,
borderRadius: 5,
overflow: 'hidden',
}}
>
{t('Kutilmoqda')}
</AppText>
) : (
<AppText style={badgeStyle}>{t("To'lanmagan")}</AppText>
)}
@@ -87,7 +109,53 @@ const Payment = ({ packets }: Props) => {
{item.packetName}
</AppText>
</View>
<View style={PaymentStyle.row}>
{item.items.map((e: any) => (
<View
key={e.trekId}
style={{
...styles.infoCard,
flexDirection: 'column',
gap: 4,
}}
>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
gap: 4,
}}
>
<AppText style={styles.infoTitle}>
{t('Mahsulot nomi')}
</AppText>
<AppText style={styles.infoText}>{e.name}</AppText>
</View>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
gap: 4,
}}
>
<AppText style={styles.infoTitle}>{t('Trek ID')}</AppText>
<AppText style={styles.infoText}>{e.trekId}</AppText>
</View>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
gap: 4,
}}
>
<AppText style={styles.infoTitle}>{t('Ogirligi')}</AppText>
<AppText style={styles.infoText}>{e.weight} kg</AppText>
</View>
</View>
))}
{/* <View style={PaymentStyle.row}>
<AppText style={PaymentStyle.infoTitle}>
{t("Mahsulotlar og'irligi")}
</AppText>
@@ -104,7 +172,7 @@ const Payment = ({ packets }: Props) => {
{t('Umumiy narxi')}
</AppText>
<AppText style={PaymentStyle.text}>{item.totalPrice}</AppText>
</View>
</View> */}
</View>
</View>
</TouchableOpacity>
@@ -120,4 +188,59 @@ const Payment = ({ packets }: Props) => {
);
};
const makeStyles = () =>
StyleSheet.create({
container: { width: '95%', margin: 'auto', borderRadius: 10 },
count: {
flexDirection: 'row',
justifyContent: 'space-between',
marginTop: 10,
},
title: { fontSize: 16, fontWeight: '500', color: '#333' },
card: {
backgroundColor: '#fff',
marginTop: 8,
padding: 15,
borderRadius: 10,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.2,
shadowRadius: 2,
elevation: 2,
},
statusCard: { marginBottom: 10 },
circle: {
padding: 8,
borderRadius: 50,
alignItems: 'center',
justifyContent: 'center',
},
divider: { width: 20, borderBottomWidth: 2, borderStyle: 'dashed' },
iconWrapper: {
flexDirection: 'row',
alignItems: 'center',
flex: 1,
marginBottom: 10,
},
statusLabelWrapper: {
borderRadius: 8,
alignSelf: 'flex-start',
paddingVertical: 6,
paddingHorizontal: 10,
marginBottom: 12,
},
statusText: { fontFamily: 'GolosText-Bold', fontSize: 16 },
infoCard: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 6,
},
infoTitle: { fontSize: 16, color: '#979797', fontWeight: '500' },
infoText: { fontSize: 14, color: '#28A7E8', fontWeight: '500' },
});
export default Payment;

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 AppText from 'components/AppText';
import React, { useEffect, useRef } from 'react';
@@ -52,6 +53,10 @@ const ModalPay = ({
const { bottom } = useSafeAreaInsets();
const [load, setLoad] = React.useState(false);
const { t } = useTranslation();
const { data: getMe } = useQuery({
queryKey: ['getMe'],
queryFn: authApi.getMe,
});
const { mutate, isPending } = useMutation({
mutationFn: ({ id, payType }: { id: number; payType: string }) =>
@@ -137,45 +142,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={[

View File

@@ -1,9 +1,10 @@
import { useQuery } from '@tanstack/react-query';
import exchanges_api from 'api/exchanges';
import AppText from 'components/AppText';
import { formatPrice } from 'helpers/formatPrice';
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { Dimensions, ScrollView, View } from 'react-native';
import { Dimensions, ScrollView, StyleSheet, View } from 'react-native';
import Svg, { Circle, Path } from 'react-native-svg';
import Plane from 'svg/Plane';
import { PaymentStyle } from '../../payment/ui/style';
@@ -17,6 +18,7 @@ const PaymentProduct = ({ packet }: PaymentProductProps) => {
const screenWidth = Dimensions.get('window').width;
const isSmallScreen = screenWidth < 380;
const svgWidth = screenWidth * 0.8;
const svgWidthProduct = screenWidth * 1;
const { data } = useQuery({
queryKey: ['exchanges'],
@@ -80,21 +82,23 @@ const PaymentProduct = ({ packet }: PaymentProductProps) => {
: { flexBasis: '48%', alignItems: 'flex-end' },
]}
>
<AppText style={PaymentStyle.titleMethod}>Cargo</AppText>
<AppText style={PaymentStyle.textMethod}>12.267 UZS/ kg</AppText>
<AppText style={PaymentStyle.titleMethod}>{t('Reys')}</AppText>
<AppText style={[PaymentStyle.textMethod, { textAlign: 'left' }]}>
{packet.packetName}
</AppText>
</View>
{/* <View
style={[
PaymentStyle.info,
isSmallScreen
? { flexBasis: '48%', alignItems: 'flex-start' }
: { flexBasis: '48%', alignItems: 'flex-start' },
? { flexBasis: '48%', alignItems: 'flex-end' }
: { flexBasis: '48%', alignItems: 'flex-end' },
]}
>
<AppText style={PaymentStyle.titleMethod}>{t('Yetkazish vaqti')}</AppText>
<AppText style={PaymentStyle.textMethod}>08.25.2025</AppText>
</View> */}
<AppText style={PaymentStyle.titleMethod}>Cargo</AppText>
<AppText style={PaymentStyle.textMethod}>12.267 UZS/ kg</AppText>
</View>
<View
style={[
@@ -104,11 +108,11 @@ const PaymentProduct = ({ packet }: PaymentProductProps) => {
: { flexBasis: '48%', alignItems: 'flex-start' },
]}
>
<AppText style={PaymentStyle.titleMethod}>Reys</AppText>
<AppText style={[PaymentStyle.textMethod, { textAlign: 'left' }]}>
{packet.packetName}
<AppText style={PaymentStyle.titleMethod}>
{t('Yetkazish vaqti')}
</AppText>
</View>
<AppText style={PaymentStyle.textMethod}>08.25.2025</AppText>
</View> */}
</View>
<View
style={{
@@ -149,36 +153,40 @@ const PaymentProduct = ({ packet }: PaymentProductProps) => {
/>
</View>
{packet.items.map((item: any, index: number) => {
const price = Number(item.price);
const weight = Number(item.weight);
const total = price * weight;
// formatlash: 0 decimal, som bilan
const formattedPrice = price.toFixed(0);
const formattedTotal = total.toFixed(0);
return (
<View key={index} style={{ marginBottom: 15 }}>
<View style={PaymentStyle.receiptCard}>
<View style={PaymentStyle.rowBetween}>
<AppText style={PaymentStyle.itemName}>{item.name}</AppText>
<AppText style={PaymentStyle.track}>
{t('Trek ID')}: {item.trackId}
</AppText>
<View style={{ width: '100%' }}>
<AppText style={PaymentStyle.itemName}>{item.name}</AppText>
<AppText style={PaymentStyle.track}>
{t('Trek ID')}: {item.trekId}
</AppText>
</View>
</View>
<View style={PaymentStyle.rowBetween}>
<AppText style={PaymentStyle.weight}>
{t('Ogirligi')}: {weight} kg
{t('Ogirligi')}: {item.weight} kg
</AppText>
<AppText style={PaymentStyle.price}>
1kg * {formattedPrice} {t('som')}
{t('Narxi')}: {item.price}$
</AppText>
</View>
<View style={PaymentStyle.rowRight}>
<AppText style={PaymentStyle.total}>
{t('Umumiy narxi')}: {formattedTotal} {t('som')}
{t('Umumiy narxi')}: {item.totalPrice} $
</AppText>
</View>
</View>
<Svg height="1" width={svgWidthProduct}>
<Path
d={`M 0 0 Q ${svgWidthProduct} 0 ${svgWidthProduct} 0`}
stroke="#8C8A93"
strokeWidth="2"
fill="none"
strokeDasharray="9"
/>
</Svg>
</View>
);
})}
@@ -205,7 +213,7 @@ const PaymentProduct = ({ packet }: PaymentProductProps) => {
]}
>
<AppText style={PaymentStyle.titleMethod}>
{packet.totalPrice}
{formatPrice(packet.totalPrice)} {t('som')}
</AppText>
</View>
</View>
@@ -214,4 +222,15 @@ const PaymentProduct = ({ packet }: PaymentProductProps) => {
);
};
const styles = StyleSheet.create({
cornerStyle: {
position: 'absolute' as const,
width: 40,
height: 40,
borderTopWidth: 6,
borderLeftWidth: 6,
borderColor: '#007AFF',
},
});
export default PaymentProduct;

View File

@@ -1,8 +1,10 @@
import { RouteProp, useRoute } from '@react-navigation/native';
import { useQuery } from '@tanstack/react-query';
import { authApi } from 'api/auth';
import exchanges_api from 'api/exchanges';
import AppText from 'components/AppText';
import LayoutTwo from 'components/LayoutTwo';
import { formatPrice } from 'helpers/formatPrice';
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import {
@@ -42,6 +44,10 @@ const PaymentQrCode = () => {
const [cardModal, setCardModal] = React.useState(false);
const [payModal, setPayModal] = React.useState(false);
const [success, setSuccess] = React.useState(false);
const { data: getMe } = useQuery({
queryKey: ['getMe'],
queryFn: authApi.getMe,
});
const screenWidth = Dimensions.get('window').width;
const isSmallScreen = screenWidth < 380;
@@ -102,7 +108,7 @@ const PaymentQrCode = () => {
)}
</View>
<View
{/* <View
style={[
PaymentStyle.info,
isSmallScreen
@@ -112,7 +118,7 @@ const PaymentQrCode = () => {
>
<AppText style={PaymentStyle.titleMethod}>Cargo</AppText>
<AppText style={PaymentStyle.textMethod}>12.267 UZS/ kg</AppText>
</View>
</View> */}
{/* <View
style={[
@@ -132,11 +138,11 @@ const PaymentQrCode = () => {
style={[
PaymentStyle.info,
isSmallScreen
? { flexBasis: '48%', alignItems: 'flex-start' }
: { flexBasis: '48%', alignItems: 'flex-start' },
? { flexBasis: '48%', alignItems: 'flex-end' }
: { flexBasis: '48%', alignItems: 'flex-end' },
]}
>
<AppText style={PaymentStyle.titleMethod}>Reys</AppText>
<AppText style={PaymentStyle.titleMethod}>{t('Reys')}</AppText>
<AppText style={[PaymentStyle.textMethod, { textAlign: 'left' }]}>
{packets.packetName}
</AppText>
@@ -180,42 +186,46 @@ const PaymentQrCode = () => {
}}
/>
</View>
{packets.items.map((item: any, index: number) => (
<View key={index} style={{ marginBottom: 15 }}>
<View style={PaymentStyle.receiptCard}>
<View style={PaymentStyle.rowBetween}>
<View style={{ width: '100%' }}>
<AppText style={PaymentStyle.itemName}>{item.name}</AppText>
<AppText style={PaymentStyle.track}>
{t('Trek ID')}: {item.trekId}
{packets.items.map((item: any, index: number) => {
return (
<View key={index} style={{ marginBottom: 15 }}>
<View style={PaymentStyle.receiptCard}>
<View style={PaymentStyle.rowBetween}>
<View style={{ width: '100%' }}>
<AppText style={PaymentStyle.itemName}>
{item.name}
</AppText>
<AppText style={PaymentStyle.track}>
{t('Trek ID')}: {item.trekId}
</AppText>
</View>
</View>
<View style={PaymentStyle.rowBetween}>
<AppText style={PaymentStyle.weight}>
{t('Ogirligi')}: {item.weight} kg
</AppText>
<AppText style={PaymentStyle.price}>
{t('Narxi')}: {item.price}$
</AppText>
</View>
<View style={PaymentStyle.rowRight}>
<AppText style={PaymentStyle.total}>
{t('Umumiy narxi')}: {item.totalPrice} $
</AppText>
</View>
</View>
<View style={PaymentStyle.rowBetween}>
<AppText style={PaymentStyle.weight}>
{t('Ogirligi')}: {item.weight}
</AppText>
<AppText style={PaymentStyle.price}>
1kg * {item.price}
</AppText>
</View>
<View style={PaymentStyle.rowRight}>
<AppText style={PaymentStyle.total}>
{t('Umumiy narxi')}: {item.totalPrice} {t('som')}
</AppText>
</View>
<Svg height="1" width={svgWidthProduct}>
<Path
d={`M 0 0 Q ${svgWidthProduct} 0 ${svgWidthProduct} 0`}
stroke="#8C8A93"
strokeWidth="2"
fill="none"
strokeDasharray="9"
/>
</Svg>
</View>
<Svg height="1" width={svgWidthProduct}>
<Path
d={`M 0 0 Q ${svgWidthProduct} 0 ${svgWidthProduct} 0`}
stroke="#8C8A93"
strokeWidth="2"
fill="none"
strokeDasharray="9"
/>
</Svg>
</View>
))}
);
})}
<View style={PaymentStyle.infoCard}>
<View
style={[
@@ -239,7 +249,7 @@ const PaymentQrCode = () => {
]}
>
<AppText style={PaymentStyle.titleMethod}>
{packets.totalPrice}
{formatPrice(packets.totalPrice)} {t('som')}
</AppText>
</View>
<View
@@ -339,13 +349,17 @@ const PaymentQrCode = () => {
setPayModal={setPayModal}
/>
)}
{packets.paymentStatus !== 'PAYED' && (
<TouchableOpacity
style={[PaymentStyle.button, { bottom: bottom + 80 }]}
onPress={toggleModal}
>
<AppText style={PaymentStyle.btnText}>{t("To'lash")}</AppText>
</TouchableOpacity>
{getMe && !getMe.aviaCargoId.includes('CP') && (
<>
{packets.paymentStatus !== 'PAYED' && (
<TouchableOpacity
style={[PaymentStyle.button, { bottom: bottom + 80 }]}
onPress={toggleModal}
>
<AppText style={PaymentStyle.btnText}>{t("To'lash")}</AppText>
</TouchableOpacity>
)}
</>
)}
</LayoutTwo>
);

View File

@@ -40,11 +40,14 @@ const SelectLangPage = ({
styles.logoImage,
{
width: 180,
height: 180,
height: 150,
marginLeft: 35,
},
]}
/>
<AppText style={[styles.logoText, { fontSize: 24 }]}>
<AppText
style={[styles.logoText, { fontSize: 42, fontWeight: '700' }]}
>
CPOST
</AppText>
</View>
@@ -59,10 +62,19 @@ const SelectLangPage = ({
<View style={styles.btnContainer}>
<TouchableOpacity
onPress={() => onSelectLang('uz')}
style={styles.button}
style={[
styles.button,
{
backgroundColor: 'none',
borderWidth: 1,
borderColor: '#28A7E8',
},
]}
>
<Image source={UZ} style={styles.flag} />
<AppText style={styles.btnText}>O'zbek tili</AppText>
<AppText style={[styles.btnText, { color: '#28A7E8' }]}>
O'zbek tili
</AppText>
</TouchableOpacity>
<TouchableOpacity
@@ -134,7 +146,7 @@ const styles = StyleSheet.create({
},
flag: {
width: 30,
height: 30,
height: 20,
resizeMode: 'cover',
},
});

View File

@@ -1,24 +1,19 @@
import * as React from "react"
import Svg, { Path } from "react-native-svg"
import * as React from 'react';
import Svg, { Path } from 'react-native-svg';
interface Props {
color: string,
width?: number,
height?: number,
view?: string
color: string;
width?: number;
height?: number;
view?: string;
}
const Auto = ({ color, height = 24, width = 24, view = "0" }: Props) => (
<Svg
width={width}
height={height}
fill="none"
viewBox={`0 ${view} 24 24`}
>
<Path
fill={color}
d="M7.965 13a3.5 3.5 0 0 1-6.93 0H0V1a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2h3l3 4.056V13h-2.035a3.501 3.501 0 0 1-6.93 0h-5.07ZM14 2H2v8.05a3.5 3.5 0 0 1 5.663.95h5.674c.168-.353.393-.674.663-.95V2Zm2 6h4v-.285L17.992 5H16v3Zm.5 6a1.5 1.5 0 1 0 0-3.001 1.5 1.5 0 0 0 0 3.001ZM6 12.5a1.5 1.5 0 1 0-3 0 1.5 1.5 0 0 0 3 0Z"
/>
</Svg>
)
export default Auto
const Auto = ({ color, height = 24, width = 24, view = '0' }: Props) => (
<Svg width={width} height={height} fill="none" viewBox={`0 ${view} 24 24`}>
<Path
fill={color}
d="M7.965 13a3.5 3.5 0 0 1-6.93 0H0V1a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2h3l3 4.056V13h-2.035a3.501 3.501 0 0 1-6.93 0h-5.07ZM14 2H2v8.05a3.5 3.5 0 0 1 5.663.95h5.674c.168-.353.393-.674.663-.95V2Zm2 6h4v-.285L17.992 5H16v3Zm.5 6a1.5 1.5 0 1 0 0-3.001 1.5 1.5 0 0 0 0 3.001ZM6 12.5a1.5 1.5 0 1 0-3 0 1.5 1.5 0 0 0 3 0Z"
/>
</Svg>
);
export default Auto;

View File

@@ -1,14 +1,14 @@
import { initializeApp } from '@react-native-firebase/app';
import { getMessaging } from '@react-native-firebase/messaging';
// import { initializeApp } from '@react-native-firebase/app';
// import { getMessaging } from '@react-native-firebase/messaging';
const firebaseConfig = {
apiKey: 'AIzaSyBEwWi1TuZBNj2hkFGGIaWZNNDCoiC__lE',
authDomain: 'cpcargo-aee14.firebaseapp.com',
projectId: 'cpcargo-aee14',
storageBucket: 'cpcargo-aee14.firebasestorage.app',
messagingSenderId: '1030089382290',
appId: '1:1030089382290:android:668f0669ad4ac3f74dc94b',
};
// const firebaseConfig = {
// apiKey: 'AIzaSyBEwWi1TuZBNj2hkFGGIaWZNNDCoiC__lE',
// authDomain: 'cpcargo-aee14.firebaseapp.com',
// projectId: 'cpcargo-aee14',
// storageBucket: 'cpcargo-aee14.firebasestorage.app',
// messagingSenderId: '1030089382290',
// appId: '1:1030089382290:android:668f0669ad4ac3f74dc94b',
// };
export const firebaseApp = initializeApp(firebaseConfig);
export const messaging = getMessaging(await firebaseApp);
// export const firebaseApp = initializeApp(firebaseConfig);
// export const messaging = getMessaging(await firebaseApp);

View File

@@ -1,9 +1,9 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import { setLanguage } from 'helpers/event';
import i18n from 'i18n/i18n';
export const changeLanguage = async (lang: string) => {
try {
await AsyncStorage.setItem('language', lang);
setLanguage(lang);
await i18n.changeLanguage(lang);
} catch (error) {}
};

View File

@@ -1,16 +1,15 @@
// firebase.js
import { initializeApp } from '@react-native-firebase/app';
// import { initializeApp } from '@react-native-firebase/app';
const firebaseConfig = {
apiKey: 'AIzaSyBnFzHK6XAjxzcQAsg0hFbeRcon8ZMDvVw', // api_key → current_key
authDomain: 'cpcargo-77d93.firebaseapp.com', // Firebase web SDK uchun qoshimcha, yoq bolsa qoldirish mumkin
projectId: 'cpcargo-77d93', // project_info → project_id
storageBucket: 'cpcargo-77d93.firebasestorage.app', // project_info → storage_bucket
messagingSenderId: '628048576398', // project_info → project_number
appId: '1:628048576398:android:f93293c00f463267a92edf', // client_info → mobilesdk_app_id
};
// const firebaseConfig = {
// apiKey: 'AIzaSyBnFzHK6XAjxzcQAsg0hFbeRcon8ZMDvVw',
// authDomain: 'cpcargo-77d93.firebaseapp.com',
// projectId: 'cpcargo-77d93',
// storageBucket: 'cpcargo-77d93.firebasestorage.app',
// messagingSenderId: '628048576398',
// appId: '1:628048576398:android:f93293c00f463267a92edf',
// };
// Firebase ilovasini initialize qilish
const app = initializeApp(firebaseConfig);
// const app = initializeApp(firebaseConfig);
export default app;
// export default app;

View File

@@ -6,7 +6,7 @@
"*": ["./src/*"],
"@screens": ["./src/screens/*"],
"screens": ["./src/screens"],
"components": ["./src/components"],
"components": ["./src/components/*"],
"assets": ["./src/assets"],
"api": ["./src/api"],
"helpers": ["./src/helpers"],