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

View File

@@ -85,8 +85,8 @@ android {
applicationId "uz.felix.cpost" applicationId "uz.felix.cpost"
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 4 versionCode 8
versionName "0.4" versionName "0.8"
multiDexEnabled true multiDexEnabled true
vectorDrawables.useSupportLibrary = 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 getMainComponentName(): String = "first"
override fun onCreate(savedInstanceState: android.os.Bundle?) { 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.ReactPackage
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.facebook.react.defaults.DefaultReactNativeHost import com.facebook.react.defaults.DefaultReactNativeHost
import com.swmansion.rnscreens.RNScreensPackage
class MainApplication : Application(), ReactApplication { 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 = { module.exports = {
presets: ['module:@react-native/babel-preset'], presets: ['module:@react-native/babel-preset'],
plugins: [ plugins: [
'react-native-reanimated/plugin', // 'react-native-reanimated/plugin',
'react-native-worklets/plugin',
'@babel/plugin-transform-export-namespace-from', '@babel/plugin-transform-export-namespace-from',
[ [
'module-resolver', 'module-resolver',

View File

@@ -1,41 +1,46 @@
/** /**
* @format * @format
*/ */
import notifee, { AndroidImportance } from '@notifee/react-native'; // import notifee, { AndroidImportance } from '@notifee/react-native';
import messaging from '@react-native-firebase/messaging'; // import messaging from '@react-native-firebase/messaging';
import { AppRegistry } from 'react-native'; import { AppRegistry } from 'react-native';
import { enableScreens } from 'react-native-screens';
import App from './App'; import App from './App';
import { name as appName } from './app.json'; 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({ enableScreens({
id: 'default', freezeOnBlur: true, // background screen componenti freeze bo'ladi
name: 'Umumiy bildirishnomalar',
sound: 'default',
importance: AndroidImportance.HIGH,
}); });
await notifee.displayNotification({ // messaging().setBackgroundMessageHandler(async remoteMessage => {
title: // console.log('Background message:', remoteMessage);
remoteMessage.notification?.title ||
remoteMessage.data?.title || // const channelId = await notifee.createChannel({
'Yangi xabar', // id: 'default',
body: // name: 'Umumiy bildirishnomalar',
remoteMessage.notification?.body || // sound: 'default',
remoteMessage.data?.body || // importance: AndroidImportance.HIGH,
'Matn yoq', // });
android: {
channelId, // await notifee.displayNotification({
largeIcon: 'ic_launcher_foreground', // title:
sound: 'default', // remoteMessage.notification?.title ||
pressAction: { // remoteMessage.data?.title ||
id: 'default', // '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); 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/> <true/>
</dict> </dict>
<key>NSCameraUsageDescription</key> <key>NSCameraUsageDescription</key>
<string>Kamera orqali rasm olish uchun ruxsat kerak</string> <string>$(NSCameraUsageDescription)</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Biz sizning joylashuvingizni aniqlash uchun ruxsat soraymiz</string>
<key>NSPhotoLibraryUsageDescription</key> <key>NSPhotoLibraryUsageDescription</key>
<string>Rasm kutubxonasidan ruxsat kerak</string> <string>$(NSPhotoLibraryUsageDescription)</string>
<key>RCTNewArchEnabled</key> <key>RCTNewArchEnabled</key>
<true/> <true/>
<key>UILaunchStoryboardName</key> <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

@@ -18,12 +18,11 @@
"@react-native-async-storage/async-storage": "^2.2.0", "@react-native-async-storage/async-storage": "^2.2.0",
"@react-native-clipboard/clipboard": "^1.16.3", "@react-native-clipboard/clipboard": "^1.16.3",
"@react-native-community/datetimepicker": "^8.4.2", "@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": "^7.1.17",
"@react-navigation/native-stack": "^7.3.25", "@react-navigation/native-stack": "^7.3.25",
"@tanstack/react-query": "^5.84.2", "@tanstack/react-query": "^5.84.2",
"axios": "^1.11.0", "axios": "^1.11.0",
"events": "^3.3.0",
"i18next": "^25.3.2", "i18next": "^25.3.2",
"lottie-react-native": "^7.3.0", "lottie-react-native": "^7.3.0",
"react": "19.1.0", "react": "19.1.0",
@@ -33,7 +32,7 @@
"react-native-app-link": "^1.0.1", "react-native-app-link": "^1.0.1",
"react-native-asset": "^2.1.1", "react-native-asset": "^2.1.1",
"react-native-biometrics": "^3.0.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-confirmation-code-field": "^8.0.1",
"react-native-device-info": "^14.0.4", "react-native-device-info": "^14.0.4",
"react-native-dotenv": "^3.4.11", "react-native-dotenv": "^3.4.11",
@@ -43,7 +42,9 @@
"react-native-linear-gradient": "^2.8.3", "react-native-linear-gradient": "^2.8.3",
"react-native-localize": "^3.5.1", "react-native-localize": "^3.5.1",
"react-native-mask-input": "^1.2.3", "react-native-mask-input": "^1.2.3",
"react-native-mmkv": "^2.2.4",
"react-native-modal": "^14.0.0-rc.1", "react-native-modal": "^14.0.0-rc.1",
"react-native-nitro-modules": "^0.31.10",
"react-native-push-notification": "^8.1.1", "react-native-push-notification": "^8.1.1",
"react-native-reanimated": "^4.0.2", "react-native-reanimated": "^4.0.2",
"react-native-safe-area-context": "^5.6.0", "react-native-safe-area-context": "^5.6.0",

View File

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

View File

@@ -1,68 +1,86 @@
"use client" 'use client';
import React, { useEffect, useState, useCallback, useMemo } from "react" import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { type LayoutChangeEvent, View } from "react-native" import { type LayoutChangeEvent, View } from 'react-native';
import Animated, { import Animated, {
useSharedValue, cancelAnimation,
useAnimatedStyle,
withRepeat,
withTiming,
Easing, Easing,
useAnimatedStyle,
useSharedValue,
withRepeat,
withSequence, withSequence,
withDelay, withTiming,
} from "react-native-reanimated" } from 'react-native-reanimated';
import Auto from "svg/Auto" import Auto from 'svg/Auto';
import Avia from "svg/Avia" import Avia from 'svg/Avia';
type Props = { type Props = {
type: "auto" | "avia" type: 'AUTO' | 'AVIA';
} };
const AnimatedIcon = ({ type }: Props) => { const AnimatedIcon = ({ type }: Props) => {
const translateX = useSharedValue(0) const translateX = useSharedValue(0);
const translateY = useSharedValue(0) const translateY = useSharedValue(0);
const rotateY = useSharedValue(0) const rotateY = useSharedValue(0);
const direction = useSharedValue(1) const direction = useSharedValue(1);
const [containerWidth, setContainerWidth] = useState(0) const [containerWidth, setContainerWidth] = useState(0);
const iconSize = 40 const iconSize = 40;
const onLayout = useCallback((event: LayoutChangeEvent) => { const onLayout = useCallback((event: LayoutChangeEvent) => {
const { width } = event.nativeEvent.layout const { width } = event.nativeEvent.layout;
setContainerWidth(width) setContainerWidth(width);
}, []); }, []);
const animationConfig = useMemo(() => ({ const animationConfig = useMemo(
() => ({
duration: 4000, duration: 4000,
easing: Easing.linear, easing: Easing.linear,
rotationDuration: 300, rotationDuration: 300,
arcHeight: -30, arcHeight: -30,
}), []); }),
[],
);
const createXAnimation = useCallback((maxX: number) => { const createXAnimation = useCallback(
(maxX: number) => {
return withRepeat( return withRepeat(
withSequence( withSequence(
withTiming(maxX, { withTiming(
maxX,
{
duration: animationConfig.duration, duration: animationConfig.duration,
easing: animationConfig.easing easing: animationConfig.easing,
}, () => { },
() => {
direction.value = -1; direction.value = -1;
rotateY.value = withTiming(180, { duration: animationConfig.rotationDuration }); 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 withTiming(
0,
{
duration: animationConfig.duration,
easing: animationConfig.easing,
},
() => {
direction.value = 1;
rotateY.value = withTiming(0, {
duration: animationConfig.rotationDuration,
});
},
),
),
-1,
);
},
[animationConfig, direction, rotateY],
); );
}, [animationConfig, direction, rotateY]);
const createYAnimation = useCallback(() => { const createYAnimation = useCallback(() => {
if (type === "avia") { if (type === 'AVIA') {
return withRepeat( return withRepeat(
withSequence( withSequence(
withTiming(animationConfig.arcHeight, { withTiming(animationConfig.arcHeight, {
@@ -80,9 +98,9 @@ const AnimatedIcon = ({ type }: Props) => {
withTiming(0, { withTiming(0, {
duration: animationConfig.duration / 2, duration: animationConfig.duration / 2,
easing: Easing.in(Easing.quad), easing: Easing.in(Easing.quad),
}) }),
), ),
-1 -1,
); );
} }
return withTiming(0, { duration: 100 }); return withTiming(0, { duration: 100 });
@@ -93,9 +111,20 @@ const AnimatedIcon = ({ type }: Props) => {
const maxX = containerWidth - iconSize; const maxX = containerWidth - iconSize;
// eski animatsiyani toxtat
cancelAnimation(translateX);
cancelAnimation(translateY);
// qiymatlarni 0 ga reset qil
translateX.value = 0;
translateY.value = 0;
rotateY.value = 0;
direction.value = 1;
// keyin yangisini qayta boshlash
translateX.value = createXAnimation(maxX); translateX.value = createXAnimation(maxX);
translateY.value = createYAnimation(); translateY.value = createYAnimation();
}, [containerWidth, type, createXAnimation, createYAnimation, translateX, translateY]); }, [containerWidth, type]); // type = activeTab dan keladigan qiymat
const animatedStyle = useAnimatedStyle(() => { const animatedStyle = useAnimatedStyle(() => {
return { return {
@@ -104,34 +133,43 @@ const AnimatedIcon = ({ type }: Props) => {
{ translateY: translateY.value }, { translateY: translateY.value },
{ rotateY: `${rotateY.value}deg` }, { rotateY: `${rotateY.value}deg` },
], ],
} };
}); });
const containerStyle = useMemo(() => ({ const containerStyle = useMemo(
() => ({
height: 100, height: 100,
justifyContent: "center" as const, justifyContent: 'center' as const,
backgroundColor: "transparent" as const, backgroundColor: 'transparent' as const,
position: "relative" as const, position: 'relative' as const,
}), []); }),
[],
);
const trackStyle = useMemo(() => ({ const trackStyle = useMemo(
() => ({
height: 2, height: 2,
backgroundColor: "#28A7E850", backgroundColor: '#28A7E850',
position: "absolute" as const, position: 'absolute' as const,
top: 25, top: 25,
left: 0, left: 0,
right: 0, right: 0,
}), []); }),
[],
);
const iconContainerStyle = useMemo(() => ({ const iconContainerStyle = useMemo(
position: "absolute" as const, () => ({
position: 'absolute' as const,
top: 0, top: 0,
height: iconSize, height: iconSize,
width: iconSize, width: iconSize,
}), [iconSize]); }),
[iconSize],
);
const renderIcon = useMemo(() => { const renderIcon = useMemo(() => {
if (type === "auto") { if (type === 'AUTO') {
return <Auto color="#28A7E8" width={iconSize} height={iconSize} />; return <Auto color="#28A7E8" width={iconSize} height={iconSize} />;
} }
return <Avia color="#28A7E8" width={iconSize} height={iconSize} />; return <Avia color="#28A7E8" width={iconSize} height={iconSize} />;
@@ -144,7 +182,7 @@ const AnimatedIcon = ({ type }: Props) => {
{renderIcon} {renderIcon}
</Animated.View> </Animated.View>
</View> </View>
) );
} };
export default AnimatedIcon; 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 React, { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; 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 { import {
Asset, Asset,
ImagePickerResponse, ImagePickerResponse,
launchCamera,
launchImageLibrary, launchImageLibrary,
MediaType, MediaType,
} from 'react-native-image-picker'; } from 'react-native-image-picker';
@@ -20,9 +29,6 @@ export interface FileData {
export interface SingleFileDropProps { export interface SingleFileDropProps {
title: string; title: string;
onFileSelected?: (file: FileData) => void; onFileSelected?: (file: FileData) => void;
/**
* Ruxsat berilgan MIME tipi (masalan: "image/png" yoki "image/jpeg")
*/
type?: string; type?: string;
} }
@@ -44,7 +50,7 @@ const SingleFileDrop: React.FC<SingleFileDropProps> = ({
const handleImagePickerResponse = useCallback( const handleImagePickerResponse = useCallback(
(response: ImagePickerResponse) => { (response: ImagePickerResponse) => {
if (response.didCancel) return; // foydalanuvchi bekor qilsa if (response.didCancel) return;
if (response.errorCode) { if (response.errorCode) {
Alert.alert('Xato', response.errorMessage || 'Rasmni yuklashda xato'); Alert.alert('Xato', response.errorMessage || 'Rasmni yuklashda xato');
return; return;
@@ -53,7 +59,6 @@ const SingleFileDrop: React.FC<SingleFileDropProps> = ({
const asset: Asset | undefined = response.assets?.[0]; const asset: Asset | undefined = response.assets?.[0];
if (!asset || !asset.uri || !asset.type || !asset.base64) return; if (!asset || !asset.uri || !asset.type || !asset.base64) return;
// faqat belgilangan tipdagi fayllarni qabul qilish
if (!asset.type.startsWith('image/')) { if (!asset.type.startsWith('image/')) {
Alert.alert('Xato', 'Faqat rasm fayllarni yuklashingiz mumkin'); Alert.alert('Xato', 'Faqat rasm fayllarni yuklashingiz mumkin');
return; return;
@@ -77,6 +82,32 @@ const SingleFileDrop: React.FC<SingleFileDropProps> = ({
launchImageLibrary(imagePickerOptions, handleImagePickerResponse); launchImageLibrary(imagePickerOptions, handleImagePickerResponse);
}, [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( const UploadIcon = useMemo(
() => () => () => () =>
( (
@@ -122,7 +153,7 @@ const SingleFileDrop: React.FC<SingleFileDropProps> = ({
}, [selectedImage, title, UploadIcon]); }, [selectedImage, title, UploadIcon]);
return ( return (
<TouchableOpacity style={styles.dropSection} onPress={openGallery}> <TouchableOpacity style={styles.dropSection} onPress={openPicker}>
{renderContent} {renderContent}
</TouchableOpacity> </TouchableOpacity>
); );

View File

@@ -5,7 +5,6 @@ import {
Dimensions, Dimensions,
Image, Image,
Linking, Linking,
Platform,
StyleSheet, StyleSheet,
TouchableOpacity, TouchableOpacity,
View, View,
@@ -13,7 +12,6 @@ import {
import AppLink from 'react-native-app-link'; import AppLink from 'react-native-app-link';
import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useSafeAreaInsets } from 'react-native-safe-area-context';
import Logo from 'screens/../../assets/bootsplash/logo.png'; import Logo from 'screens/../../assets/bootsplash/logo.png';
import Bell from 'svg/Bell';
import Instagram from 'svg/Instagram'; import Instagram from 'svg/Instagram';
import Telegram from 'svg/Telegram'; import Telegram from 'svg/Telegram';
import AppText from './AppText'; import AppText from './AppText';
@@ -97,14 +95,13 @@ const Navbar = () => {
size={iconSizes.facebook} size={iconSizes.facebook}
/> />
</TouchableOpacity> */} </TouchableOpacity> */}
{Platform.OS === 'android' && ( {/* {Platform.OS === 'android' && (
<TouchableOpacity <TouchableOpacity
onPress={() => navigation.navigate('Notifications')} onPress={() => navigation.navigate('Notifications')}
> >
<Bell color="#fff" width={24} height={24} /> <Bell color="#fff" width={24} height={24} />
{/* <View style={styles.bellDot} /> */}
</TouchableOpacity> </TouchableOpacity>
)} )} */}
</View> </View>
</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": "Произошла ошибка", "Xatolik yuz berdi": "Произошла ошибка",
"Passport qo'shishda xatolik yuz berdi": "Ошибка добавления паспорта", "Passport qo'shishda xatolik yuz berdi": "Ошибка добавления паспорта",
"Kodsiz tovarlar": "Товары без кода", "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", "Familiya": "Familiya",
"Familiyangiz": "Familiyangiz", "Familiyangiz": "Familiyangiz",
"Bizni qaerdan topdingiz?": "Bizni qaerdan topdingiz?", "Bizni qaerdan topdingiz?": "Bizni qaerdan topdingiz?",
"Reys": "Reys",
"narxi": "narxi",
"Bizni kim tavsiya qildi...": "Bizni kim tavsiya qildi...", "Bizni kim tavsiya qildi...": "Bizni kim tavsiya qildi...",
"Foydalanish shartlari": "Foydalanish shartlari", "Foydalanish shartlari": "Foydalanish shartlari",
"bilan tanishib chiqdim!": "bilan tanishib chiqdim!", "bilan tanishib chiqdim!": "bilan tanishib chiqdim!",
@@ -42,13 +44,13 @@
"foydalanuvchi_majburiyatlari": "2. Foydalanuvchi majburiyatlari", "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", "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": "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": "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.", "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": "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.", "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": "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:", "oxirgi_yangilanish": "Oxirgi yangilanish:",
"roziman": "Roziman", "roziman": "Roziman",
"Shaxsiy maʼlumotlar": "Shaxsiy maʼlumotlar", "Shaxsiy maʼlumotlar": "Shaxsiy maʼlumotlar",
@@ -111,7 +113,7 @@
"Filiallarimiz ro'yhati ilovada mavjud": "Filiallarimiz ro'yhati ilovada mavjud", "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.", "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.", "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.", "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", "dan boshlab": "dan boshlab",
@@ -156,6 +158,7 @@
"Mahsulotlar ogirligi": "Mahsulotlar ogirligi", "Mahsulotlar ogirligi": "Mahsulotlar ogirligi",
"Faol": "Faol", "Faol": "Faol",
"Kutilmoqda": "Kutilmoqda", "Kutilmoqda": "Kutilmoqda",
"To'lov kutilmoqda": "To'lov kutilmoqda",
"Faol emas": "Faol emas", "Faol emas": "Faol emas",
"Xatolik yuz berdi!": "Xatolik yuz berdi!", "Xatolik yuz berdi!": "Xatolik yuz berdi!",
"Akkaunt faol emas!": "Akkaunt faol emas!", "Akkaunt faol emas!": "Akkaunt faol emas!",
@@ -177,10 +180,11 @@
"som": "so'm", "som": "so'm",
"Umumiy narx": "Umumiy narx", "Umumiy narx": "Umumiy narx",
"Yopish": "Yopish", "Yopish": "Yopish",
"Mahsulot nomi": "Mahsulot nomi",
"Passportlarim": "Passportlarim", "Passportlarim": "Passportlarim",
"Hali pasport qo'shilmagan": "Hali pasport qo'shilmagan", "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 uchun tugmani bosing": "Yangi passport qo'shish uchun tugmani bosing",
"Yangi pasport qo'shish": "Yangi pasport qo'shish", "Yangi pasport qo'shish": "Yangi passport qo'shish",
"Passport malumotlarim": "Passport malumotlarim", "Passport malumotlarim": "Passport malumotlarim",
"Tez ID": "Tez ID", "Tez ID": "Tez ID",
"Toliq ismi": "Toliq ismi", "Toliq ismi": "Toliq ismi",
@@ -231,5 +235,23 @@
"Yuborish": "Yuborish", "Yuborish": "Yuborish",
"Passport qo'shishda xatolik yuz berdi": "Passport qo'shishda xatolik yuz berdi", "Passport qo'shishda xatolik yuz berdi": "Passport qo'shishda xatolik yuz berdi",
"Xatolik yuz berdi": "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'), phone: z.string().min(12, 'Xato raqam kiritildi'),
passportSeriya: z.string().length(2, '2 ta harf kerak'), passportSeriya: z.string().length(2, '2 ta harf kerak'),
passportNumber: z.string().length(7, '7 ta raqam kerak'), passportNumber: z.string().length(7, '7 ta raqam kerak'),
branchId: z.number().min(1, 'Filialni tanlang'),
}); });
export type LoginFormType = z.infer<typeof loginSchema>; export type LoginFormType = z.infer<typeof loginSchema>;

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,14 +1,15 @@
import AsyncStorage from '@react-native-async-storage/async-storage'; import AsyncStorage from '@react-native-async-storage/async-storage';
import { getApp } from '@react-native-firebase/app'; // import { getApp } from '@react-native-firebase/app';
import { getMessaging, getToken } from '@react-native-firebase/messaging'; // import { getMessaging, getToken } from '@react-native-firebase/messaging';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useMutation } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query';
import { authApi } from 'api/auth'; import { authApi } from 'api/auth';
import { otpPayload, resendPayload } from 'api/auth/type'; import { otpPayload, resendPayload } from 'api/auth/type';
import AppText from 'components/AppText'; import AppText from 'components/AppText';
import ErrorNotification from 'components/ErrorNotification';
import formatPhone from 'helpers/formatPhone'; import formatPhone from 'helpers/formatPhone';
import React, { useEffect, useRef, useState } from 'react'; import React, { useRef, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
ActivityIndicator, ActivityIndicator,
@@ -20,10 +21,8 @@ import {
TouchableOpacity, TouchableOpacity,
View, View,
} from 'react-native'; } from 'react-native';
import DeviceInfo from 'react-native-device-info';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import Logo from 'screens/../../assets/bootsplash/logo_512.png'; import Logo from 'screens/../../assets/bootsplash/logo_512.png';
import { useModalStore } from 'screens/auth/registeration/lib/modalStore';
import LanguageSelector from 'screens/auth/select-language/SelectLang'; import LanguageSelector from 'screens/auth/select-language/SelectLang';
import ArrowLeft from 'svg/ArrowLeft'; import ArrowLeft from 'svg/ArrowLeft';
import { RootStackParamList } from 'types/types'; import { RootStackParamList } from 'types/types';
@@ -50,34 +49,37 @@ const Confirm = ({
const [errorConfirm, setErrorConfirm] = useState<string | null>(null); const [errorConfirm, setErrorConfirm] = useState<string | null>(null);
const inputRefs = useRef<Array<TextInput | null>>([]); const inputRefs = useRef<Array<TextInput | null>>([]);
const { phoneNumber } = useUserStore(state => state); const { phoneNumber } = useUserStore(state => state);
const [firebaseToken, setFirebseToken] = useState<{ const [visible, setVisible] = useState(false);
fcmToken: string; const [error, setError] = useState<string>('Xatolik yuz berdi');
deviceId: string; // const [firebaseToken, setFirebseToken] = useState<{
deviceName: string; // fcmToken: string;
} | null>(); // deviceId: string;
// deviceName: string;
// } | null>();
const app = getApp(); // const app = getApp();
const messaging = getMessaging(app); // const messaging = getMessaging(app);
const getDeviceData = async () => { // const getDeviceData = async () => {
try { // try {
const fcmToken = await getToken(messaging); // const fcmToken = await getToken(messaging);
return { // return {
fcmToken, // fcmToken,
deviceId: await DeviceInfo.getUniqueId(), // deviceId: await DeviceInfo.getUniqueId(),
deviceName: await DeviceInfo.getDeviceName(), // deviceName: await DeviceInfo.getDeviceName(),
}; // };
} catch (e) { // } catch (e) {
console.log('Xato:', e); // console.log('Xato:', e);
return null; // return null;
} // }
}; // };
// useEffect(() => {
// getDeviceData().then(data => {
// setFirebseToken(data);
// });
// }, []);
useEffect(() => {
getDeviceData().then(data => {
setFirebseToken(data);
});
}, []);
const { mutate, isPending } = useMutation({ const { mutate, isPending } = useMutation({
mutationFn: (payload: otpPayload) => authApi.verifyOtp(payload), mutationFn: (payload: otpPayload) => authApi.verifyOtp(payload),
onSuccess: async res => { onSuccess: async res => {
@@ -86,8 +88,8 @@ const Confirm = ({
setErrorConfirm(null); setErrorConfirm(null);
}, },
onError: (err: any) => { onError: (err: any) => {
console.dir(err); setError(err?.response?.data?.message || 'Xatolik yuz berdi');
setErrorConfirm(err?.response.data.message); setVisible(true);
}, },
}); });
@@ -101,26 +103,11 @@ const Confirm = ({
setErrorConfirm(null); setErrorConfirm(null);
}, },
onError: (err: any) => { onError: (err: any) => {
setErrorConfirm(err?.response.data.message); setError(err?.response?.data?.message || 'Xatolik yuz berdi');
setVisible(true);
}, },
}); });
const openModal = useModalStore(state => state.openModal);
useEffect(() => {
let interval: NodeJS.Timeout | null = null;
if (timer > 0) {
interval = setInterval(() => {
setTimer(prevTimer => prevTimer - 1);
}, 1000);
} else {
setCanResend(true);
if (interval) clearInterval(interval);
}
return () => {
if (interval) clearInterval(interval);
};
}, [timer]);
const handleCodeChange = (text: string, index: number) => { const handleCodeChange = (text: string, index: number) => {
const newCode = [...code]; const newCode = [...code];
newCode[index] = text; newCode[index] = text;
@@ -149,16 +136,15 @@ const Confirm = ({
const handleVerifyCode = () => { const handleVerifyCode = () => {
const enteredCode = code.join(''); const enteredCode = code.join('');
if (firebaseToken) {
mutate({ mutate({
phoneNumber, phoneNumber,
otp: enteredCode, otp: enteredCode,
otpType: 'REGISTRATION', otpType: 'REGISTRATION',
deviceId: firebaseToken.deviceId, deviceId: '',
deviceName: firebaseToken.deviceName, deviceName: '',
fcmToken: firebaseToken.fcmToken, fcmToken: '',
}); });
}
}; };
return ( return (
@@ -180,6 +166,11 @@ const Confirm = ({
</TouchableOpacity> </TouchableOpacity>
<LanguageSelector /> <LanguageSelector />
</View> </View>
<ErrorNotification
setVisible={setVisible}
error={error}
visible={visible}
/>
<KeyboardAvoidingView <KeyboardAvoidingView
style={styles.container} style={styles.container}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'} behavior={Platform.OS === 'ios' ? 'padding' : 'height'}

View File

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

View File

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

View File

@@ -3,7 +3,14 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import AppText from 'components/AppText'; import AppText from 'components/AppText';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; 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 { SafeAreaView } from 'react-native-safe-area-context';
import ArrowLeft from 'svg/ArrowLeft'; import ArrowLeft from 'svg/ArrowLeft';
import { RootStackParamList } from 'types/types'; import { RootStackParamList } from 'types/types';
@@ -16,11 +23,21 @@ type TermsScreenNavigationProp = NativeStackNavigationProp<
const TermsAndConditions = () => { const TermsAndConditions = () => {
const navigation = useNavigation<TermsScreenNavigationProp>(); const navigation = useNavigation<TermsScreenNavigationProp>();
const { t } = useTranslation(); const { t } = useTranslation();
const handleAgree = () => { const handleAgree = () => {
navigation.goBack(); 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 ( return (
<SafeAreaView style={styles.container}> <SafeAreaView style={styles.container}>
<View style={styles.header}> <View style={styles.header}>
@@ -35,16 +52,24 @@ const TermsAndConditions = () => {
<ScrollView style={styles.content} showsVerticalScrollIndicator={false}> <ScrollView style={styles.content} showsVerticalScrollIndicator={false}>
<AppText style={styles.title}> <AppText style={styles.title}>
{t('foydalanish_shartlari_va_qoidalari')} {t('Foydalanish shartlari va qoidalari')}
</AppText> </AppText>
<AppText style={styles.sectionTitle}>{t('umumiy_qoidalar')}</AppText> <AppText style={styles.text}>
<AppText style={styles.text}>{t('umumiy_qoidalar_text')}</AppText> {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}> <AppText style={styles.sectionTitle}>
{t('foydalanuvchi_majburiyatlari')} {t('foydalanuvchi_majburiyatlari')}
</AppText> </AppText>
<AppText style={styles.text}> <AppText style={[styles.text, { textAlign: 'left' }]}>
{t('foydalanuvchi_majburiyatlari_text')} {t('foydalanuvchi_majburiyatlari_text')}
</AppText> </AppText>
@@ -63,17 +88,30 @@ const TermsAndConditions = () => {
<AppText style={styles.sectionTitle}>{t('aloqa')}</AppText> <AppText style={styles.sectionTitle}>{t('aloqa')}</AppText>
<AppText style={styles.text}>{t('aloqa_text')}</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}> <View style={styles.footer}>
<AppText style={styles.footerText}> <AppText style={styles.footerText}>
{t('oxirgi_yangilanish')} {new Date().toLocaleDateString('uz-UZ')} {t('oxirgi_yangilanish')} 01/09/2025
</AppText> </AppText>
</View> </View>
</ScrollView> </ScrollView>
<View style={styles.bottomContainer}> <View style={styles.bottomContainer}>
<TouchableOpacity style={styles.agreeButton} onPress={handleAgree}> <TouchableOpacity style={styles.agreeButton} onPress={handleAgree}>
<AppText style={styles.agreeButtonText}>{t('roziman')}</AppText> <AppText style={styles.agreeButtonText}>{t('Roziman')}</AppText>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
</SafeAreaView> </SafeAreaView>

View File

@@ -1,16 +1,19 @@
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import AppText from 'components/AppText'; import AppText from 'components/AppText';
import { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
Image, Image,
Linking,
Modal,
ScrollView, ScrollView,
StyleSheet, StyleSheet,
TouchableOpacity, TouchableOpacity,
View, View,
useWindowDimensions,
} from 'react-native'; } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import SendIntentAndroid from 'react-native-send-intent';
import Logo from 'screens/../../assets/bootsplash/logo_512.png'; import Logo from 'screens/../../assets/bootsplash/logo_512.png';
import InfoIcon from 'svg/Info'; import InfoIcon from 'svg/Info';
import { RootStackParamList } from 'types/types'; import { RootStackParamList } from 'types/types';
@@ -23,7 +26,18 @@ type LoginScreenNavigationProp = NativeStackNavigationProp<
const SelectAuth = () => { const SelectAuth = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const navigation = useNavigation<LoginScreenNavigationProp>(); const navigation = useNavigation<LoginScreenNavigationProp>();
const { width } = useWindowDimensions(); const [modalVisible, setModalVisible] = useState(false);
const [newRegisterModalVisible, setNewRegisterModalVisible] = useState(false);
const openTelegram = async () => {
const telegramUri = 'tg://resolve?domain=cpostuz';
try {
const success = await SendIntentAndroid.openAppWithUri(telegramUri);
if (!success) Linking.openURL('https://t.me/cpostuz');
} catch (error) {
Linking.openURL('https://t.me/cpostuz');
}
};
return ( return (
<SafeAreaView style={{ flex: 1 }}> <SafeAreaView style={{ flex: 1 }}>
@@ -41,10 +55,16 @@ const SelectAuth = () => {
styles.logoImage, styles.logoImage,
{ {
width: 180, 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> */} {/* <AppText style={[styles.logoText, { fontSize: 32 }]}>CPOST</AppText> */}
</View> </View>
@@ -54,6 +74,78 @@ const SelectAuth = () => {
<View style={styles.btnContainer}> <View style={styles.btnContainer}>
<View style={{ gap: 6 }}> <View style={{ gap: 6 }}>
<TouchableOpacity
onPress={() => setModalVisible(true)}
style={{
gap: 4,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
}}
>
<InfoIcon color="#000" height={18} width={18} />
<AppText style={styles.helperText}>
{t("Botdan ro'yxatdan otganmisiz")}?
</AppText>
</TouchableOpacity>
<Modal
animationType="slide"
transparent={true}
visible={modalVisible}
onRequestClose={() => setModalVisible(false)}
>
<View style={styles.modalOverlay}>
<View style={styles.modalContainer}>
<ScrollView>
<AppText style={styles.modalTitle}>
{t('Yordam')}
</AppText>
<AppText style={styles.modalText}>
{t('Agar siz oldin ')}
<AppText
style={{ color: '#28A7E8' }}
onPress={openTelegram}
>
@cpcargo_bot
</AppText>
{t(
' orqali royxatdan otgan bolsangiz, tizimga kirish uchun botda ishlatilgan telefon raqamingiz va passport seriya raqamingizni kiriting.',
)}
</AppText>
<AppText style={styles.modalText}>
{t('Masalan')}:
</AppText>
<AppText style={styles.modalText}>
{t('Telefon: +998901234567')}
</AppText>
<AppText style={styles.modalText}>
{t('Passport seriya: AA1234567')}
</AppText>
<AppText style={styles.modalText}>
{t(
'Shu malumotlarni kiritganingizdan song siz tizimga kirishingiz mumkin.',
)}
</AppText>
<TouchableOpacity
style={styles.closeButton}
onPress={() => setModalVisible(false)}
>
<AppText
style={{
color: '#fff',
textAlign: 'center',
fontWeight: 'bold',
}}
>
{t('Yopish')}
</AppText>
</TouchableOpacity>
</ScrollView>
</View>
</View>
</Modal>
<TouchableOpacity <TouchableOpacity
onPress={() => navigation.navigate('Login')} onPress={() => navigation.navigate('Login')}
style={[ style={[
@@ -69,30 +161,10 @@ const SelectAuth = () => {
{t('Tizimga kirish')} {t('Tizimga kirish')}
</AppText> </AppText>
</TouchableOpacity> </TouchableOpacity>
<View
style={{
gap: 4,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
}}
>
<InfoIcon color="#000" height={18} width={18} />
<AppText style={styles.helperText}>
{t("Botdan ro'yxatdan otganmisiz")}?
</AppText>
</View>
</View> </View>
<View style={{ gap: 6 }}> <View style={{ gap: 6 }}>
<TouchableOpacity <TouchableOpacity
onPress={() => navigation.navigate('Register')} onPress={() => setNewRegisterModalVisible(true)}
style={styles.button}
>
<AppText style={styles.btnText}>
{t('Royxatdan otish')}
</AppText>
</TouchableOpacity>
<View
style={{ style={{
gap: 4, gap: 4,
flexDirection: 'row', flexDirection: 'row',
@@ -104,8 +176,70 @@ const SelectAuth = () => {
<AppText style={styles.helperText}> <AppText style={styles.helperText}>
{t("Yangi royxatdan o'tmoqchimisiz")}? {t("Yangi royxatdan o'tmoqchimisiz")}?
</AppText> </AppText>
</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>
</View> </View>
</Modal>
</View> </View>
</View> </View>
</ScrollView> </ScrollView>
@@ -167,4 +301,34 @@ const styles = StyleSheet.create({
btnContainer: { btnContainer: {
gap: 16, gap: 16,
}, },
modalOverlay: {
flex: 1,
backgroundColor: 'rgba(0,0,0,0.5)',
justifyContent: 'center',
alignItems: 'center',
},
modalContainer: {
backgroundColor: '#fff',
borderRadius: 12,
padding: 20,
width: '85%',
maxHeight: '70%',
},
modalTitle: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 10,
},
modalText: {
fontSize: 14,
marginBottom: 2,
lineHeight: 20,
color: '#000',
},
closeButton: {
backgroundColor: '#28A7E8',
paddingVertical: 12,
borderRadius: 8,
marginTop: 10,
},
}); });

View File

@@ -6,7 +6,14 @@ import AppText from 'components/AppText';
import LayoutTwo from 'components/LayoutTwo'; import LayoutTwo from 'components/LayoutTwo';
import * as React from 'react'; import * as React from 'react';
import { useTranslation } from 'react-i18next'; 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'; import Tabs from '../../home/ui/Tabs';
interface CargoPricesProps {} interface CargoPricesProps {}
@@ -25,6 +32,19 @@ const CargoPrices = (props: CargoPricesProps) => {
refetch(); refetch();
}, [activeTab]); }, [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 ( return (
<LayoutTwo title={t('Kargo narxlari')}> <LayoutTwo title={t('Kargo narxlari')}>
<ScrollView style={{ flex: 1 }}> <ScrollView style={{ flex: 1 }}>
@@ -34,7 +54,7 @@ const CargoPrices = (props: CargoPricesProps) => {
<View style={{ marginTop: 10, gap: 10, marginBottom: 20 }}> <View style={{ marginTop: 10, gap: 10, marginBottom: 20 }}>
{data && {data &&
data.map(ref => ( data.map(ref => (
<View style={styles.cardWhite}> <View style={styles.cardWhite} key={ref.id}>
<View style={styles.priceCard}> <View style={styles.priceCard}>
<AppText style={styles.titleBlack}>{ref.title}</AppText> <AppText style={styles.titleBlack}>{ref.title}</AppText>
<AppText style={[styles.titleBlack, { fontSize: 16 }]}> <AppText style={[styles.titleBlack, { fontSize: 16 }]}>
@@ -105,7 +125,10 @@ const CargoPrices = (props: CargoPricesProps) => {
</AppText> </AppText>
</View> </View>
<AppText style={[styles.desc, { color: '#000000' }]}> <AppText style={[styles.desc, { color: '#000000' }]}>
{t('Batafsil')}: @CPcargo_admin {t('Batafsil')}:{' '}
<AppText style={{ color: '#28A7E8' }} onPress={openTelegram}>
@cpost_admin
</AppText>
</AppText> </AppText>
</View> </View>
<View style={[styles.card]}> <View style={[styles.card]}>
@@ -129,7 +152,7 @@ const CargoPrices = (props: CargoPricesProps) => {
<View style={{ marginTop: 20, gap: 10, marginBottom: 20 }}> <View style={{ marginTop: 20, gap: 10, marginBottom: 20 }}>
{data && {data &&
data.map(ref => ( data.map(ref => (
<View style={styles.cardWhite}> <View style={styles.cardWhite} key={ref.id}>
<View style={styles.priceCard}> <View style={styles.priceCard}>
<AppText style={styles.titleBlack}>{ref.title}</AppText> <AppText style={styles.titleBlack}>{ref.title}</AppText>
<AppText style={[styles.titleBlack, { fontSize: 16 }]}> <AppText style={[styles.titleBlack, { fontSize: 16 }]}>
@@ -193,7 +216,10 @@ const CargoPrices = (props: CargoPricesProps) => {
</AppText> </AppText>
</View> </View>
<AppText style={[styles.desc, { color: '#000000' }]}> <AppText style={[styles.desc, { color: '#000000' }]}>
{t('Batafsil')}: @CPcargo_admin {t('Batafsil')}:{' '}
<AppText style={{ color: '#28A7E8' }} onPress={openTelegram}>
@cpost_admin
</AppText>
</AppText> </AppText>
</View> </View>
<View style={[styles.card]}> <View style={[styles.card]}>

View File

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

View File

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

View File

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

View File

@@ -3,6 +3,7 @@ import { useMutation } from '@tanstack/react-query';
import passportApi, { AddPassportPayload } from 'api/passport'; import passportApi, { AddPassportPayload } from 'api/passport';
import AppText from 'components/AppText'; import AppText from 'components/AppText';
import DatePickerInput from 'components/DatePicker'; import DatePickerInput from 'components/DatePicker';
import ErrorNotification from 'components/ErrorNotification';
import SingleFileDrop from 'components/FileDrop'; import SingleFileDrop from 'components/FileDrop';
import LayoutTwo from 'components/LayoutTwo'; import LayoutTwo from 'components/LayoutTwo';
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
@@ -12,7 +13,6 @@ import {
ActivityIndicator, ActivityIndicator,
Dimensions, Dimensions,
KeyboardAvoidingView, KeyboardAvoidingView,
Platform,
ScrollView, ScrollView,
TextInput, TextInput,
TouchableOpacity, TouchableOpacity,
@@ -39,22 +39,38 @@ const CreatePassword = () => {
const [isDatePickerVisible, setDatePickerVisibility] = useState(false); const [isDatePickerVisible, setDatePickerVisibility] = useState(false);
const [selectedDate, setSelectedDate] = useState<Date | null>(null); const [selectedDate, setSelectedDate] = useState<Date | null>(null);
const [success, setSuccess] = React.useState(false); const [success, setSuccess] = React.useState(false);
const [error, setError] = useState(false); const [error, setError] = useState<string>('Xatolik yuz berdi');
const [frontImage, setFrontImage] = useState<FileData | null>(null); const [frontImage, setFrontImage] = useState<FileData | null>(null);
const [backImage, setBackImage] = useState<FileData | null>(null); const [backImage, setBackImage] = useState<FileData | null>(null);
const [visible, setVisible] = useState(false);
const [errorVisible, setErrorVisible] = useState(false);
const [errorText, setErrorText] = useState('');
// firstName: '',
// 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({ const { mutate, isPending } = useMutation({
mutationFn: (payload: AddPassportPayload) => { mutationFn: (payload: AddPassportPayload) => {
const res = passportApi.addPassport(payload); const res = passportApi.addPassport(payload);
return res; return res;
}, },
onSuccess: res => { onSuccess: () => {
setSuccess(true); setSuccess(true);
}, },
onError: err => { onError: (err: any) => {
console.dir(err); console.dir(err);
setErrorVisible(true);
setError(true); setErrorText(
err?.response?.data || err?.response?.message || 'Xatolik yuz berdi',
);
}, },
}); });
@@ -90,6 +106,28 @@ const CreatePassword = () => {
}; };
const onSubmit = (data: CreatePassSchemaType) => { const onSubmit = (data: CreatePassSchemaType) => {
if (
errors.birthDate ||
errors.firstName ||
errors.jshshir ||
errors.lastName ||
errors.passportNumber ||
errors.passportSeriya
) {
const firstError =
errors.birthDate?.message ||
errors.passportSeriya?.message ||
errors.passportNumber?.message ||
errors.firstName?.message ||
errors.lastName?.message ||
errors.jshshir?.message ||
'Xatolik yuz berdi';
setError(
firstError ? "Ma'lumotlarni to'liq kiriting" : 'Xatolik yuz berdi',
);
setVisible(true);
return;
}
const [d, m, y] = data.birthDate.split('/'); const [d, m, y] = data.birthDate.split('/');
const isoBirthDate = `${y}-${m.padStart(2, '0')}-${d.padStart(2, '0')}`; const isoBirthDate = `${y}-${m.padStart(2, '0')}-${d.padStart(2, '0')}`;
@@ -104,17 +142,40 @@ const CreatePassword = () => {
passportBackImage: backImage ? `${backImage.base64}` : '', passportBackImage: backImage ? `${backImage.base64}` : '',
}); });
}; };
const onErrorSubmit = (errors: any) => {
const firstError =
errors.birthDate?.message ||
errors.passportSeriya?.message ||
errors.passportNumber?.message ||
errors.firstName?.message ||
errors.lastName?.message ||
errors.jshshir?.message ||
'Xatolik yuz berdi';
setError(
firstError ? "Ma'lumotlarni to'liq kiriting" : 'Xatolik yuz berdi',
);
setVisible(true);
};
return ( return (
<LayoutTwo title="Yangi pasport qo'shish"> <LayoutTwo title={t("Yangi pasport qo'shish")}>
<KeyboardAvoidingView <KeyboardAvoidingView
style={PassportStyle.container} style={PassportStyle.container}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'} behavior={'padding'}
keyboardVerticalOffset={30}
> >
<ScrollView <ScrollView
keyboardShouldPersistTaps="handled"
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
style={PassportStyle.content} style={PassportStyle.content}
> >
<View style={PassportStyle.scrollContainer}> <View style={PassportStyle.scrollContainer}>
<ErrorNotification
setVisible={setVisible}
error={error}
visible={visible}
/>
<View style={PassportStyle.loginContainer}> <View style={PassportStyle.loginContainer}>
<Controller <Controller
control={control} control={control}
@@ -123,17 +184,15 @@ const CreatePassword = () => {
<View> <View>
<AppText style={PassportStyle.label}>{t('Ism')}</AppText> <AppText style={PassportStyle.label}>{t('Ism')}</AppText>
<TextInput <TextInput
ref={firstNameRef}
returnKeyType="next"
onSubmitEditing={() => lastNameRef.current?.focus()}
style={PassportStyle.input} style={PassportStyle.input}
placeholder={t('Ismingiz')} placeholder={t('Ismingiz')}
onChangeText={onChange} onChangeText={onChange}
value={value} value={value}
placeholderTextColor={'#D8DADC'} placeholderTextColor={'#D8DADC'}
/> />
{errors.firstName && (
<AppText style={PassportStyle.errorText}>
{t(errors.firstName.message || '')}
</AppText>
)}
</View> </View>
)} )}
/> />
@@ -146,17 +205,15 @@ const CreatePassword = () => {
{t('Familiya')} {t('Familiya')}
</AppText> </AppText>
<TextInput <TextInput
ref={lastNameRef}
returnKeyType="next"
onSubmitEditing={() => passportSeriyaRef.current?.focus()}
style={PassportStyle.input} style={PassportStyle.input}
placeholder={t('Familiyangiz')} placeholder={t('Familiyangiz')}
placeholderTextColor={'#D8DADC'} placeholderTextColor={'#D8DADC'}
onChangeText={onChange} onChangeText={onChange}
value={value} value={value}
/> />
{errors.lastName && (
<AppText style={PassportStyle.errorText}>
{t(errors.lastName.message || '')}
</AppText>
)}
</View> </View>
)} )}
/> />
@@ -170,6 +227,11 @@ const CreatePassword = () => {
name="passportSeriya" name="passportSeriya"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<TextInput <TextInput
ref={passportSeriyaRef}
returnKeyType="next"
onSubmitEditing={() =>
passportNumberRef.current?.focus()
}
style={[PassportStyle.input, PassportStyle.seriyaInput]} style={[PassportStyle.input, PassportStyle.seriyaInput]}
placeholder="AA" placeholder="AA"
maxLength={2} maxLength={2}
@@ -191,6 +253,8 @@ const CreatePassword = () => {
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<TextInput <TextInput
ref={passportNumberRef} ref={passportNumberRef}
returnKeyType="next"
onSubmitEditing={() => jshshirRef.current?.focus()}
style={[PassportStyle.input, PassportStyle.raqamInput]} style={[PassportStyle.input, PassportStyle.raqamInput]}
placeholder="1234567" placeholder="1234567"
maxLength={7} maxLength={7}
@@ -205,12 +269,6 @@ const CreatePassword = () => {
)} )}
/> />
</View> </View>
{(errors.passportSeriya || errors.passportNumber) && (
<AppText style={PassportStyle.errorText}>
{t(errors.passportSeriya?.message || '') ||
t(errors.passportNumber?.message || '')}
</AppText>
)}
</View> </View>
<View> <View>
<AppText style={PassportStyle.label}>{t('JSHSHIR')}</AppText> <AppText style={PassportStyle.label}>{t('JSHSHIR')}</AppText>
@@ -220,6 +278,9 @@ const CreatePassword = () => {
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<TextInput <TextInput
style={PassportStyle.input} style={PassportStyle.input}
ref={jshshirRef}
returnKeyType="next"
onSubmitEditing={() => birthDateRef.current?.focus()}
placeholder="12345678901234" placeholder="12345678901234"
placeholderTextColor={'#D8DADC'} placeholderTextColor={'#D8DADC'}
keyboardType="numeric" keyboardType="numeric"
@@ -232,11 +293,6 @@ const CreatePassword = () => {
/> />
)} )}
/> />
{errors.jshshir && (
<AppText style={PassportStyle.errorText}>
{t(errors.jshshir.message || '')}
</AppText>
)}
</View> </View>
<View> <View>
<Controller <Controller
@@ -250,6 +306,8 @@ const CreatePassword = () => {
<View style={[PassportStyle.inputContainer]}> <View style={[PassportStyle.inputContainer]}>
<TextInput <TextInput
ref={birthDateRef}
returnKeyType="done"
style={[ style={[
PassportStyle.input, PassportStyle.input,
{ flex: 1, borderWidth: 0, padding: 0 }, { flex: 1, borderWidth: 0, padding: 0 },
@@ -315,12 +373,6 @@ const CreatePassword = () => {
<Calendar color="#D8DADC" width={25} height={25} /> <Calendar color="#D8DADC" width={25} height={25} />
</TouchableOpacity> </TouchableOpacity>
</View> </View>
{errors.birthDate && (
<AppText style={PassportStyle.errorText}>
{t(errors.birthDate?.message || '')}
</AppText>
)}
</View> </View>
)} )}
/> />
@@ -375,7 +427,7 @@ const CreatePassword = () => {
</View> </View>
</View> </View>
<TouchableOpacity <TouchableOpacity
onPress={handleSubmit(onSubmit)} onPress={handleSubmit(onSubmit, onErrorSubmit)}
style={PassportStyle.button} style={PassportStyle.button}
> >
{isPending ? ( {isPending ? (
@@ -391,7 +443,12 @@ const CreatePassword = () => {
</ScrollView> </ScrollView>
</KeyboardAvoidingView> </KeyboardAvoidingView>
{success && ( {success && (
<CreateModal visible={success} setVisible={setSuccess} error={error} /> <CreateModal
visible={success}
setVisible={setSuccess}
error={errorVisible}
errorText={errorText}
/>
)} )}
</LayoutTwo> </LayoutTwo>
); );

View File

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

View File

@@ -1,7 +1,7 @@
import Layout from 'components/Layout'; import Layout from 'components/Layout';
import LoadingScreen from 'components/LoadingScreen'; import LoadingScreen from 'components/LoadingScreen';
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { RefreshControl, ScrollView, StyleSheet } from 'react-native'; import { RefreshControl, ScrollView } from 'react-native';
import ProfileHeader from './ProfileHeader'; import ProfileHeader from './ProfileHeader';
import ProfilePages from './ProfilePages'; import ProfilePages from './ProfilePages';
@@ -61,29 +61,4 @@ const Profile = () => {
); );
}; };
const styles = StyleSheet.create({
container: {
flex: 1,
},
addBtn: {
backgroundColor: '#28A7E8',
padding: 10,
marginBottom: 10,
textAlign: 'center',
justifyContent: 'center',
alignItems: 'center',
width: '95%',
margin: 'auto',
borderRadius: 8,
flexDirection: 'row',
gap: 10,
position: 'static',
},
btnText: {
color: '#FFFFFF',
fontSize: 18,
fontFamily: 'GolosText-Bold',
},
});
export default Profile; export default Profile;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,5 @@
import AppText from 'components/AppText'; import AppText from 'components/AppText';
import { formatPrice } from 'helpers/formatPrice';
import React, { useEffect, useRef } from 'react'; import React, { useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
@@ -22,14 +23,6 @@ const OrderDetailModal = ({ visible, setVisible, selectedOrder }: Props) => {
const translateY = useRef(new Animated.Value(50)).current; const translateY = useRef(new Animated.Value(50)).current;
const { t } = useTranslation(); 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 = () => { const closeModal = () => {
Animated.timing(opacity, { Animated.timing(opacity, {
toValue: 0, toValue: 0,
@@ -82,10 +75,6 @@ const OrderDetailModal = ({ visible, setVisible, selectedOrder }: Props) => {
style={{ maxHeight: 250 }} style={{ maxHeight: 250 }}
> >
{selectedOrder.items.map((product, index) => { {selectedOrder.items.map((product, index) => {
const totalPrice = product.totalPrice;
const weight = product.weight;
const pricePerKg = Math.ceil(weight ? totalPrice / weight : 0);
return ( return (
<View key={product.trekId + index} style={styles.productItem}> <View key={product.trekId + index} style={styles.productItem}>
<View style={styles.row}> <View style={styles.row}>
@@ -100,13 +89,12 @@ const OrderDetailModal = ({ visible, setVisible, selectedOrder }: Props) => {
{t('Ogirligi')}: {product.weight} {t('Ogirligi')}: {product.weight}
</AppText> </AppText>
<AppText style={styles.detail}> <AppText style={styles.detail}>
{t('Narxi')}: 1kg * {pricePerKg.toLocaleString('uz-UZ')}{' '} {t('Narxi')}: {product.price}$
{t("so'm")}
</AppText> </AppText>
</View> </View>
<View style={styles.rowRight}> <View style={styles.rowRight}>
<AppText style={styles.total}> <AppText style={styles.total}>
{t('Umumiy narxi')}: {product.totalPrice} {t('som')} {t('Umumiy narxi')}: {product.totalPrice}$
</AppText> </AppText>
</View> </View>
</View> </View>
@@ -117,7 +105,7 @@ const OrderDetailModal = ({ visible, setVisible, selectedOrder }: Props) => {
<View style={styles.totalRow}> <View style={styles.totalRow}>
<AppText style={styles.totalLabel}>{t('Umumiy narx')}:</AppText> <AppText style={styles.totalLabel}>{t('Umumiy narx')}:</AppText>
<AppText style={styles.totalValue}> <AppText style={styles.totalValue}>
{selectedOrder.totalPrice} {formatPrice(selectedOrder.totalPrice)} {t('som')}
</AppText> </AppText>
</View> </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, View,
useWindowDimensions, useWindowDimensions,
} from 'react-native'; } from 'react-native';
import Serach from 'svg/Serach';
import { DataInfo } from '../lib/data'; import { DataInfo } from '../lib/data';
import Filter from './Filter'; import Filter from './Filter';
import Order from './Order'; import Order from './Order';
import OrderDetailModal from './OrderDetailModal'; import OrderDetailModal from './OrderDetailModal';
import Tabs from './Tabs';
const Status = () => { const Status = () => {
const { width: screenWidth } = useWindowDimensions(); const { width: screenWidth } = useWindowDimensions();
const scale = screenWidth < 360 ? 0.85 : 1; const scale = screenWidth < 360 ? 0.85 : 1;
const [paymentStatus, setPaymentStatus] = useState('NEW');
const [filter, setFilter] = useState< const [filter, setFilter] = useState<
| 'COLLECTING' | 'COLLECTING'
@@ -38,7 +37,7 @@ const Status = () => {
const [transportTypes, setTransportTypes] = useState< const [transportTypes, setTransportTypes] = useState<
// 'all'| // 'all'|
'AUTO' | 'AVIA' 'AUTO' | 'AVIA'
>('AUTO'); >('AVIA');
const [page, setPage] = useState(0); const [page, setPage] = useState(0);
const { const {
data: statusData, data: statusData,
@@ -46,12 +45,14 @@ const Status = () => {
isLoading, isLoading,
isFetching, isFetching,
} = useQuery({ } = useQuery({
queryKey: ['status', filter, transportTypes, page], queryKey: ['status', transportTypes, page, paymentStatus],
queryFn: () => queryFn: () =>
packetsApi.getPacketsStatus(filter, { packetsApi.getPackets({
page, page,
size: 10, size: 10,
cargoType: transportTypes, cargoType: transportTypes,
sort: 'id',
direction: 'DESC',
}), }),
}); });
@@ -131,11 +132,6 @@ const Status = () => {
[refreshing, isFetching, onRefresh], [refreshing, isFetching, onRefresh],
); );
const searchIcon = useMemo(
() => <Serach color="#D8DADC" width={20 * scale} height={20 * scale} />,
[scale],
);
if (isLoading || isFetching) { if (isLoading || isFetching) {
return ( return (
<Layout> <Layout>
@@ -144,20 +140,30 @@ const Status = () => {
); );
} }
if (statusData?.data.length === 0) { if (statusData?.data.length === 0 || statusData === undefined) {
return ( return (
<Layout> <Layout>
<Tabs filter={filter} setFilter={setFilter} /> {/* <Tabs filter={filter} setFilter={setFilter} /> */}
<View style={styles.controls}> <View style={styles.controls}>
<View style={{ position: 'relative' }}> <View
style={{
position: 'relative',
flexDirection: 'row',
gap: 8,
}}
>
<Filter <Filter
transportTypes={transportTypes} transportTypes={transportTypes}
setTransportTypes={setTransportTypes} setTransportTypes={setTransportTypes}
reys={reys} reys={reys}
setReys={setReys} setReys={setReys}
data={statusData!} data={statusData}
setSelectedData={setSelectedData} setSelectedData={setSelectedData}
/> />
{/* <PaymentFilter
paymentStatus={paymentStatus}
setPaymentStatus={setPaymentStatus}
/> */}
</View> </View>
</View> </View>
<NoResult message={t("Hech qanday ma'lumot topilmadi")} /> <NoResult message={t("Hech qanday ma'lumot topilmadi")} />
@@ -172,7 +178,7 @@ const Status = () => {
refreshControl={refreshControl} refreshControl={refreshControl}
removeClippedSubviews={true} removeClippedSubviews={true}
> >
<Tabs filter={filter} setFilter={setFilter} /> {/* <Tabs filter={filter} setFilter={setFilter} /> */}
<View style={styles.controls}> <View style={styles.controls}>
{/* <View style={styles.searchContainer}> {/* <View style={styles.searchContainer}>
<TextInput <TextInput
@@ -182,7 +188,13 @@ const Status = () => {
/> />
<View style={styles.searchIcon}>{searchIcon}</View> <View style={styles.searchIcon}>{searchIcon}</View>
</View> */} </View> */}
<View style={{ position: 'relative' }}> <View
style={{
position: 'relative',
flexDirection: 'row',
gap: 8,
}}
>
<Filter <Filter
transportTypes={transportTypes} transportTypes={transportTypes}
setTransportTypes={setTransportTypes} setTransportTypes={setTransportTypes}
@@ -191,6 +203,10 @@ const Status = () => {
data={statusData!} data={statusData!}
setSelectedData={setSelectedData} setSelectedData={setSelectedData}
/> />
{/* <PaymentFilter
paymentStatus={paymentStatus}
setPaymentStatus={setPaymentStatus}
/> */}
</View> </View>
</View> </View>
<Order <Order

View File

@@ -4,7 +4,7 @@ import { PacketsData } from 'api/packets';
import AppText from 'components/AppText'; import AppText from 'components/AppText';
import React, { useCallback, useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { TouchableOpacity, View } from 'react-native'; import { StyleSheet, TouchableOpacity, View } from 'react-native';
import { PaymentStyle } from './style'; import { PaymentStyle } from './style';
type WalletStackParamList = { type WalletStackParamList = {
@@ -22,6 +22,7 @@ interface Props {
const Payment = ({ packets }: Props) => { const Payment = ({ packets }: Props) => {
const navigation = useNavigation<LoginScreenNavigationProp>(); const navigation = useNavigation<LoginScreenNavigationProp>();
const { t } = useTranslation(); const { t } = useTranslation();
const styles = useMemo(() => makeStyles(), []);
const handlePaymentPress = useCallback( const handlePaymentPress = useCallback(
(item: any) => { (item: any) => {
@@ -52,10 +53,17 @@ const Payment = ({ packets }: Props) => {
const renderPaymentCard = useCallback( const renderPaymentCard = useCallback(
(item: any) => { (item: any) => {
const isPaid = const isPaid =
item.paymentStatus === 'PAYED' || item.paymentStatus === 'PENDING'; item.paymentStatus === 'PAYED' || item.paymentStatus === 'PAID';
const cardStyle = [ const cardStyle = [
PaymentStyle.card, PaymentStyle.card,
{ borderColor: isPaid ? '#4CAF50' : '#D32F2F', borderWidth: 1.5 }, {
borderColor: isPaid
? '#4CAF50'
: item.paymentStatus === 'PENDING'
? '#FFA500'
: '#D32F2F',
borderWidth: 1.5,
},
]; ];
return ( return (
@@ -70,6 +78,20 @@ const Payment = ({ packets }: Props) => {
<AppText style={PaymentStyle.title}>{item.packetName}</AppText> <AppText style={PaymentStyle.title}>{item.packetName}</AppText>
{isPaid ? ( {isPaid ? (
<AppText style={PaymentStyle.badge}>{t("To'langan")}</AppText> <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> <AppText style={badgeStyle}>{t("To'lanmagan")}</AppText>
)} )}
@@ -87,7 +109,53 @@ const Payment = ({ packets }: Props) => {
{item.packetName} {item.packetName}
</AppText> </AppText>
</View> </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}> <AppText style={PaymentStyle.infoTitle}>
{t("Mahsulotlar og'irligi")} {t("Mahsulotlar og'irligi")}
</AppText> </AppText>
@@ -104,7 +172,7 @@ const Payment = ({ packets }: Props) => {
{t('Umumiy narxi')} {t('Umumiy narxi')}
</AppText> </AppText>
<AppText style={PaymentStyle.text}>{item.totalPrice}</AppText> <AppText style={PaymentStyle.text}>{item.totalPrice}</AppText>
</View> </View> */}
</View> </View>
</View> </View>
</TouchableOpacity> </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; 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 packetsApi from 'api/packets';
import AppText from 'components/AppText'; import AppText from 'components/AppText';
import React, { useEffect, useRef } from 'react'; import React, { useEffect, useRef } from 'react';
@@ -52,6 +53,10 @@ const ModalPay = ({
const { bottom } = useSafeAreaInsets(); const { bottom } = useSafeAreaInsets();
const [load, setLoad] = React.useState(false); const [load, setLoad] = React.useState(false);
const { t } = useTranslation(); const { t } = useTranslation();
const { data: getMe } = useQuery({
queryKey: ['getMe'],
queryFn: authApi.getMe,
});
const { mutate, isPending } = useMutation({ const { mutate, isPending } = useMutation({
mutationFn: ({ id, payType }: { id: number; payType: string }) => mutationFn: ({ id, payType }: { id: number; payType: string }) =>
@@ -137,11 +142,13 @@ const ModalPay = ({
}, },
]} ]}
> >
{getMe && !getMe.aviaCargoId.includes('CP') && (
<TouchableOpacity <TouchableOpacity
style={[ style={[
styles.option, styles.option,
{ {
backgroundColor: selectedId === 'card' ? '#28A7E81A' : '#fff', backgroundColor:
selectedId === 'card' ? '#28A7E81A' : '#fff',
}, },
]} ]}
onPress={() => setSelectedId('card')} onPress={() => setSelectedId('card')}
@@ -167,7 +174,8 @@ const ModalPay = ({
{ {
backgroundColor: backgroundColor:
selectedId === 'card' ? '#28A7E8' : '#FFFFFF', selectedId === 'card' ? '#28A7E8' : '#FFFFFF',
borderColor: selectedId === 'card' ? '#28A7E8' : '#383838', borderColor:
selectedId === 'card' ? '#28A7E8' : '#383838',
}, },
]} ]}
> >
@@ -176,6 +184,7 @@ const ModalPay = ({
)} )}
</View> </View>
</TouchableOpacity> </TouchableOpacity>
)}
{paymentType !== 'CASH' && ( {paymentType !== 'CASH' && (
<TouchableOpacity <TouchableOpacity
style={[ style={[

View File

@@ -1,9 +1,10 @@
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import exchanges_api from 'api/exchanges'; import exchanges_api from 'api/exchanges';
import AppText from 'components/AppText'; import AppText from 'components/AppText';
import { formatPrice } from 'helpers/formatPrice';
import * as React from 'react'; import * as React from 'react';
import { useTranslation } from 'react-i18next'; 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 Svg, { Circle, Path } from 'react-native-svg';
import Plane from 'svg/Plane'; import Plane from 'svg/Plane';
import { PaymentStyle } from '../../payment/ui/style'; import { PaymentStyle } from '../../payment/ui/style';
@@ -17,6 +18,7 @@ const PaymentProduct = ({ packet }: PaymentProductProps) => {
const screenWidth = Dimensions.get('window').width; const screenWidth = Dimensions.get('window').width;
const isSmallScreen = screenWidth < 380; const isSmallScreen = screenWidth < 380;
const svgWidth = screenWidth * 0.8; const svgWidth = screenWidth * 0.8;
const svgWidthProduct = screenWidth * 1;
const { data } = useQuery({ const { data } = useQuery({
queryKey: ['exchanges'], queryKey: ['exchanges'],
@@ -80,21 +82,23 @@ const PaymentProduct = ({ packet }: PaymentProductProps) => {
: { flexBasis: '48%', alignItems: 'flex-end' }, : { flexBasis: '48%', alignItems: 'flex-end' },
]} ]}
> >
<AppText style={PaymentStyle.titleMethod}>Cargo</AppText> <AppText style={PaymentStyle.titleMethod}>{t('Reys')}</AppText>
<AppText style={PaymentStyle.textMethod}>12.267 UZS/ kg</AppText> <AppText style={[PaymentStyle.textMethod, { textAlign: 'left' }]}>
{packet.packetName}
</AppText>
</View> </View>
{/* <View {/* <View
style={[ style={[
PaymentStyle.info, PaymentStyle.info,
isSmallScreen isSmallScreen
? { flexBasis: '48%', alignItems: 'flex-start' } ? { flexBasis: '48%', alignItems: 'flex-end' }
: { flexBasis: '48%', alignItems: 'flex-start' }, : { flexBasis: '48%', alignItems: 'flex-end' },
]} ]}
> >
<AppText style={PaymentStyle.titleMethod}>{t('Yetkazish vaqti')}</AppText> <AppText style={PaymentStyle.titleMethod}>Cargo</AppText>
<AppText style={PaymentStyle.textMethod}>08.25.2025</AppText> <AppText style={PaymentStyle.textMethod}>12.267 UZS/ kg</AppText>
</View> */} </View>
<View <View
style={[ style={[
@@ -104,11 +108,11 @@ const PaymentProduct = ({ packet }: PaymentProductProps) => {
: { flexBasis: '48%', alignItems: 'flex-start' }, : { flexBasis: '48%', alignItems: 'flex-start' },
]} ]}
> >
<AppText style={PaymentStyle.titleMethod}>Reys</AppText> <AppText style={PaymentStyle.titleMethod}>
<AppText style={[PaymentStyle.textMethod, { textAlign: 'left' }]}> {t('Yetkazish vaqti')}
{packet.packetName}
</AppText> </AppText>
</View> <AppText style={PaymentStyle.textMethod}>08.25.2025</AppText>
</View> */}
</View> </View>
<View <View
style={{ style={{
@@ -149,36 +153,40 @@ const PaymentProduct = ({ packet }: PaymentProductProps) => {
/> />
</View> </View>
{packet.items.map((item: any, index: number) => { {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 ( return (
<View key={index} style={{ marginBottom: 15 }}> <View key={index} style={{ marginBottom: 15 }}>
<View style={PaymentStyle.receiptCard}> <View style={PaymentStyle.receiptCard}>
<View style={PaymentStyle.rowBetween}> <View style={PaymentStyle.rowBetween}>
<View style={{ width: '100%' }}>
<AppText style={PaymentStyle.itemName}>{item.name}</AppText> <AppText style={PaymentStyle.itemName}>{item.name}</AppText>
<AppText style={PaymentStyle.track}> <AppText style={PaymentStyle.track}>
{t('Trek ID')}: {item.trackId} {t('Trek ID')}: {item.trekId}
</AppText> </AppText>
</View> </View>
</View>
<View style={PaymentStyle.rowBetween}> <View style={PaymentStyle.rowBetween}>
<AppText style={PaymentStyle.weight}> <AppText style={PaymentStyle.weight}>
{t('Ogirligi')}: {weight} kg {t('Ogirligi')}: {item.weight} kg
</AppText> </AppText>
<AppText style={PaymentStyle.price}> <AppText style={PaymentStyle.price}>
1kg * {formattedPrice} {t('som')} {t('Narxi')}: {item.price}$
</AppText> </AppText>
</View> </View>
<View style={PaymentStyle.rowRight}> <View style={PaymentStyle.rowRight}>
<AppText style={PaymentStyle.total}> <AppText style={PaymentStyle.total}>
{t('Umumiy narxi')}: {formattedTotal} {t('som')} {t('Umumiy narxi')}: {item.totalPrice} $
</AppText> </AppText>
</View> </View>
</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> </View>
); );
})} })}
@@ -205,7 +213,7 @@ const PaymentProduct = ({ packet }: PaymentProductProps) => {
]} ]}
> >
<AppText style={PaymentStyle.titleMethod}> <AppText style={PaymentStyle.titleMethod}>
{packet.totalPrice} {formatPrice(packet.totalPrice)} {t('som')}
</AppText> </AppText>
</View> </View>
</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; export default PaymentProduct;

View File

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

View File

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

View File

@@ -1,24 +1,19 @@
import * as React from "react" import * as React from 'react';
import Svg, { Path } from "react-native-svg" import Svg, { Path } from 'react-native-svg';
interface Props { interface Props {
color: string, color: string;
width?: number, width?: number;
height?: number, height?: number;
view?: string view?: string;
} }
const Auto = ({ color, height = 24, width = 24, view = "0" }: Props) => ( const Auto = ({ color, height = 24, width = 24, view = '0' }: Props) => (
<Svg <Svg width={width} height={height} fill="none" viewBox={`0 ${view} 24 24`}>
width={width}
height={height}
fill="none"
viewBox={`0 ${view} 24 24`}
>
<Path <Path
fill={color} 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" 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> </Svg>
) );
export default Auto export default Auto;

View File

@@ -1,14 +1,14 @@
import { initializeApp } from '@react-native-firebase/app'; // import { initializeApp } from '@react-native-firebase/app';
import { getMessaging } from '@react-native-firebase/messaging'; // import { getMessaging } from '@react-native-firebase/messaging';
const firebaseConfig = { // const firebaseConfig = {
apiKey: 'AIzaSyBEwWi1TuZBNj2hkFGGIaWZNNDCoiC__lE', // apiKey: 'AIzaSyBEwWi1TuZBNj2hkFGGIaWZNNDCoiC__lE',
authDomain: 'cpcargo-aee14.firebaseapp.com', // authDomain: 'cpcargo-aee14.firebaseapp.com',
projectId: 'cpcargo-aee14', // projectId: 'cpcargo-aee14',
storageBucket: 'cpcargo-aee14.firebasestorage.app', // storageBucket: 'cpcargo-aee14.firebasestorage.app',
messagingSenderId: '1030089382290', // messagingSenderId: '1030089382290',
appId: '1:1030089382290:android:668f0669ad4ac3f74dc94b', // appId: '1:1030089382290:android:668f0669ad4ac3f74dc94b',
}; // };
export const firebaseApp = initializeApp(firebaseConfig); // export const firebaseApp = initializeApp(firebaseConfig);
export const messaging = getMessaging(await firebaseApp); // 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'; import i18n from 'i18n/i18n';
export const changeLanguage = async (lang: string) => { export const changeLanguage = async (lang: string) => {
try { try {
await AsyncStorage.setItem('language', lang); setLanguage(lang);
await i18n.changeLanguage(lang); await i18n.changeLanguage(lang);
} catch (error) {} } catch (error) {}
}; };

View File

@@ -1,16 +1,15 @@
// firebase.js // firebase.js
import { initializeApp } from '@react-native-firebase/app'; // import { initializeApp } from '@react-native-firebase/app';
const firebaseConfig = { // const firebaseConfig = {
apiKey: 'AIzaSyBnFzHK6XAjxzcQAsg0hFbeRcon8ZMDvVw', // api_key → current_key // apiKey: 'AIzaSyBnFzHK6XAjxzcQAsg0hFbeRcon8ZMDvVw',
authDomain: 'cpcargo-77d93.firebaseapp.com', // Firebase web SDK uchun qoshimcha, yoq bolsa qoldirish mumkin // authDomain: 'cpcargo-77d93.firebaseapp.com',
projectId: 'cpcargo-77d93', // project_info → project_id // projectId: 'cpcargo-77d93',
storageBucket: 'cpcargo-77d93.firebasestorage.app', // project_info → storage_bucket // storageBucket: 'cpcargo-77d93.firebasestorage.app',
messagingSenderId: '628048576398', // project_info → project_number // messagingSenderId: '628048576398',
appId: '1:628048576398:android:f93293c00f463267a92edf', // client_info → mobilesdk_app_id // 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/*"], "*": ["./src/*"],
"@screens": ["./src/screens/*"], "@screens": ["./src/screens/*"],
"screens": ["./src/screens"], "screens": ["./src/screens"],
"components": ["./src/components"], "components": ["./src/components/*"],
"assets": ["./src/assets"], "assets": ["./src/assets"],
"api": ["./src/api"], "api": ["./src/api"],
"helpers": ["./src/helpers"], "helpers": ["./src/helpers"],