This commit is contained in:
azizziy
2025-09-04 10:09:23 +05:00
54 changed files with 2502 additions and 643 deletions

204
App.tsx
View File

@@ -2,7 +2,10 @@
import AsyncStorage from '@react-native-async-storage/async-storage'; import AsyncStorage from '@react-native-async-storage/async-storage';
import { NavigationContainer } from '@react-navigation/native'; import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { toastConfig } from 'components/CustomAlertModal'; import { toastConfig } from 'components/CustomAlertModal';
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';
@@ -10,33 +13,34 @@ import {
Animated, Animated,
Dimensions, Dimensions,
LogBox, LogBox,
PermissionsAndroid,
Platform,
StatusBar, StatusBar,
StyleSheet, StyleSheet,
View, View,
} from 'react-native'; } from 'react-native';
import { enableScreens } from 'react-native-screens';
import Toast from 'react-native-toast-message'; import Toast from 'react-native-toast-message';
// 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 Register from 'screens/auth/registeration/ui'; import Register from 'screens/auth/registeration/ui';
import SecondStep from 'screens/auth/registeration/ui/SecondStep';
import TermsAndConditions from 'screens/auth/registeration/ui/TermsAndConditions'; import TermsAndConditions from 'screens/auth/registeration/ui/TermsAndConditions';
import SelectAuth from 'screens/auth/select-auth/SelectAuth'; import SelectAuth from 'screens/auth/select-auth/SelectAuth';
import Branches from 'screens/home/branches/ui/Branches'; import Branches from 'screens/home/branches/ui/Branches';
import CargoPrices from 'screens/home/cargoPrices/ui/CargoPrices';
// Ignore specific warnings that might affect performance
LogBox.ignoreLogs([
'Non-serializable values were found in the navigation state',
'ViewPropTypes will be removed',
]);
enableScreens();
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { navigationRef } from 'components/NavigationRef';
import SplashScreen from 'components/SplashScreen';
import Confirm from 'screens/auth/login/ui/Confirm';
import SecondStep from 'screens/auth/registeration/ui/SecondStep';
import ListBranches from 'screens/home/branches/ui/ListBranches'; import ListBranches from 'screens/home/branches/ui/ListBranches';
import CargoPrices from 'screens/home/cargoPrices/ui/CargoPrices';
import Home from 'screens/home/home/ui/Home'; import Home from 'screens/home/home/ui/Home';
import RestrictedProduct from 'screens/home/restrictedProduct/ui/RestrictedProduct'; import RestrictedProduct from 'screens/home/restrictedProduct/ui/RestrictedProduct';
import CreatePassword from 'screens/passport/createPassport/ui/CreatePassword'; import CreatePassword from 'screens/passport/createPassport/ui/CreatePassword';
@@ -55,13 +59,116 @@ import PaymentMethod from 'screens/wallet/paymentMethod/ui/PaymentMethod';
import PaymentQrCode from 'screens/wallet/successPayment/ui/PaymentQrCode'; import PaymentQrCode from 'screens/wallet/successPayment/ui/PaymentQrCode';
import Onboarding from 'screens/welcome/Onboarding'; import Onboarding from 'screens/welcome/Onboarding';
LogBox.ignoreLogs([
'Non-serializable values were found in the navigation state',
'ViewPropTypes will be removed',
]);
const Stack = createNativeStackNavigator(); 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) => {
try {
const stored = await AsyncStorage.getItem('notifications');
const notifications = stored ? JSON.parse(stored) : [];
const newNotification = {
id: Date.now(),
title:
remoteMessage.notification?.title ||
remoteMessage.data?.title ||
'Yangi bildirishnoma',
message:
remoteMessage.notification?.body ||
remoteMessage.data?.body ||
'Matn yoq',
sentTime: remoteMessage.sentTime || Date.now(),
};
await AsyncStorage.setItem(
'notifications',
JSON.stringify([newNotification, ...notifications]),
);
} catch (e) {
console.error('Notification saqlashda xato:', e);
}
};
async function onDisplayNotification(remoteMessage: any) {
const channelId = await notifee.createChannel({
id: 'default',
name: 'Umumiy bildirishnomalar',
sound: 'default',
importance: AndroidImportance.HIGH,
});
await notifee.displayNotification({
title:
remoteMessage.notification?.title ||
remoteMessage.data?.title ||
'Yangi xabar',
body:
remoteMessage.notification?.body ||
remoteMessage.data?.body ||
'Matn yoq',
android: {
channelId,
smallIcon: 'ic_launcher_foreground',
sound: 'default',
pressAction: {
id: 'default',
},
},
});
}
async function requestNotificationPermission() {
if (Platform.OS === 'android' && Platform.Version >= 33) {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS,
);
console.log('POST_NOTIFICATIONS permission:', granted);
}
}
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);
useEffect(() => {
requestNotificationPermission();
const messagingInstance = getMessaging();
const unsubscribe = onMessage(messagingInstance, async remoteMessage => {
console.log('Foreground message:', remoteMessage);
await saveNotification(remoteMessage);
await onDisplayNotification(remoteMessage);
});
const unsubscribeOpened = onNotificationOpenedApp(
messagingInstance,
remoteMessage => {
console.log('Backgrounddan ochildi:', remoteMessage);
saveNotification(remoteMessage);
},
);
(async () => {
const remoteMessage = await getInitialNotification(messagingInstance);
if (remoteMessage) {
console.log('Killeddan ochildi:', remoteMessage);
saveNotification(remoteMessage);
}
})();
return () => {
unsubscribe();
unsubscribeOpened();
};
}, []);
useEffect(() => { useEffect(() => {
const initializeApp = async () => { const initializeApp = async () => {
@@ -72,9 +179,7 @@ export default function App() {
AsyncStorage.getItem('language'), AsyncStorage.getItem('language'),
]); ]);
if (lang) { if (lang) await i18n.changeLanguage(lang);
await i18n.changeLanguage(lang);
}
const initialRouteName = !seen const initialRouteName = !seen
? 'Onboarding' ? 'Onboarding'
@@ -91,8 +196,6 @@ export default function App() {
initializeApp(); initializeApp();
}, []); }, []);
const [isSplashVisible, setIsSplashVisible] = useState(true);
const handleSplashFinish = useMemo( const handleSplashFinish = useMemo(
() => () => { () => () => {
Animated.timing(slideAnim, { Animated.timing(slideAnim, {
@@ -111,6 +214,34 @@ export default function App() {
}, },
[], [],
); );
const [firebaseToken, setFirebseToken] = useState<{
fcmToken: string;
deviceId: string;
deviceName: string;
} | null>();
const app = getApp();
const messaging = getMessaging(app);
const getDeviceData = async () => {
try {
const fcmToken = await getToken(messaging);
return {
fcmToken,
deviceId: await DeviceInfo.getUniqueId(),
deviceName: await DeviceInfo.getDeviceName(),
};
} catch (e) {
console.log('Xato:', e);
return null;
}
};
console.log(firebaseToken);
useEffect(() => {
getDeviceData().then(data => {
setFirebseToken(data);
});
}, []);
if (!initialRoute) return null; if (!initialRoute) return null;
@@ -128,13 +259,7 @@ export default function App() {
}} }}
initialRouteName={initialRoute} initialRouteName={initialRoute}
> >
<Stack.Screen <Stack.Screen name="Onboarding">
name="Onboarding"
options={{
gestureEnabled: false,
animation: 'none',
}}
>
{props => ( {props => (
<Onboarding <Onboarding
{...props} {...props}
@@ -142,8 +267,6 @@ export default function App() {
/> />
)} )}
</Stack.Screen> </Stack.Screen>
{/* <Stack.Screen name="select-lang" component={SelectLangPage} /> */}
<Stack.Screen name="select-auth" component={SelectAuth} /> <Stack.Screen name="select-auth" component={SelectAuth} />
<Stack.Screen name="Login" component={Login} /> <Stack.Screen name="Login" component={Login} />
<Stack.Screen name="Login-Confirm" component={Confirm} /> <Stack.Screen name="Login-Confirm" component={Confirm} />
@@ -151,32 +274,15 @@ export default function App() {
<Stack.Screen name="Confirm" component={SecondStep} /> <Stack.Screen name="Confirm" component={SecondStep} />
<Stack.Screen name="SettingsLock" component={SettingsLock} /> <Stack.Screen name="SettingsLock" component={SettingsLock} />
<Stack.Screen name="AddLock" component={AddedLock} /> <Stack.Screen name="AddLock" component={AddedLock} />
<Stack.Screen name="Home" component={Home} />
<Stack.Screen <Stack.Screen name="Status" component={Status} />
name="Home"
component={Home}
options={{
gestureEnabled: false,
animation: 'none',
}}
/>
<Stack.Screen
name="Status"
component={Status}
options={{
animation: 'none',
}}
/>
<Stack.Screen name="Passports" component={Passport} /> <Stack.Screen name="Passports" component={Passport} />
<Stack.Screen name="CargoPrices" component={CargoPrices} /> <Stack.Screen name="CargoPrices" component={CargoPrices} />
<Stack.Screen name="create-password" component={CreatePassword} /> <Stack.Screen name="create-password" component={CreatePassword} />
<Stack.Screen name="Wallet" component={Wallet} /> <Stack.Screen name="Wallet" component={Wallet} />
<Stack.Screen name="PaymentMethod" component={PaymentMethod} /> <Stack.Screen name="PaymentMethod" component={PaymentMethod} />
<Stack.Screen name="EnterCard" component={EnterCard} /> <Stack.Screen name="EnterCard" component={EnterCard} />
<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} />
<Stack.Screen name="Notifications" component={Notifications} /> <Stack.Screen name="Notifications" component={Notifications} />

View File

@@ -165,3 +165,5 @@ dependencies {
// Performance dependencies // Performance dependencies
implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.multidex:multidex:2.0.1'
} }
apply plugin: 'com.google.gms.google-services'

View File

@@ -1,13 +1,13 @@
{ {
"project_info": { "project_info": {
"project_number": "628048576398", "project_number": "1030089382290",
"project_id": "cpcargo-77d93", "project_id": "cpcargo-aee14",
"storage_bucket": "cpcargo-77d93.firebasestorage.app" "storage_bucket": "cpcargo-aee14.firebasestorage.app"
}, },
"client": [ "client": [
{ {
"client_info": { "client_info": {
"mobilesdk_app_id": "1:628048576398:android:f93293c00f463267a92edf", "mobilesdk_app_id": "1:1030089382290:android:668f0669ad4ac3f74dc94b",
"android_client_info": { "android_client_info": {
"package_name": "uz.felix.cpost" "package_name": "uz.felix.cpost"
} }
@@ -15,7 +15,7 @@
"oauth_client": [], "oauth_client": [],
"api_key": [ "api_key": [
{ {
"current_key": "AIzaSyBnFzHK6XAjxzcQAsg0hFbeRcon8ZMDvVw" "current_key": "AIzaSyBEwWi1TuZBNj2hkFGGIaWZNNDCoiC__lE"
} }
], ],
"services": { "services": {
@@ -26,4 +26,4 @@
} }
], ],
"configuration_version": "1" "configuration_version": "1"
} }

View File

@@ -4,6 +4,9 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.VIBRATE" />
<application <application
android:name=".MainApplication" android:name=".MainApplication"

View File

@@ -15,6 +15,7 @@ buildscript {
classpath("com.android.tools.build:gradle") classpath("com.android.tools.build:gradle")
classpath("com.facebook.react:react-native-gradle-plugin") classpath("com.facebook.react:react-native-gradle-plugin")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin")
classpath('com.google.gms:google-services:4.4.2')
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

View File

@@ -1,9 +1,41 @@
/** /**
* @format * @format
*/ */
import notifee, { AndroidImportance } from '@notifee/react-native';
import messaging from '@react-native-firebase/messaging';
import { AppRegistry } from 'react-native'; import { AppRegistry } from 'react-native';
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({
id: 'default',
name: 'Umumiy bildirishnomalar',
sound: 'default',
importance: AndroidImportance.HIGH,
});
await notifee.displayNotification({
title:
remoteMessage.notification?.title ||
remoteMessage.data?.title ||
'Yangi xabar',
body:
remoteMessage.notification?.body ||
remoteMessage.data?.body ||
'Matn yoq',
android: {
channelId,
smallIcon: 'ic_launcher',
sound: 'default',
pressAction: {
id: 'default',
},
},
});
});
AppRegistry.registerComponent(appName, () => App); AppRegistry.registerComponent(appName, () => App);

1152
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -14,12 +14,15 @@
}, },
"dependencies": { "dependencies": {
"@hookform/resolvers": "^5.2.0", "@hookform/resolvers": "^5.2.0",
"@notifee/react-native": "^9.1.8",
"@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-community/geolocation": "^3.4.0", "@react-native-community/geolocation": "^3.4.0",
"@react-native-community/image-editor": "^4.3.0", "@react-native-community/image-editor": "^4.3.0",
"@react-native-community/push-notification-ios": "^1.11.0", "@react-native-community/push-notification-ios": "^1.11.0",
"@react-native-firebase/app": "^23.2.0",
"@react-native-firebase/messaging": "^23.2.0",
"@react-native-picker/picker": "^2.11.1", "@react-native-picker/picker": "^2.11.1",
"@react-native/new-app-screen": "0.80.1", "@react-native/new-app-screen": "0.80.1",
"@react-navigation/native": "^7.1.17", "@react-navigation/native": "^7.1.17",
@@ -41,7 +44,9 @@
"react-native-biometrics": "^3.0.1", "react-native-biometrics": "^3.0.1",
"react-native-config": "^1.5.6", "react-native-config": "^1.5.6",
"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-dotenv": "^3.4.11", "react-native-dotenv": "^3.4.11",
"react-native-fs": "^2.20.0",
"react-native-geolocation-service": "^5.3.1", "react-native-geolocation-service": "^5.3.1",
"react-native-gesture-handler": "^2.28.0", "react-native-gesture-handler": "^2.28.0",
"react-native-gesture-password": "^0.4.0", "react-native-gesture-password": "^0.4.0",
@@ -64,6 +69,7 @@
"react-native-safe-area-context": "^5.6.0", "react-native-safe-area-context": "^5.6.0",
"react-native-screens": "^4.14.1", "react-native-screens": "^4.14.1",
"react-native-send-intent": "^1.3.0", "react-native-send-intent": "^1.3.0",
"react-native-share": "^12.2.0",
"react-native-skia": "^0.0.1", "react-native-skia": "^0.0.1",
"react-native-smooth-pincode-input": "^1.0.9", "react-native-smooth-pincode-input": "^1.0.9",
"react-native-splash-screen": "^3.3.0", "react-native-splash-screen": "^3.3.0",

View File

@@ -11,3 +11,6 @@ export const RESEND_OTP = '/mobile/auth/resend-otp';
export const CALENDAR = '/mobile/calendar'; export const CALENDAR = '/mobile/calendar';
export const PACKETS = '/mobile/packets'; export const PACKETS = '/mobile/packets';
export const GET_ME = '/mobile/profile/me'; export const GET_ME = '/mobile/profile/me';
export const GET_EXCHANGES = '/exchanges';
export const GET_WARHOUSES = '/mobile/cargo-reference-book/warehouse-address';
export const GET_REFERENCE = '/mobile/cargo-reference-book/info';

View File

@@ -6,12 +6,20 @@ export interface registerPayload {
phoneNumber: string; phoneNumber: string;
recommend: string; recommend: string;
branchId: number; branchId: number;
fcmToken: string;
deviceId: string;
deviceType: string;
deviceName: string;
address: string;
} }
export interface otpPayload { export interface otpPayload {
phoneNumber: string; phoneNumber: string;
otp: string; otp: string;
otpType: 'LOGIN' | 'RESET_PASSWORD' | 'REGISTRATION'; otpType: 'LOGIN' | 'RESET_PASSWORD' | 'REGISTRATION';
fcmToken: string;
deviceId: string;
deviceName: string;
} }
export interface resendPayload { export interface resendPayload {
phoneNumber: string; phoneNumber: string;
@@ -21,8 +29,11 @@ export interface resendPayload {
export interface loginPayload { export interface loginPayload {
phoneNumber: string; phoneNumber: string;
passportSerial: string; passportSerial: string;
// passportNumber: string;
branchId: number; branchId: number;
fcmToken: string;
deviceId: string;
deviceType: string;
deviceName: string;
} }
export interface getMeData { export interface getMeData {

View File

@@ -0,0 +1,19 @@
import axiosInstance from 'api/axios';
import { GET_EXCHANGES } from 'api/URL';
export interface ExchangeType {
code: string;
date: string;
id: number;
nominal: number;
rate: number;
}
const exchanges_api = {
async getExchanges(): Promise<ExchangeType[]> {
const data = await axiosInstance.get(GET_EXCHANGES);
return data.data;
},
};
export default exchanges_api;

View File

@@ -0,0 +1,26 @@
import axiosInstance from 'api/axios';
import { GET_REFERENCE } from 'api/URL';
export interface ReferenceType {
id: number;
title: string;
shortDescription: string;
price: number;
exchange: string;
unit: string;
unitValue: number;
}
const reference_api = {
async getReference(params: {
cargoType: 'AVIA' | 'AUTO';
}): Promise<ReferenceType[]> {
const res = await axiosInstance.get(GET_REFERENCE, {
params: {
cargoType: params.cargoType,
},
});
return res.data;
},
};
export default reference_api;

View File

@@ -0,0 +1,16 @@
import axiosInstance from 'api/axios';
import { GET_WARHOUSES } from 'api/URL';
const warhouses_api = {
async getWarhouses(params: { cargoType: 'AUTO' | 'AVIA' }): Promise<any> {
const data = await axiosInstance.get<any>(GET_WARHOUSES, {
params: {
...params,
},
});
return data.data;
},
};
export default warhouses_api;

View File

@@ -9,30 +9,37 @@ import {
View, View,
} from 'react-native'; } from 'react-native';
import { import {
Asset,
ImagePickerResponse, ImagePickerResponse,
launchImageLibrary, launchImageLibrary,
MediaType, MediaType,
} from 'react-native-image-picker'; } from 'react-native-image-picker';
import Download from 'svg/Download'; import Download from 'svg/Download';
interface FileData { export interface FileData {
uri: string; uri: string;
name: string; name: string;
type: string; type: string;
base64: string; base64: string;
} }
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;
} }
const SingleFileDrop: React.FC<SingleFileDropProps> = ({ const SingleFileDrop: React.FC<SingleFileDropProps> = ({
title, title,
onFileSelected, onFileSelected,
type = 'image/png',
}) => { }) => {
const [selectedImage, setSelectedImage] = useState<string | null>(null); const [selectedImage, setSelectedImage] = useState<string | null>(null);
const { t } = useTranslation(); const { t } = useTranslation();
const imagePickerOptions = useMemo( const imagePickerOptions = useMemo(
() => ({ () => ({
mediaType: 'photo' as MediaType, mediaType: 'photo' as MediaType,
@@ -49,29 +56,27 @@ const SingleFileDrop: React.FC<SingleFileDropProps> = ({
return; return;
} }
if (response.assets && response.assets[0]) { const asset: Asset | undefined = response.assets?.[0];
const asset = response.assets[0]; if (!asset || !asset.uri || !asset.type || !asset.base64) return;
if (!asset.uri || !asset.type || !asset.base64) return;
// faqat PNG fayllarni qabul qilish // faqat belgilangan tipdagi fayllarni qabul qilish
if (asset.type !== 'image/png') { if (!asset.type.startsWith('image/')) {
Alert.alert('Xato', 'Faqat PNG fayllarni yuklashingiz mumkin'); Alert.alert('Xato', 'Faqat rasm fayllarni yuklashingiz mumkin');
return; return;
}
setSelectedImage(asset.uri);
const fileData: FileData = {
uri: asset.uri,
name: asset.fileName || 'image.png',
type: asset.type,
base64: `data:${asset.type};base64,${asset.base64}`,
};
onFileSelected?.(fileData);
} }
setSelectedImage(asset.uri);
const fileData: FileData = {
uri: asset.uri,
name: asset.fileName || `file.${asset.type.split('/')[1]}`,
type: asset.type,
base64: `data:${asset.type};base64,${asset.base64}`,
};
onFileSelected?.(fileData);
}, },
[onFileSelected], [onFileSelected, type],
); );
const openGallery = useCallback((): void => { const openGallery = useCallback((): void => {
@@ -101,17 +106,8 @@ const SingleFileDrop: React.FC<SingleFileDropProps> = ({
<> <>
<View style={styles.innerContainer}> <View style={styles.innerContainer}>
<View style={styles.topContent}> <View style={styles.topContent}>
{selectedImage ? ( <UploadIcon />
<Image <Text style={styles.sectionTitle}>{title}</Text>
source={{ uri: selectedImage }}
style={styles.previewImage}
/>
) : (
<>
<UploadIcon />
<Text style={styles.sectionTitle}>{title}</Text>
</>
)}
</View> </View>
<View style={styles.bottomContent}> <View style={styles.bottomContent}>
@@ -158,11 +154,6 @@ const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
}, },
iconText: {
fontSize: 20,
color: '#007bff',
fontWeight: 'bold',
},
sectionTitle: { sectionTitle: {
fontSize: 16, fontSize: 16,
fontWeight: '500', fontWeight: '500',
@@ -191,7 +182,6 @@ const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
marginVertical: 16, marginVertical: 16,
position: 'relative',
flexDirection: 'row', flexDirection: 'row',
}, },
dividerLine: { dividerLine: {
@@ -203,18 +193,15 @@ const styles = StyleSheet.create({
paddingHorizontal: 12, paddingHorizontal: 12,
fontSize: 14, fontSize: 14,
color: '#999', color: '#999',
zIndex: 1,
}, },
innerContainer: { innerContainer: {
flex: 1, flex: 1,
justifyContent: 'space-between', justifyContent: 'space-between',
width: '100%', width: '100%',
}, },
topContent: { topContent: {
alignItems: 'center', alignItems: 'center',
}, },
bottomContent: { bottomContent: {
width: '100%', width: '100%',
alignItems: 'center', alignItems: 'center',

View File

@@ -5,7 +5,6 @@ import {
Dimensions, Dimensions,
Image, Image,
Linking, Linking,
Platform,
StyleSheet, StyleSheet,
Text, Text,
TouchableOpacity, TouchableOpacity,
@@ -98,14 +97,12 @@ const Navbar = () => {
/> />
</TouchableOpacity> */} </TouchableOpacity> */}
{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} /> */}
{/* <View style={styles.bellDot} /> */} </TouchableOpacity>
</TouchableOpacity>
)}
</View> </View>
</View> </View>

View File

@@ -193,7 +193,7 @@ const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
}, },
brand: { brand: {
fontWeight: '700', fontWeight: '500',
color: '#fff', color: '#fff',
}, },
}); });

View File

@@ -170,6 +170,8 @@
"som": "сум", "som": "сум",
"Umumiy narx": "Общая цена", "Umumiy narx": "Общая цена",
"Yopish": "Закрыть", "Yopish": "Закрыть",
"Manzilingizni kiriting": "Введите свой адрес",
"Toshkent Shahri, Mirzo Ulug'bek tumani...": "г. Ташкент, Мирзо-Улугбекский район...",
"Passportlarim": "Мои паспорта", "Passportlarim": "Мои паспорта",
"Hali pasport qo'shilmagan": "Паспорт еще не добавлен", "Hali pasport qo'shilmagan": "Паспорт еще не добавлен",
"Yangi pasport qo'shish uchun tugmani bosing": "Нажмите кнопку, чтобы добавить новый паспорт", "Yangi pasport qo'shish uchun tugmani bosing": "Нажмите кнопку, чтобы добавить новый паспорт",

View File

@@ -16,6 +16,8 @@
"7 ta raqam kerak": "7 ta raqam kerak", "7 ta raqam kerak": "7 ta raqam kerak",
"Filialni tanlang": "Filialni tanlang", "Filialni tanlang": "Filialni tanlang",
"Ro'yxatdan o'tish": "Ro'yxatdan o'tish", "Ro'yxatdan o'tish": "Ro'yxatdan o'tish",
"Manzilingizni kiriting": "Manzilingizni kiriting",
"Toshkent Shahri, Mirzo Ulug'bek tumani...": "Toshkent Shahri, Mirzo Ulug'bek tumani...",
"Ism": "Ism", "Ism": "Ism",
"Ismingiz": "Ismingiz", "Ismingiz": "Ismingiz",
"Familiya": "Familiya", "Familiya": "Familiya",

View File

@@ -1,4 +1,6 @@
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';
@@ -17,9 +19,9 @@ 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';
@@ -36,27 +38,57 @@ 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<{
fcmToken: string;
deviceId: string;
deviceName: string;
} | 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 app = getApp();
const messaging = getMessaging(app);
const getDeviceData = async () => {
try {
const fcmToken = await getToken(messaging);
return {
fcmToken,
deviceId: await DeviceInfo.getUniqueId(),
deviceName: await DeviceInfo.getDeviceName(),
};
} catch (e) {
console.log('Xato:', e);
return null;
}
};
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 => {
await AsyncStorage.setItem('token', res.data.accessToken); await AsyncStorage.setItem('token', res.data.accessToken);
navigation.navigate('Home'); navigation.navigate('Home');
setErrorConfirm(null); setErrorConfirm(null);
console.log(res);
}, },
onError: (err: any) => { onError: (err: any) => {
setErrorConfirm(err?.response.data.message); setErrorConfirm(err?.response?.data?.message || 'Xato yuz berdi');
}, },
}); });
const { mutate: resendMutate } = useMutation({ const { mutate: resendMutate } = useMutation({
mutationFn: (payload: resendPayload) => authApi.resendOtp(payload), mutationFn: (payload: resendPayload) => authApi.resendOtp(payload),
onSuccess: async res => { onSuccess: async () => {
setTimer(60); setTimer(60);
setCanResend(false); setCanResend(false);
setCode(new Array(OTP_LENGTH).fill('')); setCode(new Array(OTP_LENGTH).fill(''));
@@ -64,17 +96,14 @@ const Confirm = () => {
setErrorConfirm(null); setErrorConfirm(null);
}, },
onError: (err: any) => { onError: (err: any) => {
setErrorConfirm(err?.response.data.message); setErrorConfirm(err?.response?.data?.message || 'Xato yuz berdi');
}, },
}); });
const openModal = useModalStore(state => state.openModal);
useEffect(() => { useEffect(() => {
let interval: NodeJS.Timeout | null = null; let interval: NodeJS.Timeout | null = null;
if (timer > 0) { if (timer > 0) {
interval = setInterval(() => { interval = setInterval(() => setTimer(prev => prev - 1), 1000);
setTimer(prevTimer => prevTimer - 1);
}, 1000);
} else { } else {
setCanResend(true); setCanResend(true);
if (interval) clearInterval(interval); if (interval) clearInterval(interval);
@@ -104,20 +133,21 @@ const Confirm = () => {
}; };
const handleResendCode = () => { const handleResendCode = () => {
resendMutate({ resendMutate({ phoneNumber, otpType: 'LOGIN' });
phoneNumber: phoneNumber,
otpType: 'LOGIN',
});
}; };
const handleVerifyCode = () => { const handleVerifyCode = () => {
const enteredCode = code.join(''); const enteredCode = code.join('');
mutate({ if (firebaseToken) {
phoneNumber: phoneNumber, mutate({
otp: String(enteredCode), phoneNumber,
otpType: 'LOGIN', otp: enteredCode,
}); otpType: 'LOGIN',
// navigation.navigate('Home'); deviceId: firebaseToken.deviceId,
deviceName: firebaseToken.deviceName,
fcmToken: firebaseToken.fcmToken,
});
}
}; };
return ( return (
@@ -147,15 +177,13 @@ const Confirm = () => {
<Text style={styles.title}>{t('Tasdiqlash kodini kiriting')}</Text> <Text style={styles.title}>{t('Tasdiqlash kodini kiriting')}</Text>
<Text style={styles.message}> <Text style={styles.message}>
{phoneNumber} {t('raqamiga yuborilgan')} {OTP_LENGTH}{' '} {phoneNumber} {t('raqamiga yuborilgan')} {OTP_LENGTH}{' '}
{t('xonali kodni kiriting.')} {t('xonali kodni kiriting.')}{' '}
</Text> </Text>
<View style={styles.otpContainer}> <View style={styles.otpContainer}>
{code.map((digit, index) => ( {code.map((digit, index) => (
<TextInput <TextInput
key={index} key={index}
ref={ref => { ref={ref => (inputRefs.current[index] = ref)}
inputRefs.current[index] = ref;
}}
style={styles.otpInput} style={styles.otpInput}
keyboardType="number-pad" keyboardType="number-pad"
maxLength={1} maxLength={1}
@@ -166,7 +194,7 @@ const Confirm = () => {
/> />
))} ))}
</View> </View>
{errorConfirm !== null && ( {errorConfirm && (
<Text style={styles.errorText}>{errorConfirm}</Text> <Text style={styles.errorText}>{errorConfirm}</Text>
)} )}
<View style={styles.resendContainer}> <View style={styles.resendContainer}>
@@ -210,10 +238,7 @@ const Confirm = () => {
}; };
const styles = StyleSheet.create({ const styles = StyleSheet.create({
safeArea: { safeArea: { flex: 1, backgroundColor: '#f8f9fa' },
flex: 1,
backgroundColor: '#f8f9fa',
},
errorText: { errorText: {
color: 'red', color: 'red',
fontSize: 14, fontSize: 14,
@@ -221,33 +246,22 @@ const styles = StyleSheet.create({
marginTop: 10, marginTop: 10,
textAlign: 'center', textAlign: 'center',
}, },
buttonTextDisabled: { buttonTextDisabled: { color: 'white' },
color: 'white',
},
langContainer: { langContainer: {
width: '100%', width: '100%',
marginTop: 10, marginTop: 10,
display: 'flex',
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center', alignItems: 'center',
paddingHorizontal: 16, paddingHorizontal: 16,
}, },
headerTitle: {
fontSize: 18,
fontWeight: 'bold',
color: '#333',
},
container: { container: {
flex: 1, flex: 1,
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
padding: 20, padding: 20,
}, },
content: { content: { width: '100%', alignItems: 'center' },
width: '100%',
alignItems: 'center',
},
title: { title: {
fontSize: 24, fontSize: 24,
fontWeight: 'bold', fontWeight: 'bold',
@@ -280,24 +294,10 @@ const styles = StyleSheet.create({
color: '#333', color: '#333',
backgroundColor: '#fff', backgroundColor: '#fff',
}, },
resendContainer: { resendContainer: { marginBottom: 30, marginTop: 20 },
marginBottom: 30, timerText: { fontSize: 15, color: '#999' },
marginTop: 20, resendButton: { paddingVertical: 10, paddingHorizontal: 20, borderRadius: 8 },
}, resendButtonText: { fontSize: 15, color: '#007bff', fontWeight: 'bold' },
timerText: {
fontSize: 15,
color: '#999',
},
resendButton: {
paddingVertical: 10,
paddingHorizontal: 20,
borderRadius: 8,
},
resendButtonText: {
fontSize: 15,
color: '#007bff',
fontWeight: 'bold',
},
verifyButton: { verifyButton: {
backgroundColor: '#007bff', backgroundColor: '#007bff',
paddingVertical: 15, paddingVertical: 15,
@@ -311,11 +311,7 @@ const styles = StyleSheet.create({
shadowOpacity: 0.2, shadowOpacity: 0.2,
shadowRadius: 4, shadowRadius: 4,
}, },
verifyButtonText: { verifyButtonText: { color: '#fff', fontSize: 18, fontWeight: '600' },
color: '#fff',
fontSize: 18,
fontWeight: '600',
},
}); });
export default Confirm; export default Confirm;

View File

@@ -1,4 +1,6 @@
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, useQuery } from '@tanstack/react-query';
@@ -6,7 +8,13 @@ import { authApi } from 'api/auth';
import { loginPayload } from 'api/auth/type'; import { loginPayload } from 'api/auth/type';
import { Branch, branchApi } from 'api/branch'; import { Branch, branchApi } from 'api/branch';
import formatPhone from 'helpers/formatPhone'; import formatPhone from 'helpers/formatPhone';
import React, { useCallback, useMemo, useRef, useState } from 'react'; import 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 {
@@ -20,6 +28,7 @@ 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';
@@ -50,6 +59,36 @@ const Login = () => {
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: loginPayload) => authApi.login(payload), mutationFn: (payload: loginPayload) => authApi.login(payload),
@@ -84,6 +123,10 @@ const Login = () => {
passportSerial: `${data.passportSeriya.toUpperCase()}${ passportSerial: `${data.passportSeriya.toUpperCase()}${
data.passportNumber data.passportNumber
}`, }`,
fcmToken: firebaseToken?.fcmToken || '',
deviceId: firebaseToken?.deviceId || '',
deviceType: firebaseToken?.deviceType || '',
deviceName: firebaseToken?.deviceName || '',
}); });
// navigation.navigate('Login-Confirm'); // navigation.navigate('Login-Confirm');
setUser({ setUser({

View File

@@ -4,6 +4,7 @@ import { z } from 'zod';
export const FirstStepSchema = z.object({ export const FirstStepSchema = z.object({
firstName: z.string().min(3, "Eng kamida 3ta belgi bo'lishi kerak"), firstName: z.string().min(3, "Eng kamida 3ta belgi bo'lishi kerak"),
lastName: z.string().min(3, "Eng kamida 3ta belgi bo'lishi kerak"), lastName: z.string().min(3, "Eng kamida 3ta belgi bo'lishi kerak"),
address: z.string().min(3, "Eng kamida 3ta belgi bo'lishi kerak"),
phoneNumber: z.string().min(12, 'Xato raqam kiritildi'), phoneNumber: z.string().min(12, 'Xato raqam kiritildi'),
branchId: z.number().min(1, 'Filialni tanlang'), branchId: z.number().min(1, 'Filialni tanlang'),
recommend: z.string().min(1, 'Majburiy maydon'), recommend: z.string().min(1, 'Majburiy maydon'),

View File

@@ -1,4 +1,6 @@
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';
@@ -17,6 +19,7 @@ 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 { useModalStore } from 'screens/auth/registeration/lib/modalStore';
@@ -46,6 +49,34 @@ 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<{
fcmToken: string;
deviceId: string;
deviceName: string;
} | null>();
const app = getApp();
const messaging = getMessaging(app);
const getDeviceData = async () => {
try {
const fcmToken = await getToken(messaging);
return {
fcmToken,
deviceId: await DeviceInfo.getUniqueId(),
deviceName: await DeviceInfo.getDeviceName(),
};
} catch (e) {
console.log('Xato:', e);
return null;
}
};
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 => {
@@ -54,6 +85,7 @@ const Confirm = ({
setErrorConfirm(null); setErrorConfirm(null);
}, },
onError: (err: any) => { onError: (err: any) => {
console.dir(err);
setErrorConfirm(err?.response.data.message); setErrorConfirm(err?.response.data.message);
}, },
}); });
@@ -116,11 +148,16 @@ const Confirm = ({
const handleVerifyCode = () => { const handleVerifyCode = () => {
const enteredCode = code.join(''); const enteredCode = code.join('');
mutate({ if (firebaseToken) {
phoneNumber: phoneNumber, mutate({
otp: String(enteredCode), phoneNumber,
otpType: 'REGISTRATION', otp: enteredCode,
}); otpType: 'REGISTRATION',
deviceId: firebaseToken.deviceId,
deviceName: firebaseToken.deviceName,
fcmToken: firebaseToken.fcmToken,
});
}
}; };
return ( return (

View File

@@ -1,6 +1,8 @@
'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,
@@ -27,6 +29,7 @@ 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 { import {
@@ -64,14 +67,43 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
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 => {
console.dir(err);
setError('Xatolik yuz berdi'); setError('Xatolik yuz berdi');
}, },
}); });
@@ -93,6 +125,7 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
resolver: zodResolver(FirstStepSchema), resolver: zodResolver(FirstStepSchema),
defaultValues: { defaultValues: {
firstName: '', firstName: '',
address: '',
lastName: '', lastName: '',
recommend: '', recommend: '',
}, },
@@ -104,7 +137,18 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
lastName: data.lastName, lastName: data.lastName,
phoneNumber: data.phoneNumber, phoneNumber: data.phoneNumber,
}); });
mutate(data); mutate({
firstName: data.firstName,
lastName: data.lastName,
phoneNumber: data.phoneNumber,
recommend: data.recommend,
branchId: data.branchId,
address: data.address,
fcmToken: firebaseToken?.fcmToken || '',
deviceId: firebaseToken?.deviceId || '',
deviceType: firebaseToken?.deviceType || '',
deviceName: firebaseToken?.deviceId || '',
});
}; };
useEffect(() => { useEffect(() => {
@@ -143,6 +187,7 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
}).start(); }).start();
} }
}; };
return ( return (
<ImageBackground <ImageBackground
source={Logo} source={Logo}
@@ -314,6 +359,31 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
</View> </View>
)} )}
/> />
<Controller
control={control}
name="address"
render={({ field: { onChange, value } }) => (
<View>
<Text style={RegisterStyle.label}>
{t('Manzilingizni kiriting')}
</Text>
<TextInput
style={RegisterStyle.input}
placeholder={t(
"Toshkent Shahri, Mirzo Ulug'bek tumani...",
)}
placeholderTextColor={'#D8DADC'}
onChangeText={onChange}
value={value}
/>
{errors.lastName && (
<Text style={RegisterStyle.errorText}>
{t(errors.lastName.message || '')}
</Text>
)}
</View>
)}
/>
<Controller <Controller
control={control} control={control}
name="recommend" name="recommend"

View File

@@ -35,6 +35,7 @@ import ArrowLeft from 'svg/ArrowLeft';
import Calendar from 'svg/Calendar'; import Calendar from 'svg/Calendar';
import { RootStackParamList } from 'types/types'; import { RootStackParamList } from 'types/types';
import { SecondStepFormType, SecondStepSchema } from '../lib/form'; import { SecondStepFormType, SecondStepSchema } from '../lib/form';
import { useUserStore } from '../lib/userstore';
import { RegisterStyle } from './styled'; import { RegisterStyle } from './styled';
interface FileData { interface FileData {
@@ -54,6 +55,7 @@ const SecondStep = () => {
const passportNumberRef = useRef<TextInput>(null); const passportNumberRef = useRef<TextInput>(null);
const [checkboxAnimation] = useState(new Animated.Value(1)); const [checkboxAnimation] = useState(new Animated.Value(1));
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState('');
const { firstName, lastName } = useUserStore(state => state);
const navigation = const navigation =
useNavigation<NativeStackNavigationProp<RootStackParamList, 'Login'>>(); useNavigation<NativeStackNavigationProp<RootStackParamList, 'Login'>>();
@@ -111,7 +113,7 @@ const SecondStep = () => {
const isoBirthDate = `${y}-${m.padStart(2, '0')}-${d.padStart(2, '0')}`; const isoBirthDate = `${y}-${m.padStart(2, '0')}-${d.padStart(2, '0')}`;
mutate({ mutate({
fullName: data.passportSeriya.toUpperCase(), fullName: `${firstName} ${lastName}`,
birthDate: isoBirthDate, birthDate: isoBirthDate,
passportSerial: `${data.passportSeriya.toUpperCase()}${ passportSerial: `${data.passportSeriya.toUpperCase()}${
data.passportNumber data.passportNumber

View File

@@ -24,8 +24,6 @@ const SelectAuth = () => {
const navigation = useNavigation<LoginScreenNavigationProp>(); const navigation = useNavigation<LoginScreenNavigationProp>();
const { width } = useWindowDimensions(); const { width } = useWindowDimensions();
const isSmallScreen = width < 360;
return ( return (
<SafeAreaView style={{ flex: 1 }}> <SafeAreaView style={{ flex: 1 }}>
<View style={styles.container}> <View style={styles.container}>
@@ -41,20 +39,15 @@ const SelectAuth = () => {
style={[ style={[
styles.logoImage, styles.logoImage,
{ {
width: isSmallScreen ? 120 : 250, width: 180,
height: isSmallScreen ? 120 : 200, height: 180,
borderRadius: 20000,
}, },
]} ]}
/> />
<Text <Text style={[styles.logoText, { fontSize: 24 }]}>CPOST</Text>
style={[styles.logoText, { fontSize: isSmallScreen ? 22 : 50 }]}
>
CPOST
</Text>
</View> </View>
<Text style={[styles.title, { fontSize: isSmallScreen ? 20 : 24 }]}> <Text style={[styles.title, { fontSize: 24 }]}>
{t('Royxatdan otganmisz')} {t('Royxatdan otganmisz')}
</Text> </Text>
@@ -110,17 +103,18 @@ const styles = StyleSheet.create({
}, },
logoWrapper: { logoWrapper: {
alignItems: 'center', alignItems: 'center',
marginBottom: 40, marginBottom: 20,
}, },
logoImage: { logoImage: {
resizeMode: 'stretch', resizeMode: 'stretch',
}, },
logoText: { logoText: {
fontWeight: '700', fontWeight: '500',
marginTop: 4,
color: '#28A7E8', color: '#28A7E8',
}, },
title: { title: {
fontWeight: '600', fontWeight: '500',
textAlign: 'center', textAlign: 'center',
color: '#28A7E8', color: '#28A7E8',
marginBottom: 20, marginBottom: 20,

View File

@@ -7,7 +7,6 @@ import {
StyleSheet, StyleSheet,
Text, Text,
TouchableOpacity, TouchableOpacity,
useWindowDimensions,
View, View,
} from 'react-native'; } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
@@ -21,9 +20,6 @@ type NavigationProp = NativeStackNavigationProp<RootStackParamList>;
const SelectLangPage = () => { const SelectLangPage = () => {
const navigation = useNavigation<NavigationProp>(); const navigation = useNavigation<NavigationProp>();
const { width } = useWindowDimensions();
const isSmallScreen = width < 380;
const selectLanguage = async (lang: 'uz' | 'ru') => { const selectLanguage = async (lang: 'uz' | 'ru') => {
await changeLanguage(lang); await changeLanguage(lang);
@@ -45,24 +41,17 @@ const SelectLangPage = () => {
style={[ style={[
styles.logoImage, styles.logoImage,
{ {
width: isSmallScreen ? 120 : 250, width: 180,
height: isSmallScreen ? 120 : 200, height: 180,
borderRadius: 20000,
}, },
]} ]}
/> />
<Text <Text style={[styles.logoText, { fontSize: 24 }]}>CPOST</Text>
style={[styles.logoText, { fontSize: isSmallScreen ? 24 : 40 }]}
>
CPOST
</Text>
</View> </View>
<Text style={[styles.title, { fontSize: isSmallScreen ? 18 : 24 }]}> <Text style={[styles.title, { fontSize: 24 }]}>
Tilni tanlang{' '} Tilni tanlang{' '}
<Text <Text style={[styles.title, { fontSize: 18 }]}>
style={[styles.title, { fontSize: isSmallScreen ? 14 : 18 }]}
>
(Выберите язык) (Выберите язык)
</Text> </Text>
</Text> </Text>
@@ -118,11 +107,11 @@ const styles = StyleSheet.create({
marginBottom: 8, marginBottom: 8,
}, },
logoText: { logoText: {
fontWeight: '700', fontWeight: '500',
color: '#28A7E8', color: '#28A7E8',
}, },
title: { title: {
fontWeight: '600', fontWeight: '500',
textAlign: 'center', textAlign: 'center',
color: '#28A7E8', color: '#28A7E8',
marginBottom: 24, marginBottom: 24,

View File

@@ -51,11 +51,27 @@ const Branches = (props: BranchesProps) => {
navigation.navigate('ListBranches', { branchId: e.id }) navigation.navigate('ListBranches', { branchId: e.id })
} }
> >
<View> <View
<Text style={styles.title}>{e.name}</Text> style={{
<Text style={styles.subtitle}>{e.address}</Text> flexDirection: 'row',
justifyContent: 'space-between',
width: '100%',
alignItems: 'center',
}}
>
<View
style={{
flexDirection: 'column',
justifyContent: 'flex-start',
width: '80%',
alignItems: 'flex-start',
}}
>
<Text style={styles.title}>{e.name}</Text>
<Text style={styles.subtitle}>{e.address}</Text>
</View>
<ArrowRightUnderline color="#28A7E8" />
</View> </View>
<ArrowRightUnderline color="#28A7E8" />
</TouchableOpacity> </TouchableOpacity>
))} ))}
</ScrollView> </ScrollView>
@@ -89,14 +105,14 @@ const styles = StyleSheet.create({
elevation: 1, elevation: 1,
}, },
title: { title: {
fontSize: 18, fontSize: 16,
paddingHorizontal: 5, paddingHorizontal: 5,
fontWeight: '600', fontWeight: '600',
color: '#000', color: '#000',
marginBottom: 6, marginBottom: 6,
}, },
subtitle: { subtitle: {
fontSize: 16, fontSize: 14,
paddingHorizontal: 5, paddingHorizontal: 5,
fontWeight: '500', fontWeight: '500',
color: '#000000B2', color: '#000000B2',

View File

@@ -25,46 +25,58 @@ const ListBranches = () => {
queryFn: branchApi.branchList, queryFn: branchApi.branchList,
}); });
// ✅ ObjectManager orqali markerlarni yuboramiz
useEffect(() => { useEffect(() => {
if (webViewReady && route.params?.branchId) { if (webViewReady && data && data.length) {
const branch = data && data.find(b => b.id === route?.params?.branchId); const features = data.map(branch => ({
type: 'Feature',
id: branch.id,
geometry: {
type: 'Point',
coordinates: [branch.latitude, branch.longitude],
},
properties: { balloonContent: branch.name },
}));
const jsCode = `
if (window.objectManager) {
map.geoObjects.remove(window.objectManager);
}
window.objectManager = new ymaps.ObjectManager({ clusterize: true });
window.objectManager.add({
type: 'FeatureCollection',
features: ${JSON.stringify(features)}
});
window.objectManager.objects.events.add('click', function (e) {
const id = e.get('objectId');
window.ReactNativeWebView.postMessage(JSON.stringify({
type: 'branch_click',
id
}));
});
map.geoObjects.add(window.objectManager);
true;
`;
webviewRef.current?.injectJavaScript(jsCode);
}
}, [webViewReady, data]);
// ✅ branchId bilan markerni ochish
useEffect(() => {
if (webViewReady && route.params?.branchId && data) {
const branch = data.find(b => b.id === route.params?.branchId);
if (branch) { if (branch) {
setSelectedBranch(branch); setSelectedBranch(branch);
setModalVisible(true); setModalVisible(true);
const jsCode = ` const jsCode = `
map.setCenter([${branch.latitude}, ${branch.longitude}], 14); map.setCenter([${branch.latitude}, ${branch.longitude}], 14);
placemark${branch.id}?.balloon.open();
true; true;
`; `;
webviewRef.current?.injectJavaScript(jsCode); webviewRef.current?.injectJavaScript(jsCode);
} }
} }
}, [webViewReady, route.params]); }, [webViewReady, route.params, data]);
const generatePlacemarks = () => {
if (!data || !data.length) return '';
return data
.map(
branch => `
var placemark${branch.id} = new ymaps.Placemark([${branch.latitude}, ${branch.longitude}], {
balloonContent: '${branch.name}'
}, {
iconLayout: 'default#image',
iconImageSize: [30, 30],
iconImageOffset: [-15, -30]
});
placemark${branch.id}.events.add('click', function () {
window.ReactNativeWebView.postMessage(JSON.stringify({
type: 'branch_click',
id: ${branch.id}
}));
});
map.geoObjects.add(placemark${branch.id});
`,
)
.join('\n');
};
const html = ` const html = `
<!DOCTYPE html> <!DOCTYPE html>
@@ -96,15 +108,12 @@ const ListBranches = () => {
zoom: 6, zoom: 6,
controls: [] controls: []
}); });
${generatePlacemarks()}
window.ReactNativeWebView.postMessage("map_ready"); window.ReactNativeWebView.postMessage("map_ready");
}); });
function zoomIn() { function zoomIn() {
if (map) map.setZoom(map.getZoom() + 1); if (map) map.setZoom(map.getZoom() + 1);
} }
function zoomOut() { function zoomOut() {
if (map) map.setZoom(map.getZoom() - 1); if (map) map.setZoom(map.getZoom() - 1);
} }
@@ -143,8 +152,9 @@ const ListBranches = () => {
} }
const parsed = JSON.parse(message); const parsed = JSON.parse(message);
if (parsed.type === 'branch_click') { if (parsed.type === 'branch_click') {
const branchItem = const branchItem = data?.find(
data && data.find((b: Branch) => b.id === parsed.id); (b: Branch) => b.id === parsed.id,
);
if (branchItem) { if (branchItem) {
setSelectedBranch(branchItem); setSelectedBranch(branchItem);
setModalVisible(true); setModalVisible(true);

View File

@@ -1,5 +1,7 @@
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 { useQuery } from '@tanstack/react-query';
import reference_api from 'api/reference';
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';
@@ -16,39 +18,38 @@ interface CargoPricesProps {}
const CargoPrices = (props: CargoPricesProps) => { const CargoPrices = (props: CargoPricesProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [activeTab, setActiveTab] = React.useState<'avia' | 'auto'>('avia'); const [activeTab, setActiveTab] = React.useState<'AVIA' | 'AUTO'>('AVIA');
const navigation = useNavigation<NativeStackNavigationProp<any>>(); const navigation = useNavigation<NativeStackNavigationProp<any>>();
const { data, refetch } = useQuery({
queryKey: ['reference_list'],
queryFn: () => reference_api.getReference({ cargoType: activeTab }),
});
React.useEffect(() => {
refetch();
}, [activeTab]);
return ( return (
<LayoutTwo title={t('Kargo narxlari')}> <LayoutTwo title={t('Kargo narxlari')}>
<ScrollView style={{ flex: 1 }}> <ScrollView style={{ flex: 1 }}>
<View style={styles.container}> <View style={styles.container}>
<Tabs activeTab={activeTab} setActiveTab={setActiveTab} /> <Tabs activeTab={activeTab} setActiveTab={setActiveTab} />
{activeTab === 'avia' && ( {activeTab === 'AVIA' && (
<View style={{ marginTop: 10, gap: 10, marginBottom: 20 }}> <View style={{ marginTop: 10, gap: 10, marginBottom: 20 }}>
<View style={styles.cardWhite}> {data &&
<View style={styles.priceCard}> data.map(ref => (
<Text style={styles.titleBlack}> <View style={styles.cardWhite}>
{t('Oddiy maxsulotlar')} <View style={styles.priceCard}>
</Text> <Text style={styles.titleBlack}>{ref.title}</Text>
<Text style={[styles.titleBlack, { fontSize: 16 }]}> <Text style={[styles.titleBlack, { fontSize: 16 }]}>
9.2$ /1kg {ref.price}$/{ref.unitValue}
</Text> {ref.unit}
</View> </Text>
<Text style={styles.desc}> </View>
{t('(Katta miqdordagi yuklar kelishuv asosida)')} <Text style={styles.desc}>{ref.shortDescription}</Text>
</Text> </View>
</View> ))}
<View style={styles.cardWhite}>
<View style={styles.priceCard}>
<Text style={styles.titleBlack}>{t('Brend buyumlar')}</Text>
<Text style={[styles.titleBlack, { fontSize: 16 }]}>
12.2$ /1kg
</Text>
</View>
<Text style={styles.desc}>
{t('(Karobka,dokumentlar bilan birga)')}
</Text>
</View>
<View style={styles.cardWhite}> <View style={styles.cardWhite}>
<View style={styles.priceCard}> <View style={styles.priceCard}>
<Text style={styles.titleBlack}> <Text style={styles.titleBlack}>
@@ -127,34 +128,21 @@ const CargoPrices = (props: CargoPricesProps) => {
</TouchableOpacity> </TouchableOpacity>
</View> </View>
)} )}
{activeTab === 'auto' && ( {activeTab === 'AUTO' && (
<View style={{ marginTop: 20, gap: 10, marginBottom: 20 }}> <View style={{ marginTop: 20, gap: 10, marginBottom: 20 }}>
<View style={styles.cardWhite}> {data &&
<View style={styles.priceCard}> data.map(ref => (
<Text style={styles.titleBlack}>0-30 kg</Text> <View style={styles.cardWhite}>
<Text style={[styles.titleBlack, { fontSize: 16 }]}> <View style={styles.priceCard}>
7$ /1kg <Text style={styles.titleBlack}>{ref.title}</Text>
</Text> <Text style={[styles.titleBlack, { fontSize: 16 }]}>
</View> {ref.price}$/{ref.unitValue}
</View> {ref.unit}
<View style={styles.cardWhite}> </Text>
<View style={styles.priceCard}> </View>
<Text style={styles.titleBlack}>30-100kg</Text> <Text style={styles.desc}>{ref.shortDescription}</Text>
<Text style={[styles.titleBlack, { fontSize: 16 }]}> </View>
6.5$ /1kg ))}
</Text>
</View>
</View>
<View style={styles.cardWhite}>
<View style={styles.priceCard}>
<Text style={styles.titleBlack}>
100kg {t('dan boshlab')}
</Text>
<Text style={[styles.titleBlack, { fontSize: 16 }]}>
6$ /1kg
</Text>
</View>
</View>
<View style={styles.cardWhite}> <View style={styles.cardWhite}>
<Text style={styles.desc}> <Text style={styles.desc}>
{t( {t(

View File

@@ -12,7 +12,7 @@ import TabsAuto from './TabsAuto';
import TabsAvia from './TabsAvia'; import TabsAvia from './TabsAvia';
const Home = () => { const Home = () => {
const [activeTab, setActiveTab] = useState<'avia' | 'auto'>('avia'); const [activeTab, setActiveTab] = useState<'AVIA' | 'AUTO'>('AVIA');
const [refreshing, setRefreshing] = useState(false); const [refreshing, setRefreshing] = useState(false);
const { width: screenWidth } = useWindowDimensions(); const { width: screenWidth } = useWindowDimensions();
const scale = screenWidth < 360 ? 0.85 : 1; const scale = screenWidth < 360 ? 0.85 : 1;
@@ -52,9 +52,9 @@ const Home = () => {
); );
const activeTabContent = useMemo(() => { const activeTabContent = useMemo(() => {
if (activeTab === 'auto') { if (activeTab === 'AUTO') {
return <TabsAuto />; return <TabsAuto />;
} else if (activeTab === 'avia') { } else if (activeTab === 'AVIA') {
return <TabsAvia />; return <TabsAvia />;
} }
return null; return null;

View File

@@ -8,7 +8,7 @@ import { HomeStyle } from './styled';
interface Props { interface Props {
activeTab: string; activeTab: string;
setActiveTab: Dispatch<SetStateAction<'avia' | 'auto'>>; setActiveTab: Dispatch<SetStateAction<'AVIA' | 'AUTO'>>;
} }
const Tabs = ({ activeTab, setActiveTab }: Props) => { const Tabs = ({ activeTab, setActiveTab }: Props) => {
@@ -30,12 +30,12 @@ const Tabs = ({ activeTab, setActiveTab }: Props) => {
const tabsData = useMemo( const tabsData = useMemo(
() => [ () => [
{ {
type: 'avia' as const, type: 'AVIA' as const,
label: t('Avia orqali yetkazish'), label: t('Avia orqali yetkazish'),
logo: AviaLogo, logo: AviaLogo,
}, },
{ {
type: 'auto' as const, type: 'AUTO' as const,
label: t('Avto orqali yetkazish'), label: t('Avto orqali yetkazish'),
logo: AutoLogo, logo: AutoLogo,
}, },

View File

@@ -1,6 +1,7 @@
import Clipboard from '@react-native-clipboard/clipboard'; import Clipboard from '@react-native-clipboard/clipboard';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { authApi } from 'api/auth'; import { authApi } from 'api/auth';
import warhouses_api from 'api/warhouses';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
@@ -26,35 +27,9 @@ const TabsAuto = () => {
queryFn: authApi.getMe, queryFn: authApi.getMe,
}); });
const addressList = [
{
id: 1,
title: 'China (Auto)',
postCode: '510440',
addressInfo: [
`收件人吴彦祖AT(${getMe?.autoCargoId})`,
'地址广州市白云区龙归街道南村攀龙六巷30号AТ(N209)',
' 电话: 18023847617',
`1004 ${getMe?.aviaCargoId}`,
],
},
// {
// id: 2,
// title: 'Korea (Auto)',
// postCode: '520550',
// addressInfo: [
// '收件人李小龙AT(M312)',
// '地址∶深圳市南山区科技园科发路',
// '18号AT(M312)',
// '电话: 13800008888',
// ],
// },
];
const handleCopy = (info: string[]) => { const handleCopy = (info: string[]) => {
if (getMe?.status === 'active') { if (getMe?.status === 'ACTIVE') {
const textToCopy = info.join('\n'); Clipboard.setString(info.join('\n'));
Clipboard.setString(textToCopy);
Toast.show({ Toast.show({
type: 'success', type: 'success',
text1: t('Nusxa olingan'), text1: t('Nusxa olingan'),
@@ -73,43 +48,54 @@ const TabsAuto = () => {
} }
}; };
const { data } = useQuery({
queryKey: ['warhouses'],
queryFn: () => warhouses_api.getWarhouses({ cargoType: 'AUTO' }),
});
const formattedData =
data?.map((item: string | null) => {
if (!item) return '';
const withAutoCargo = item.replace('%s', getMe?.autoCargoId || '');
const withAviaCargo = withAutoCargo.replace(
'%s',
getMe?.aviaCargoId || '',
);
return withAviaCargo;
}) || [];
return ( return (
<FlatList <FlatList
data={addressList} data={formattedData}
horizontal horizontal
keyExtractor={item => item.id.toString()} keyExtractor={(_, index) => index.toString()}
pagingEnabled pagingEnabled
showsHorizontalScrollIndicator={false} showsHorizontalScrollIndicator={false}
snapToInterval={cardWidth + 10} // +10: marginRight snapToInterval={cardWidth + 10}
decelerationRate="fast" decelerationRate="fast"
contentContainerStyle={{ contentContainerStyle={{
paddingHorizontal: (screenWidth - cardWidth) / 2, paddingHorizontal: (screenWidth - cardWidth) / 2,
padding: 10, padding: 10,
}} }}
renderItem={({ item, index }) => { renderItem={({ item, index }) => {
const isLast = index === addressList.length - 1; const isLast = index === formattedData.length - 1;
return ( return (
<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} />
<Text style={styles.title}>{item.title}</Text> <Text style={styles.title}>China (Auto)</Text>
</View> </View>
<View style={styles.infoId}> <View style={styles.infoId}>
<View style={{ gap: 4 * scale, width: '90%' }}> <View style={{ gap: 4 * scale, width: '90%' }}>
{item.addressInfo.map((line, idx) => ( <Text style={styles.infoText}>{item}</Text>
<Text key={idx} style={[styles.infoText]}>
{line}
</Text>
))}
</View> </View>
<TouchableOpacity onPress={() => handleCopy(item.addressInfo)}> <TouchableOpacity onPress={() => handleCopy([item])}>
<Copy color="#28A7E8" width={24 * scale} height={24 * scale} /> <Copy color="#28A7E8" width={24 * scale} height={24 * scale} />
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<View style={styles.postCodeWrapper}>
<Text style={styles.postCodeText}>{t('Auto post kodi')}: </Text>
<Text style={styles.postCode}>{item.postCode}</Text>
</View>
</View> </View>
); );
}} }}

View File

@@ -1,6 +1,7 @@
import Clipboard from '@react-native-clipboard/clipboard'; import Clipboard from '@react-native-clipboard/clipboard';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { authApi } from 'api/auth'; import { authApi } from 'api/auth';
import warhouses_api from 'api/warhouses';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
@@ -21,31 +22,6 @@ const TabsAvia = () => {
queryFn: authApi.getMe, queryFn: authApi.getMe,
}); });
const addressList = [
{
id: 1,
title: 'China (Avia)',
postCode: ' 101399',
addressInfo: [
`收货人: ${getMe?.aviaCargoId}`,
'手机号码: 18335530701',
'北京市顺义区南法信旭辉空港中心C座',
`1004 ${getMe?.aviaCargoId}`,
],
},
// {
// id: 2,
// title: 'Korea (Avia)',
// postCode: '510440',
// addressInfo: [
// '收货人: M312',
// '手机号码: 18335530701',
// '北京市顺义区南法信旭辉空港中心C座',
// '1004 N209',
// ],
// },
];
const { width: screenWidth } = useWindowDimensions(); const { width: screenWidth } = useWindowDimensions();
const scale = screenWidth < 360 ? 0.85 : 1; const scale = screenWidth < 360 ? 0.85 : 1;
const cardWidth = screenWidth * 0.95; const cardWidth = screenWidth * 0.95;
@@ -53,13 +29,12 @@ const TabsAvia = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const handleCopy = (info: string[]) => { const handleCopy = (info: string[]) => {
if (getMe?.status === 'active') { if (getMe?.status === 'ACTIVE') {
const textToCopy = info.join('\n'); Clipboard.setString(info.join('\n'));
Clipboard.setString(textToCopy);
Toast.show({ Toast.show({
type: 'success', type: 'success',
text1: t('Nusxa olingan'), text1: t('Nusxa olingan'),
text2: t('Avia manzili nusxalandi!'), text2: t('Avto manzili nusxalandi!'),
position: 'top', position: 'top',
visibilityTime: 2000, visibilityTime: 2000,
}); });
@@ -74,43 +49,54 @@ const TabsAvia = () => {
} }
}; };
const { data } = useQuery({
queryKey: ['warhouses'],
queryFn: () => warhouses_api.getWarhouses({ cargoType: 'AVIA' }),
});
const formattedData =
data?.map((item: string | null) => {
if (!item) return '';
const withAutoCargo = item.replace('%s', getMe?.autoCargoId || '');
const withAviaCargo = withAutoCargo.replace(
'%s',
getMe?.aviaCargoId || '',
);
return withAviaCargo;
}) || [];
return ( return (
<FlatList <FlatList
data={addressList} data={formattedData}
horizontal horizontal
keyExtractor={item => item.id.toString()} keyExtractor={(_, index) => index.toString()}
pagingEnabled pagingEnabled
showsHorizontalScrollIndicator={false} showsHorizontalScrollIndicator={false}
snapToInterval={cardWidth + 10} // +10: marginRight snapToInterval={cardWidth + 10}
decelerationRate="fast" decelerationRate="fast"
contentContainerStyle={{ contentContainerStyle={{
paddingHorizontal: (screenWidth - cardWidth) / 2, paddingHorizontal: (screenWidth - cardWidth) / 2,
padding: 10, padding: 10,
}} }}
renderItem={({ item, index }) => { renderItem={({ item, index }) => {
const isLast = index === addressList.length - 1; const isLast = index === formattedData.length - 1;
return ( return (
<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} />
<Text style={styles.title}>{item.title}</Text> <Text style={styles.title}>China (AVIA)</Text>
</View> </View>
<View style={styles.infoId}> <View style={styles.infoId}>
<View style={{ gap: 4 * scale }}> <View style={{ gap: 4 * scale, width: '90%' }}>
{item.addressInfo.map((line, idx) => ( <Text style={styles.infoText}>{item}</Text>
<Text key={idx} style={styles.infoText}>
{line}
</Text>
))}
</View> </View>
<TouchableOpacity onPress={() => handleCopy(item.addressInfo)}> <TouchableOpacity onPress={() => handleCopy([item])}>
<Copy color="#28A7E8" width={24 * scale} height={24 * scale} /> <Copy color="#28A7E8" width={24 * scale} height={24 * scale} />
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<View style={styles.postCodeWrapper}>
<Text style={styles.postCodeText}>{t('Avia post kodi')}: </Text>
<Text style={styles.postCode}>{item.postCode}</Text>
</View>
</View> </View>
); );
}} }}
@@ -139,7 +125,7 @@ const makeStyles = (scale: number, cardWidth: number, screenWidth: number) =>
alignItems: 'center', alignItems: 'center',
}, },
card: { card: {
height: 220 * scale, // height: 220 * scale,
width: cardWidth, width: cardWidth,
backgroundColor: '#28a8e82c', backgroundColor: '#28a8e82c',
borderRadius: 12 * scale, borderRadius: 12 * scale,

View File

@@ -36,12 +36,53 @@ const MyPassport = ({ getMe, myPassport }: Props) => {
}); });
}; };
const getStatusMeta = (status?: string) => {
const key = (status || '').toLowerCase();
switch (key) {
case 'active':
return { label: t('Faol'), bg: '#E6F7EE', fg: '#1F9254' };
case 'pending':
return { label: t('Kutilmoqda'), bg: '#FFF7E6', fg: '#B26A00' };
case 'inactive':
case 'blocked':
return { label: t('Faol emas'), bg: '#FDECEF', fg: '#A61D24' };
default:
return { label: t('Faol emas'), bg: '#EDF2F7', fg: '#A61D24' };
}
};
return ( return (
<View style={styles.container}> <View style={styles.container}>
{myPassport && {myPassport &&
myPassport.map(data => ( myPassport.map(data => (
<View style={styles.card} key={data.passportPin}> <View style={styles.card} key={data.passportPin}>
<Text style={styles.title}>{t('Passport malumotlarim')}</Text> <View
style={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<Text style={styles.title}>{t('Passport malumotlarim')}</Text>
{getMe?.status && (
<View
style={[
styles.statusBadge,
{ backgroundColor: getStatusMeta(getMe.status).bg },
]}
>
<Text
style={[
styles.statusText,
{ color: getStatusMeta(getMe.status).fg },
]}
>
{getStatusMeta(getMe.status).label}
</Text>
</View>
)}
</View>
<View style={styles.infoCard}> <View style={styles.infoCard}>
<View <View
style={[ style={[
@@ -115,7 +156,9 @@ const MyPassport = ({ getMe, myPassport }: Props) => {
<View <View
style={[ style={[
styles.info, styles.info,
{ flexBasis: '100%', alignItems: 'flex-start' }, isSmallScreen
? { flexBasis: '100%', alignItems: 'flex-start' }
: { flexBasis: '48%', alignItems: 'flex-start' },
]} ]}
> >
<Text style={[styles.infoTitle]}>{t('Telefon raqami')}</Text> <Text style={[styles.infoTitle]}>{t('Telefon raqami')}</Text>
@@ -125,25 +168,13 @@ const MyPassport = ({ getMe, myPassport }: Props) => {
<View <View
style={[ style={[
styles.info, styles.info,
{ flexBasis: '100%', alignItems: 'flex-end' }, isSmallScreen
? { flexBasis: '100%', alignItems: 'flex-start' }
: { flexBasis: '48%', alignItems: 'flex-end' },
]} ]}
> >
<Text <Text style={[styles.infoTitle]}>{t('Limit')}</Text>
style={[ <Text style={[styles.infoText]}>{data.availableLimit}$</Text>
styles.infoTitle,
!isSmallScreen && { textAlign: 'right' },
]}
>
{t('Limit')}
</Text>
<Text
style={[
styles.infoText,
!isSmallScreen && { textAlign: 'right' },
]}
>
{data.availableLimit}$
</Text>
</View> </View>
</View> </View>
</View> </View>
@@ -158,6 +189,10 @@ const styles = StyleSheet.create({
alignSelf: 'center', alignSelf: 'center',
marginTop: 10, marginTop: 10,
}, },
statusText: {
fontSize: 13,
fontWeight: '600',
},
card: { card: {
backgroundColor: '#FFFFFF', backgroundColor: '#FFFFFF',
padding: 20, padding: 20,
@@ -184,6 +219,19 @@ const styles = StyleSheet.create({
marginBottom: 15, marginBottom: 15,
gap: 5, gap: 5,
}, },
statusBadge: {
alignSelf: 'center',
marginTop: 5,
marginBottom: 10,
paddingHorizontal: 14,
paddingVertical: 4,
borderRadius: 20,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.15,
shadowRadius: 3,
elevation: 3, // Android uchun chiroyli korinishi
},
infoTitle: { infoTitle: {
color: '#979797', color: '#979797',
fontSize: 16, fontSize: 16,

View File

@@ -6,7 +6,6 @@ import { useTranslation } from 'react-i18next';
import { import {
Alert, Alert,
Linking, Linking,
Platform,
StyleSheet, StyleSheet,
Text, Text,
TouchableOpacity, TouchableOpacity,
@@ -80,21 +79,19 @@ 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' && ( <TouchableOpacity
<TouchableOpacity style={[
style={[ styles.card,
styles.card, { flexDirection: 'row', alignItems: 'center', gap: 10 },
{ flexDirection: 'row', alignItems: 'center', gap: 10 }, ]}
]} onPress={() => navigation.navigate('Notifications')}
onPress={() => navigation.navigate('Notifications')} >
> <View style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}> <Bell color="#373737" width={24} height={24} />
<Bell color="#373737" width={24} height={24} /> <Text style={styles.title}>{t('Bildirishnomalar')}</Text>
<Text style={styles.title}>{t('Bildirishnomalar')}</Text> </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

@@ -1,3 +1,4 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import LayoutTwo from 'components/LayoutTwo'; import LayoutTwo from 'components/LayoutTwo';
import NoResult from 'components/NoResult'; import NoResult from 'components/NoResult';
import * as React from 'react'; import * as React from 'react';
@@ -10,12 +11,13 @@ import {
TouchableOpacity, TouchableOpacity,
} from 'react-native'; } from 'react-native';
import Clock from 'svg/Clock'; import Clock from 'svg/Clock';
import { fakeNotifications, NotificationsData } from '../lib/data'; import { NotificationsData } from '../lib/data';
import NotificationsModal from './NotificationsModal'; import NotificationsModal from './NotificationsModal';
interface NotificationsProps {} const Notifications = () => {
const [notifications, setNotifications] = React.useState<NotificationsData[]>(
const Notifications = (props: NotificationsProps) => { [],
);
const [refreshing, setRefreshing] = React.useState(false); const [refreshing, setRefreshing] = React.useState(false);
const [modalVisible, setModalVisible] = React.useState(false); const [modalVisible, setModalVisible] = React.useState(false);
const { t } = useTranslation(); const { t } = useTranslation();
@@ -39,13 +41,19 @@ const Notifications = (props: NotificationsProps) => {
[refreshing, onRefresh], [refreshing, onRefresh],
); );
if (!(fakeNotifications.length > 0)) { const loadNotifications = async () => {
return ( const stored = await AsyncStorage.getItem('notifications');
<LayoutTwo title={t('Bildirishnomalar')}> if (stored) {
<NoResult /> setNotifications(JSON.parse(stored));
</LayoutTwo> }
); };
}
React.useEffect(() => {
const unsubscribe = loadNotifications();
return () => {
unsubscribe;
};
}, []);
return ( return (
<LayoutTwo title={t('Bildirishnomalar')}> <LayoutTwo title={t('Bildirishnomalar')}>
@@ -53,16 +61,20 @@ const Notifications = (props: NotificationsProps) => {
keyboardShouldPersistTaps="handled" keyboardShouldPersistTaps="handled"
refreshControl={refreshControl} refreshControl={refreshControl}
> >
{fakeNotifications.map(item => ( {notifications.length > 0 ? (
<TouchableOpacity notifications.map(item => (
onPress={() => openModal(item)} <TouchableOpacity
style={styles.card} onPress={() => openModal(item)}
key={item.id} style={styles.card}
> key={item.id}
<Text style={styles.text}>{item.message}</Text> >
<Clock color="#000000" /> <Text style={styles.text}>{item.message}</Text>
</TouchableOpacity> <Clock color="#000000" />
))} </TouchableOpacity>
))
) : (
<NoResult />
)}
</ScrollView> </ScrollView>
<NotificationsModal <NotificationsModal
visible={modalVisible} visible={modalVisible}
@@ -76,18 +88,6 @@ const Notifications = (props: NotificationsProps) => {
export default Notifications; export default Notifications;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: {
flex: 1,
},
header: {
paddingHorizontal: 20,
paddingTop: 5,
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: '#333',
},
card: { card: {
borderBottomWidth: 1, borderBottomWidth: 1,
borderColor: '#D8DADC', borderColor: '#D8DADC',

View File

@@ -1,6 +1,7 @@
import Clipboard from '@react-native-clipboard/clipboard'; import Clipboard from '@react-native-clipboard/clipboard';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { authApi } from 'api/auth'; import { authApi } from 'api/auth';
import warhouses_api from 'api/warhouses';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
@@ -53,9 +54,8 @@ const TabsAutoWarehouses = () => {
]; ];
const handleCopy = (info: string[]) => { const handleCopy = (info: string[]) => {
if (getMe?.status === 'active') { if (getMe?.status === 'ACTIVE') {
const textToCopy = info.join('\n'); Clipboard.setString(info.join('\n'));
Clipboard.setString(textToCopy);
Toast.show({ Toast.show({
type: 'success', type: 'success',
text1: t('Nusxa olingan'), text1: t('Nusxa olingan'),
@@ -74,39 +74,50 @@ const TabsAutoWarehouses = () => {
} }
}; };
const { data } = useQuery({
queryKey: ['warhouses'],
queryFn: () => warhouses_api.getWarhouses({ cargoType: 'AUTO' }),
});
const formattedData =
data?.map((item: string | null) => {
if (!item) return '';
const withAutoCargo = item.replace('%s', getMe?.autoCargoId || '');
const withAviaCargo = withAutoCargo.replace(
'%s',
getMe?.aviaCargoId || '',
);
return withAviaCargo;
}) || [];
return ( return (
<FlatList <FlatList
data={addressInfo} data={formattedData}
horizontal horizontal
keyExtractor={item => item.id.toString()} keyExtractor={(_, index) => index.toString()}
pagingEnabled pagingEnabled
showsHorizontalScrollIndicator={false} showsHorizontalScrollIndicator={false}
snapToInterval={cardWidth + 10} snapToInterval={cardWidth + 10}
decelerationRate="fast" decelerationRate="fast"
renderItem={({ item, index }) => { renderItem={({ item, index }) => {
const isLast = index === addressInfo.length - 1; const isLast = index === formattedData.length - 1;
return ( return (
<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} />
<Text style={styles.title}>{item.title}</Text> <Text style={styles.title}>China (Auto)</Text>
</View> </View>
<View style={styles.infoId}> <View style={styles.infoId}>
<View style={{ gap: 4 * scale, width: '90%' }}> <View style={{ gap: 4 * scale, width: '90%' }}>
{item.addressInfo.map((line, idx) => ( <Text style={styles.infoText}>{item}</Text>
<Text key={idx} style={styles.infoText}>
{line}
</Text>
))}
</View> </View>
<TouchableOpacity onPress={() => handleCopy(item.addressInfo)}> <TouchableOpacity onPress={() => handleCopy(item.addressInfo)}>
<Copy color="#28A7E8" width={24 * scale} height={24 * scale} /> <Copy color="#28A7E8" width={24 * scale} height={24 * scale} />
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<View style={styles.postCodeWrapper}>
<Text style={styles.postCodeText}>{t('Auto post kodi')}: </Text>
<Text style={styles.postCode}>{item.postCode}</Text>
</View>
</View> </View>
); );
}} }}
@@ -117,7 +128,6 @@ const TabsAutoWarehouses = () => {
const makeStyles = (scale: number, cardWidth: number, screenWidth: number) => const makeStyles = (scale: number, cardWidth: number, screenWidth: number) =>
StyleSheet.create({ StyleSheet.create({
card: { card: {
height: '100%',
width: cardWidth, width: cardWidth,
backgroundColor: '#28a8e82c', backgroundColor: '#28a8e82c',
borderRadius: 12 * scale, borderRadius: 12 * scale,

View File

@@ -1,6 +1,7 @@
import Clipboard from '@react-native-clipboard/clipboard'; import Clipboard from '@react-native-clipboard/clipboard';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { authApi } from 'api/auth'; import { authApi } from 'api/auth';
import warhouses_api from 'api/warhouses';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
@@ -53,13 +54,12 @@ const TabsAviaWarehouses = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const handleCopy = (info: string[]) => { const handleCopy = (info: string[]) => {
if (getMe?.status === 'active') { if (getMe?.status === 'ACTIVE') {
const textToCopy = info.join('\n'); Clipboard.setString(info.join('\n'));
Clipboard.setString(textToCopy);
Toast.show({ Toast.show({
type: 'success', type: 'success',
text1: t('Nusxa olingan'), text1: t('Nusxa olingan'),
text2: t('Avia manzili nusxalandi!'), text2: t('Avto manzili nusxalandi!'),
position: 'top', position: 'top',
visibilityTime: 2000, visibilityTime: 2000,
}); });
@@ -74,39 +74,50 @@ const TabsAviaWarehouses = () => {
} }
}; };
const { data } = useQuery({
queryKey: ['warhouses'],
queryFn: () => warhouses_api.getWarhouses({ cargoType: 'AVIA' }),
});
const formattedData =
data?.map((item: string | null) => {
if (!item) return '';
const withAutoCargo = item.replace('%s', getMe?.autoCargoId || '');
const withAviaCargo = withAutoCargo.replace(
'%s',
getMe?.aviaCargoId || '',
);
return withAviaCargo;
}) || [];
return ( return (
<FlatList <FlatList
data={addressList} data={formattedData}
horizontal horizontal
keyExtractor={item => item.id.toString()} keyExtractor={(_, index) => index.toString()}
pagingEnabled pagingEnabled
showsHorizontalScrollIndicator={false} showsHorizontalScrollIndicator={false}
snapToInterval={cardWidth + 10} // +10: marginRight snapToInterval={cardWidth + 10}
decelerationRate="fast" decelerationRate="fast"
renderItem={({ item, index }) => { renderItem={({ item, index }) => {
const isLast = index === addressList.length - 1; const isLast = index === formattedData.length - 1;
return ( return (
<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} />
<Text style={styles.title}>{item.title}</Text> <Text style={styles.title}>China (Auto)</Text>
</View> </View>
<View style={styles.infoId}> <View style={styles.infoId}>
<View style={{ gap: 4 * scale }}> <View style={{ gap: 4 * scale, width: '90%' }}>
{item.addressInfo.map((line, idx) => ( <Text style={styles.infoText}>{item}</Text>
<Text key={idx} style={styles.infoText}>
{line}
</Text>
))}
</View> </View>
<TouchableOpacity onPress={() => handleCopy(item.addressInfo)}> <TouchableOpacity onPress={() => handleCopy(item.addressInfo)}>
<Copy color="#28A7E8" width={24 * scale} height={24 * scale} /> <Copy color="#28A7E8" width={24 * scale} height={24 * scale} />
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<View style={styles.postCodeWrapper}>
<Text style={styles.postCodeText}>{t('Avia post kodi')}: </Text>
<Text style={styles.postCode}>{item.postCode}</Text>
</View>
</View> </View>
); );
}} }}
@@ -116,26 +127,7 @@ const TabsAviaWarehouses = () => {
const makeStyles = (scale: number, cardWidth: number, screenWidth: number) => const makeStyles = (scale: number, cardWidth: number, screenWidth: number) =>
StyleSheet.create({ StyleSheet.create({
container: {
height: 200,
width: '95%',
backgroundColor: '#28a8e82c',
margin: 'auto',
marginTop: 20,
borderRadius: 12,
padding: 12,
gap: 10,
},
scrollContainer: {
marginTop: 20,
paddingHorizontal: (screenWidth - cardWidth) / 2,
},
postCodeWrapper: {
flexDirection: 'row',
alignItems: 'center',
},
card: { card: {
height: 220 * scale,
width: cardWidth, width: cardWidth,
backgroundColor: '#28a8e82c', backgroundColor: '#28a8e82c',
borderRadius: 12 * scale, borderRadius: 12 * scale,
@@ -144,31 +136,37 @@ const makeStyles = (scale: number, cardWidth: number, screenWidth: number) =>
}, },
titleCard: { titleCard: {
flexDirection: 'row', flexDirection: 'row',
gap: 8, gap: 8 * scale,
alignItems: 'center', alignItems: 'center',
}, },
title: { title: {
fontSize: 20, fontSize: 20 * scale,
fontWeight: '600', fontWeight: '600',
color: '#101623CC', color: '#101623CC',
}, },
infoId: { infoId: {
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
marginVertical: 8 * scale,
}, },
infoText: { infoText: {
fontSize: 16, fontSize: 16 * scale,
color: '#28A7E8', color: '#28A7E8',
}, },
postCodeWrapper: {
flexDirection: 'row',
alignItems: 'center',
},
postCodeText: { postCodeText: {
fontSize: 16, fontSize: 16 * scale,
color: '#000000', color: '#000000',
fontWeight: '500', fontWeight: '500',
}, },
postCode: { postCode: {
fontSize: 16, fontSize: 16 * scale,
color: '#28A7E8', color: '#28A7E8',
fontWeight: '400', fontWeight: '400',
marginLeft: 4 * scale,
}, },
}); });

View File

@@ -1,8 +1,12 @@
import { useQuery } from '@tanstack/react-query';
import { authApi } from 'api/auth';
import SingleFileDrop from 'components/FileDrop'; import SingleFileDrop from 'components/FileDrop';
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 { import {
ActivityIndicator,
Image,
RefreshControl, RefreshControl,
ScrollView, ScrollView,
StyleSheet, StyleSheet,
@@ -10,14 +14,71 @@ import {
TouchableOpacity, TouchableOpacity,
View, View,
} from 'react-native'; } from 'react-native';
import Toast from 'react-native-toast-message';
import Shablon from 'screens/../../assets/bootsplash/shablon.jpg';
import TabsAutoWarehouses from './TabsAutoWarehouses'; import TabsAutoWarehouses from './TabsAutoWarehouses';
import TabsAviaWarehouses from './TabsAviaWarehouses'; import TabsAviaWarehouses from './TabsAviaWarehouses';
interface FileData {
uri: string;
name: string;
type: string;
base64: string;
}
interface WarehousesProps {} interface WarehousesProps {}
const botToken = '7768577881:AAGXGtOl2IiMImrsY6BZmksN9Rjeq2InlTo';
const Warehouses = (props: WarehousesProps) => { const Warehouses = (props: WarehousesProps) => {
const [refreshing, setRefreshing] = React.useState(false); const [refreshing, setRefreshing] = React.useState(false);
const { t } = useTranslation(); const { t } = useTranslation();
const [isLoading, setIsLoading] = React.useState(false);
const [backImage, setBackImage] = React.useState<FileData | null>(null);
const { data: getMe } = useQuery({
queryKey: ['getMe'],
queryFn: authApi.getMe,
});
const openTelegramWithImage = async () => {
const telegramApiUrl = `https://api.telegram.org/bot${botToken}/sendPhoto`;
const formData = new FormData();
formData.append('chat_id', '-1002950892822');
formData.append('photo', {
uri: backImage?.uri,
type: 'image/jpeg',
name: 'photo.jpg',
});
formData.append(
'caption',
`Foydalanuvchi ismi: ${getMe?.fullName}
Telefon nomer: +${getMe?.phone}
Cargo Idsi: ${getMe?.aviaCargoId}
`,
);
try {
setIsLoading(true);
await fetch(telegramApiUrl, {
method: 'POST',
body: formData,
});
Toast.show({
type: 'success',
text1: t("So'rovingiz jo'natilidi. Tez orada siz bilan bog'lanamiz"),
position: 'top',
visibilityTime: 2000,
});
} catch (error) {
Toast.show({
type: 'error',
text1: t('Xatolik yuz berdi'),
position: 'top',
visibilityTime: 2000,
});
} finally {
setIsLoading(false); // 👈 loading tugadi
}
};
const onRefresh = React.useCallback(() => { const onRefresh = React.useCallback(() => {
setRefreshing(true); setRefreshing(true);
@@ -72,9 +133,25 @@ const Warehouses = (props: WarehousesProps) => {
</Text> </Text>
</View> </View>
<Text style={styles.title}>{t('Skrenshot rasmini yuklang')}</Text> <Text style={styles.title}>{t('Skrenshot rasmini yuklang')}</Text>
<SingleFileDrop title={t('Rasmni shu yerga yuklang')} /> <Image
<TouchableOpacity style={styles.button}> source={Shablon}
<Text style={styles.btnText}>{t('Manzilni tekshirish')}</Text> style={{ width: '100%', height: 500, objectFit: 'cover' }}
/>
<SingleFileDrop
title={t('Rasmni shu yerga yuklang')}
onFileSelected={setBackImage}
type="image/*"
/>
<TouchableOpacity
style={[styles.button, isLoading && { opacity: 0.7 }]}
onPress={openTelegramWithImage}
disabled={isLoading}
>
{isLoading ? (
<ActivityIndicator size="small" color="#fff" />
) : (
<Text style={styles.btnText}>{t('Manzilni tekshirish')}</Text>
)}
</TouchableOpacity> </TouchableOpacity>
</View> </View>
</ScrollView> </ScrollView>

View File

@@ -24,9 +24,10 @@ const Payment = ({ packets }: Props) => {
const handlePaymentPress = useCallback( const handlePaymentPress = useCallback(
(item: any) => { (item: any) => {
const isPaid = item.paymentStatus === 'paid'; const isPaid =
item.paymentStatus === 'PAYED' || item.paymentStatus === 'PENDING';
navigation.navigate(isPaid ? 'PaymentQrCode' : 'PaymentMethod', { navigation.navigate(isPaid ? 'PaymentQrCode' : 'PaymentMethod', {
packets: item, // tanlangan itemni toliq yuboramiz packets: item,
}); });
}, },
[navigation], [navigation],
@@ -49,7 +50,8 @@ const Payment = ({ packets }: Props) => {
const renderPaymentCard = useCallback( const renderPaymentCard = useCallback(
(item: any) => { (item: any) => {
const isPaid = item.paymentStatus === 'paid'; const isPaid =
item.paymentStatus === 'PAYED' || item.paymentStatus === 'PENDING';
const cardStyle = [ const cardStyle = [
PaymentStyle.card, PaymentStyle.card,
{ borderColor: isPaid ? '#4CAF50' : '#D32F2F', borderWidth: 1.5 }, { borderColor: isPaid ? '#4CAF50' : '#D32F2F', borderWidth: 1.5 },

View File

@@ -101,23 +101,142 @@ const Wallet = () => {
if (isError || isErrorAvia) { if (isError || isErrorAvia) {
return ( return (
<Layout> <Layout>
<NoResult message="Xatolik yuz berdi" /> <ScrollView
refreshControl={refreshControl}
style={{ flex: 1 }}
contentContainerStyle={{ flexGrow: 1 }}
removeClippedSubviews={true}
keyboardShouldPersistTaps="handled"
>
<NoResult message="Xatolik yuz berdi" />
</ScrollView>
</Layout> </Layout>
); );
} }
if (selectedType === 'AUTO' && packets && !(packets?.data.length > 0)) {
return (
<Layout>
<ScrollView
refreshControl={refreshControl}
style={{ flex: 1 }}
contentContainerStyle={{ flexGrow: 1 }}
removeClippedSubviews={true}
keyboardShouldPersistTaps="handled"
>
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.title}>{t("To'lov")}</Text>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<TouchableOpacity
style={{
padding: 5,
backgroundColor: '#F3FAFF',
borderTopLeftRadius: 8,
borderBottomLeftRadius: 8,
}}
onPress={() => setSelectedType('AVIA')}
>
<Text
style={{
color: '#28A7E8',
fontSize: 14,
}}
>
AVIA
</Text>
</TouchableOpacity>
<View style={{ width: 1 }} />
<TouchableOpacity
style={{
padding: 5,
backgroundColor:
selectedType === 'AUTO' ? '#28A7E8' : '#F3FAFF',
borderTopRightRadius: 8,
borderBottomRightRadius: 8,
}}
onPress={() => setSelectedType('AUTO')}
>
<Text
style={{
color: selectedType === 'AUTO' ? '#fff' : '#28A7E8',
fontSize: 14,
fontWeight: '500',
}}
>
AUTO
</Text>
</TouchableOpacity>
</View>
</View>
<NoResult />
</View>
</ScrollView>
</Layout>
);
}
if ( if (
(packets && !(packets?.data.length > 0)) || selectedType === 'AVIA' &&
(packetsAvia && !(packetsAvia?.data.length > 0)) packetsAvia &&
!(packetsAvia?.data.length > 0)
) { ) {
return ( return (
<Layout> <Layout>
<View style={styles.container}> <ScrollView
<View style={styles.header}> refreshControl={refreshControl}
<Text style={styles.title}>{t("To'lov")}</Text> style={{ flex: 1 }}
contentContainerStyle={{ flexGrow: 1 }}
removeClippedSubviews={true}
keyboardShouldPersistTaps="handled"
>
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.title}>{t("To'lov")}</Text>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<TouchableOpacity
style={{
padding: 5,
backgroundColor:
selectedType === 'AVIA' ? '#28A7E8' : '#F3FAFF',
borderTopLeftRadius: 8,
borderBottomLeftRadius: 8,
}}
onPress={() => setSelectedType('AVIA')}
>
<Text
style={{
color: selectedType === 'AVIA' ? '#fff' : '#28A7E8',
fontSize: 14,
}}
>
AVIA
</Text>
</TouchableOpacity>
<View style={{ width: 1 }} />
<TouchableOpacity
style={{
padding: 5,
backgroundColor: '#F3FAFF',
borderTopRightRadius: 8,
borderBottomRightRadius: 8,
}}
onPress={() => setSelectedType('AUTO')}
>
<Text
style={{
color: '#28A7E8',
fontSize: 14,
fontWeight: '500',
}}
>
AUTO
</Text>
</TouchableOpacity>
</View>
</View>
<NoResult />
</View> </View>
<NoResult /> </ScrollView>
</View>
</Layout> </Layout>
); );
} }

View File

@@ -69,14 +69,12 @@ export const PaymentStyle = StyleSheet.create({
zIndex: 10, zIndex: 10,
}, },
modalBtn: { modalBtn: {
position: 'absolute',
height: 56, height: 56,
width: '100%', width: '100%',
margin: 'auto',
borderRadius: 8, borderRadius: 8,
justifyContent: 'center', justifyContent: 'center',
backgroundColor: '#28A7E8', backgroundColor: '#28A7E8',
bottom: 10,
right: 20,
}, },
btnText: { btnText: {
color: '#fff', color: '#fff',

View File

@@ -55,12 +55,10 @@ const ModalCard = ({
if (supported) { if (supported) {
await Linking.openURL(url); await Linking.openURL(url);
} else { } else {
// Agar app ocholmasa, default brauzerda ochishga urinadi
await Linking.openURL(url); await Linking.openURL(url);
} }
} catch (err) { } catch (err) {
console.error('Link xatolik:', err); console.error('Link xatolik:', err);
// Xato bolsa ham brauzer orqali ochishga urinish
try { try {
await Linking.openURL(url); await Linking.openURL(url);
} catch (err2) { } catch (err2) {
@@ -74,6 +72,7 @@ const ModalCard = ({
packetsApi.payPackets(id, { payType }), packetsApi.payPackets(id, { payType }),
onSuccess: res => { onSuccess: res => {
setIsVisible(false); setIsVisible(false);
console.log(res, 'url');
const url = res.data.paymentUrl; const url = res.data.paymentUrl;
openLink(url); openLink(url);
}, },
@@ -120,7 +119,6 @@ const ModalCard = ({
style={[styles.sheet, { transform: [{ translateY: slideAnim }] }]} style={[styles.sheet, { transform: [{ translateY: slideAnim }] }]}
> >
<View style={styles.sheetContent}> <View style={styles.sheetContent}>
{/* CLICK */}
<TouchableOpacity <TouchableOpacity
style={[ style={[
styles.paymentOption, styles.paymentOption,
@@ -129,7 +127,9 @@ const ModalCard = ({
selectedId === 'click' ? '#28A7E81A' : '#FFFFFF', selectedId === 'click' ? '#28A7E81A' : '#FFFFFF',
}, },
]} ]}
onPress={() => setSelectedId('click')} onPress={() => {
setPay('CLICK'), setSelectedId('click');
}}
> >
<View style={PaymentStyle.paymentCard}> <View style={PaymentStyle.paymentCard}>
<Click width={80} height={80} /> <Click width={80} height={80} />

View File

@@ -1,3 +1,5 @@
import { useMutation } from '@tanstack/react-query';
import packetsApi from 'api/packets';
import React, { useEffect, useRef } from 'react'; import React, { useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
@@ -26,6 +28,9 @@ interface ModalPayProps {
cardModal: boolean; cardModal: boolean;
setCardModal: React.Dispatch<React.SetStateAction<boolean>>; setCardModal: React.Dispatch<React.SetStateAction<boolean>>;
payModal: boolean; payModal: boolean;
paymentStatus: string;
packId: number;
paymentType: string | null;
setPayModal: React.Dispatch<React.SetStateAction<boolean>>; setPayModal: React.Dispatch<React.SetStateAction<boolean>>;
success: boolean; success: boolean;
setSuccess: React.Dispatch<React.SetStateAction<boolean>>; setSuccess: React.Dispatch<React.SetStateAction<boolean>>;
@@ -35,15 +40,31 @@ const ModalPay = ({
isModalVisible, isModalVisible,
setModalVisible, setModalVisible,
selectedId, selectedId,
paymentType,
setSelectedId, setSelectedId,
packId,
setCardModal, setCardModal,
setPayModal, setPayModal,
paymentStatus,
}: ModalPayProps) => { }: ModalPayProps) => {
const translateY = useRef(new Animated.Value(SCREEN_HEIGHT)).current; const translateY = useRef(new Animated.Value(SCREEN_HEIGHT)).current;
const opacity = useRef(new Animated.Value(0)).current; const opacity = useRef(new Animated.Value(0)).current;
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 { mutate, isPending } = useMutation({
mutationFn: ({ id, payType }: { id: number; payType: string }) =>
packetsApi.payPackets(id, { payType }),
onSuccess: res => {
console.log(res, 'url');
setPayModal(true);
},
onError: err => {
console.dir(err);
},
});
useEffect(() => { useEffect(() => {
if (isModalVisible) { if (isModalVisible) {
Animated.parallel([ Animated.parallel([
@@ -83,7 +104,9 @@ const ModalPay = ({
setLoad(true); setLoad(true);
setTimeout(() => { setTimeout(() => {
if (selectedId === 'pay') setPayModal(true); if (selectedId === 'pay') {
mutate({ id: packId, payType: 'CASH' });
}
if (selectedId === 'card') setCardModal(true); if (selectedId === 'card') setCardModal(true);
setModalVisible(false); setModalVisible(false);
@@ -114,7 +137,6 @@ const ModalPay = ({
}, },
]} ]}
> >
{/* CARD OPTION */}
<TouchableOpacity <TouchableOpacity
style={[ style={[
styles.option, styles.option,
@@ -154,50 +176,51 @@ const ModalPay = ({
)} )}
</View> </View>
</TouchableOpacity> </TouchableOpacity>
{paymentType !== 'CASH' && (
{/* CASH OPTION */} <TouchableOpacity
<TouchableOpacity
style={[
styles.option,
{
backgroundColor: selectedId === 'pay' ? '#28A7E81A' : '#fff',
},
]}
onPress={() => setSelectedId('pay')}
>
<View style={PaymentStyle.paymentCard}>
<Usd
color={selectedId == 'pay' ? '#28A7E8' : '#000000'}
width={28}
height={28}
colorCircle={selectedId == 'pay' ? '#28A7E8' : '#000000'}
/>
<Text
style={[
PaymentStyle.titleMethod,
{ color: selectedId == 'pay' ? '#28A7E8' : '#000' },
]}
>
{t('Naqt pul')}
</Text>
</View>
<View
style={[ style={[
PaymentStyle.select, styles.option,
{ {
backgroundColor: backgroundColor:
selectedId === 'pay' ? '#28A7E8' : '#FFFFFF', selectedId === 'pay' ? '#28A7E81A' : '#fff',
borderColor: selectedId === 'pay' ? '#28A7E8' : '#383838',
}, },
]} ]}
onPress={() => {
setSelectedId('pay');
}}
> >
{selectedId === 'pay' && ( <View style={PaymentStyle.paymentCard}>
<Check color="#fff" width={20} height={20} /> <Usd
)} color={selectedId == 'pay' ? '#28A7E8' : '#000000'}
</View> width={28}
</TouchableOpacity> height={28}
colorCircle={selectedId == 'pay' ? '#28A7E8' : '#000000'}
{/* BUTTON */} />
<Text
style={[
PaymentStyle.titleMethod,
{ color: selectedId == 'pay' ? '#28A7E8' : '#000' },
]}
>
{t('Naqt pul')}
</Text>
</View>
<View
style={[
PaymentStyle.select,
{
backgroundColor:
selectedId === 'pay' ? '#28A7E8' : '#FFFFFF',
borderColor: selectedId === 'pay' ? '#28A7E8' : '#383838',
},
]}
>
{selectedId === 'pay' && (
<Check color="#fff" width={20} height={20} />
)}
</View>
</TouchableOpacity>
)}
<TouchableOpacity <TouchableOpacity
style={[ style={[
PaymentStyle.modalBtn, PaymentStyle.modalBtn,
@@ -249,7 +272,7 @@ const styles = StyleSheet.create({
padding: 20, padding: 20,
borderTopLeftRadius: 20, borderTopLeftRadius: 20,
borderTopRightRadius: 20, borderTopRightRadius: 20,
minHeight: 250, minHeight: 'auto',
gap: 10, gap: 10,
}, },
option: { option: {

View File

@@ -27,6 +27,7 @@ const PaymentMethod = () => {
const [success, setSuccess] = React.useState(false); const [success, setSuccess] = React.useState(false);
const toggleModal = () => setModalVisible(true); const toggleModal = () => setModalVisible(true);
console.log(packets);
React.useEffect(() => { React.useEffect(() => {
if (payModal) { if (payModal) {
@@ -39,6 +40,9 @@ const PaymentMethod = () => {
<LayoutTwo title={t("To'lov usuli")}> <LayoutTwo title={t("To'lov usuli")}>
<PaymentProduct packet={packets!} /> <PaymentProduct packet={packets!} />
<ModalPay <ModalPay
paymentType={packets.paymentType}
packId={packets.id}
paymentStatus={packets.paymentStatus}
isModalVisible={isModalVisible} isModalVisible={isModalVisible}
selectedId={selectedId} selectedId={selectedId}
setModalVisible={setModalVisible} setModalVisible={setModalVisible}

View File

@@ -1,8 +1,9 @@
import { useQuery } from '@tanstack/react-query';
import exchanges_api from 'api/exchanges';
import * as React from 'react'; import * as React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Dimensions, ScrollView, Text, View } from 'react-native'; import { Dimensions, ScrollView, Text, View } from 'react-native';
import Svg, { Circle, Path } from 'react-native-svg'; import Svg, { Circle, Path } from 'react-native-svg';
import { fakePayments } from 'screens/wallet/payment/lib/data';
import Plane from 'svg/Plane'; import Plane from 'svg/Plane';
import { PaymentStyle } from '../../payment/ui/style'; import { PaymentStyle } from '../../payment/ui/style';
@@ -16,6 +17,11 @@ const PaymentProduct = ({ packet }: PaymentProductProps) => {
const isSmallScreen = screenWidth < 380; const isSmallScreen = screenWidth < 380;
const svgWidth = screenWidth * 0.8; const svgWidth = screenWidth * 0.8;
const { data } = useQuery({
queryKey: ['exchanges'],
queryFn: () => exchanges_api.getExchanges(),
});
return ( return (
<ScrollView <ScrollView
style={PaymentStyle.containerMethod} style={PaymentStyle.containerMethod}
@@ -53,8 +59,12 @@ const PaymentProduct = ({ packet }: PaymentProductProps) => {
: { flexBasis: '48%', alignItems: 'flex-start' }, : { flexBasis: '48%', alignItems: 'flex-start' },
]} ]}
> >
<Text style={PaymentStyle.titleMethod}>USD</Text> {data && (
<Text style={PaymentStyle.textMethod}>12.267 UZS</Text> <>
<Text style={PaymentStyle.titleMethod}>{data[0].code}</Text>
<Text style={PaymentStyle.textMethod}>{data[0].rate} UZS</Text>
</>
)}
</View> </View>
<View <View
@@ -69,7 +79,7 @@ const PaymentProduct = ({ packet }: PaymentProductProps) => {
<Text style={PaymentStyle.textMethod}>12.267 UZS/ kg</Text> <Text style={PaymentStyle.textMethod}>12.267 UZS/ kg</Text>
</View> </View>
<View {/* <View
style={[ style={[
PaymentStyle.info, PaymentStyle.info,
isSmallScreen isSmallScreen
@@ -79,18 +89,18 @@ const PaymentProduct = ({ packet }: PaymentProductProps) => {
> >
<Text style={PaymentStyle.titleMethod}>{t('Yetkazish vaqti')}</Text> <Text style={PaymentStyle.titleMethod}>{t('Yetkazish vaqti')}</Text>
<Text style={PaymentStyle.textMethod}>08.25.2025</Text> <Text style={PaymentStyle.textMethod}>08.25.2025</Text>
</View> </View> */}
<View <View
style={[ style={[
PaymentStyle.info, PaymentStyle.info,
isSmallScreen isSmallScreen
? { flexBasis: '48%', alignItems: 'flex-end' } ? { flexBasis: '48%', alignItems: 'flex-start' }
: { flexBasis: '48%', alignItems: 'flex-end' }, : { flexBasis: '48%', alignItems: 'flex-start' },
]} ]}
> >
<Text style={PaymentStyle.titleMethod}>Reys</Text> <Text style={PaymentStyle.titleMethod}>Reys</Text>
<Text style={[PaymentStyle.textMethod, { textAlign: 'right' }]}> <Text style={[PaymentStyle.textMethod, { textAlign: 'left' }]}>
{packet.packetName} {packet.packetName}
</Text> </Text>
</View> </View>
@@ -133,14 +143,14 @@ const PaymentProduct = ({ packet }: PaymentProductProps) => {
}} }}
/> />
</View> </View>
{fakePayments.map((item: any, index: number) => { {packet.items.map((item: any, index: number) => {
const price = Number(item.price); const price = Number(item.price);
const weight = Number(item.weight); const weight = Number(item.weight);
// const total = price * weight; const total = price * weight;
// formatlash: 0 decimal, som bilan // formatlash: 0 decimal, som bilan
const formattedPrice = price.toFixed(0); const formattedPrice = price.toFixed(0);
// const formattedTotal = total.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}>
@@ -160,7 +170,7 @@ const PaymentProduct = ({ packet }: PaymentProductProps) => {
</View> </View>
<View style={PaymentStyle.rowRight}> <View style={PaymentStyle.rowRight}>
<Text style={PaymentStyle.total}> <Text style={PaymentStyle.total}>
{/* {t('Umumiy narxi')}: {formattedTotal} {t('som')} */} {t('Umumiy narxi')}: {formattedTotal} {t('som')}
</Text> </Text>
</View> </View>
</View> </View>

View File

@@ -1,3 +1,6 @@
import { RouteProp, useRoute } from '@react-navigation/native';
import { useQuery } from '@tanstack/react-query';
import exchanges_api from 'api/exchanges';
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';
@@ -7,21 +10,48 @@ import {
ScrollView, ScrollView,
StyleSheet, StyleSheet,
Text, Text,
TouchableOpacity,
View, View,
} from 'react-native'; } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import Svg, { Circle, Path } from 'react-native-svg'; import Svg, { Circle, Path } from 'react-native-svg';
import { PaymentStyle } from 'screens/wallet/payment/ui/style'; import { PaymentStyle } from 'screens/wallet/payment/ui/style';
import ModalCard from 'screens/wallet/paymentMethod/ui/ModalCard';
import ModalPay from 'screens/wallet/paymentMethod/ui/ModalPay';
import ModalSuccess from 'screens/wallet/paymentMethod/ui/ModalSuccess';
import Plane from 'svg/Plane'; import Plane from 'svg/Plane';
import { fakeProducts } from '../lib/data';
interface PaymentQrCodeProps {} const PaymentQrCode = () => {
const PaymentQrCode = (props: PaymentQrCodeProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [selectedId, setSelectedId] = React.useState<'card' | 'pay' | null>(
null,
);
const route = useRoute<RouteProp<any, 'PaymentMethod'>>();
const packets = route.params?.packets;
const [selectedCard, setSelectedCard] = React.useState<
'click' | 'payme' | null
>(null);
const { data } = useQuery({
queryKey: ['exchanges'],
queryFn: () => exchanges_api.getExchanges(),
});
const { bottom } = useSafeAreaInsets();
const [isModalVisible, setModalVisible] = React.useState(false);
const toggleModal = () => setModalVisible(true);
const [cardModal, setCardModal] = React.useState(false);
const [payModal, setPayModal] = React.useState(false);
const [success, setSuccess] = React.useState(false);
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 svgWidthProduct = screenWidth * 1;
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=${encodeURIComponent(
packets.qrCode,
)}`;
return ( return (
<LayoutTwo title={t("To'lov usuli")}> <LayoutTwo title={t("To'lov usuli")}>
<ScrollView <ScrollView
@@ -60,8 +90,14 @@ const PaymentQrCode = (props: PaymentQrCodeProps) => {
: { flexBasis: '48%', alignItems: 'flex-start' }, : { flexBasis: '48%', alignItems: 'flex-start' },
]} ]}
> >
<Text style={PaymentStyle.titleMethod}>USD</Text> {data && (
<Text style={PaymentStyle.textMethod}>12.267 UZS</Text> <>
<Text style={PaymentStyle.titleMethod}>{data[0].code}</Text>
<Text style={PaymentStyle.textMethod}>
{data[0].rate} UZS
</Text>
</>
)}
</View> </View>
<View <View
@@ -76,7 +112,7 @@ const PaymentQrCode = (props: PaymentQrCodeProps) => {
<Text style={PaymentStyle.textMethod}>12.267 UZS/ kg</Text> <Text style={PaymentStyle.textMethod}>12.267 UZS/ kg</Text>
</View> </View>
<View {/* <View
style={[ style={[
PaymentStyle.info, PaymentStyle.info,
isSmallScreen isSmallScreen
@@ -87,19 +123,21 @@ const PaymentQrCode = (props: PaymentQrCodeProps) => {
<Text style={PaymentStyle.titleMethod}> <Text style={PaymentStyle.titleMethod}>
{t('Yetkazish vaqti')} {t('Yetkazish vaqti')}
</Text> </Text>
<Text style={PaymentStyle.textMethod}>08.25.2025</Text> <Text style={PaymentStyle.textMethod}>{}</Text>
</View> </View> */}
<View <View
style={[ style={[
PaymentStyle.info, PaymentStyle.info,
isSmallScreen isSmallScreen
? { flexBasis: '48%', alignItems: 'flex-end' } ? { flexBasis: '48%', alignItems: 'flex-start' }
: { flexBasis: '48%', alignItems: 'flex-end' }, : { flexBasis: '48%', alignItems: 'flex-start' },
]} ]}
> >
<Text style={PaymentStyle.titleMethod}>Avia-CP</Text> <Text style={PaymentStyle.titleMethod}>Reys</Text>
<Text style={PaymentStyle.textMethod}>223</Text> <Text style={[PaymentStyle.textMethod, { textAlign: 'left' }]}>
{packets.packetName}
</Text>
</View> </View>
</View> </View>
<View <View
@@ -140,7 +178,7 @@ const PaymentQrCode = (props: PaymentQrCodeProps) => {
}} }}
/> />
</View> </View>
{fakeProducts.map((item, index) => ( {packets.items.map((item: any, index: number) => (
<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}>
@@ -207,9 +245,7 @@ const PaymentQrCode = (props: PaymentQrCodeProps) => {
}} }}
> >
<Image <Image
source={{ source={{ uri: qrUrl }}
uri: 'https://docs.lightburnsoftware.com/legacy/img/QRCode/ExampleCode.png',
}}
style={{ width: '100%', height: '100%' }} style={{ width: '100%', height: '100%' }}
resizeMode="contain" resizeMode="contain"
/> />
@@ -262,6 +298,45 @@ const PaymentQrCode = (props: PaymentQrCodeProps) => {
</View> </View>
</View> </View>
</ScrollView> </ScrollView>
<ModalPay
packId={packets.id}
paymentType={packets.paymentType}
paymentStatus={packets.paymentStatus}
isModalVisible={isModalVisible}
selectedId={selectedId}
setModalVisible={setModalVisible}
setSelectedId={setSelectedId}
cardModal={cardModal}
setCardModal={setCardModal}
payModal={payModal}
setPayModal={setPayModal}
success={success}
setSuccess={setSuccess}
/>
{cardModal && (
<ModalCard
isVisible={cardModal}
packId={packets.id}
setIsVisible={setCardModal}
selectedId={selectedCard}
setSelectedId={setSelectedCard}
/>
)}
{success && (
<ModalSuccess
visible={success}
setVisible={setSuccess}
setPayModal={setPayModal}
/>
)}
{packets.paymentStatus !== 'PAYED' && (
<TouchableOpacity
style={[PaymentStyle.button, { bottom: bottom + 80 }]}
onPress={toggleModal}
>
<Text style={PaymentStyle.btnText}>{t("To'lash")}</Text>
</TouchableOpacity>
)}
</LayoutTwo> </LayoutTwo>
); );
}; };

View File

@@ -177,10 +177,11 @@ const styles = StyleSheet.create({
}, },
title: { title: {
color: '#fff', color: '#fff',
fontWeight: 'bold', fontWeight: '500',
}, },
text: { text: {
color: '#fff', color: '#eeee',
fontWeight: '400',
}, },
nextButtonWrapper: { nextButtonWrapper: {
position: 'absolute', position: 'absolute',

View File

@@ -173,10 +173,11 @@ const styles = StyleSheet.create({
}, },
title: { title: {
color: '#fff', color: '#fff',
fontWeight: 'bold', fontWeight: '500',
}, },
text: { text: {
color: '#fff', color: '#eeee',
fontWeight: '400',
}, },
nextButtonWrapper: { nextButtonWrapper: {
position: 'absolute', position: 'absolute',

View File

@@ -39,22 +39,17 @@ const SelectLangPage = ({
style={[ style={[
styles.logoImage, styles.logoImage,
{ {
width: isSmallScreen ? 120 : 250, width: 180,
height: isSmallScreen ? 120 : 200, height: 180,
borderRadius: 20000,
}, },
]} ]}
/> />
<Text <Text style={[styles.logoText, { fontSize: 24 }]}>CPOST</Text>
style={[styles.logoText, { fontSize: isSmallScreen ? 24 : 40 }]}
>
CPOST
</Text>
</View> </View>
<Text style={[styles.title, { fontSize: isSmallScreen ? 18 : 24 }]}> <Text style={[styles.title, { fontSize: 24 }]}>
Tilni tanlang{' '} Tilni tanlang{' '}
<Text style={{ fontSize: isSmallScreen ? 14 : 18 }}> <Text style={[styles.title, { fontSize: 18 }]}>
(Выберите язык) (Выберите язык)
</Text> </Text>
</Text> </Text>
@@ -93,7 +88,7 @@ const styles = StyleSheet.create({
borderRadius: 12, borderRadius: 12,
}, },
scrollContent: { scrollContent: {
flexGrow: 1, flex: 1,
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
}, },
@@ -103,21 +98,21 @@ const styles = StyleSheet.create({
}, },
logoWrapper: { logoWrapper: {
alignItems: 'center', alignItems: 'center',
marginBottom: 30, marginBottom: 20,
}, },
logoImage: { logoImage: {
resizeMode: 'stretch', resizeMode: 'stretch',
marginBottom: 8,
}, },
logoText: { logoText: {
fontWeight: '700', fontWeight: '500',
marginTop: 4,
color: '#28A7E8', color: '#28A7E8',
}, },
title: { title: {
fontWeight: '600', fontWeight: '500',
textAlign: 'center', textAlign: 'center',
color: '#28A7E8', color: '#28A7E8',
marginBottom: 24, marginBottom: 20,
}, },
button: { button: {
backgroundColor: '#28A7E8', backgroundColor: '#28A7E8',

View File

@@ -174,10 +174,11 @@ const styles = StyleSheet.create({
}, },
title: { title: {
color: '#fff', color: '#fff',
fontWeight: 'bold', fontWeight: '500',
}, },
text: { text: {
color: '#fff', color: '#eeee',
fontWeight: '400',
}, },
nextButtonWrapper: { nextButtonWrapper: {
position: 'absolute', position: 'absolute',

View File

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

View File

@@ -7,6 +7,11 @@ declare module '*.jpeg' {
export default value; export default value;
} }
declare module '*.jpg' {
const value: any;
export default value;
}
declare module '*.lottie' { declare module '*.lottie' {
const value: any; const value: any;
export default value; export default value;