added notification
This commit is contained in:
204
App.tsx
204
App.tsx
@@ -2,7 +2,10 @@
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { toastConfig } from 'components/CustomAlertModal';
|
||||
import { navigationRef } from 'components/NavigationRef';
|
||||
import SplashScreen from 'components/SplashScreen';
|
||||
import i18n from 'i18n/i18n';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
@@ -10,33 +13,34 @@ import {
|
||||
Animated,
|
||||
Dimensions,
|
||||
LogBox,
|
||||
PermissionsAndroid,
|
||||
Platform,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { enableScreens } from 'react-native-screens';
|
||||
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 Confirm from 'screens/auth/login/ui/Confirm';
|
||||
import Register from 'screens/auth/registeration/ui';
|
||||
import SecondStep from 'screens/auth/registeration/ui/SecondStep';
|
||||
import TermsAndConditions from 'screens/auth/registeration/ui/TermsAndConditions';
|
||||
import SelectAuth from 'screens/auth/select-auth/SelectAuth';
|
||||
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 CargoPrices from 'screens/home/cargoPrices/ui/CargoPrices';
|
||||
import Home from 'screens/home/home/ui/Home';
|
||||
import RestrictedProduct from 'screens/home/restrictedProduct/ui/RestrictedProduct';
|
||||
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 Onboarding from 'screens/welcome/Onboarding';
|
||||
|
||||
LogBox.ignoreLogs([
|
||||
'Non-serializable values were found in the navigation state',
|
||||
'ViewPropTypes will be removed',
|
||||
]);
|
||||
|
||||
const Stack = createNativeStackNavigator();
|
||||
const screenWidth = Dimensions.get('window').width;
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const saveNotification = async (remoteMessage: any) => {
|
||||
try {
|
||||
const stored = await AsyncStorage.getItem('notifications');
|
||||
const notifications = stored ? JSON.parse(stored) : [];
|
||||
|
||||
const newNotification = {
|
||||
id: Date.now(),
|
||||
title:
|
||||
remoteMessage.notification?.title ||
|
||||
remoteMessage.data?.title ||
|
||||
'Yangi bildirishnoma',
|
||||
message:
|
||||
remoteMessage.notification?.body ||
|
||||
remoteMessage.data?.body ||
|
||||
'Matn yo‘q',
|
||||
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 yo‘q',
|
||||
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() {
|
||||
const [initialRoute, setInitialRoute] = useState<string | null>(null);
|
||||
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(() => {
|
||||
const initializeApp = async () => {
|
||||
@@ -72,9 +179,7 @@ export default function App() {
|
||||
AsyncStorage.getItem('language'),
|
||||
]);
|
||||
|
||||
if (lang) {
|
||||
await i18n.changeLanguage(lang);
|
||||
}
|
||||
if (lang) await i18n.changeLanguage(lang);
|
||||
|
||||
const initialRouteName = !seen
|
||||
? 'Onboarding'
|
||||
@@ -91,8 +196,6 @@ export default function App() {
|
||||
initializeApp();
|
||||
}, []);
|
||||
|
||||
const [isSplashVisible, setIsSplashVisible] = useState(true);
|
||||
|
||||
const handleSplashFinish = useMemo(
|
||||
() => () => {
|
||||
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;
|
||||
|
||||
@@ -128,13 +259,7 @@ export default function App() {
|
||||
}}
|
||||
initialRouteName={initialRoute}
|
||||
>
|
||||
<Stack.Screen
|
||||
name="Onboarding"
|
||||
options={{
|
||||
gestureEnabled: false,
|
||||
animation: 'none',
|
||||
}}
|
||||
>
|
||||
<Stack.Screen name="Onboarding">
|
||||
{props => (
|
||||
<Onboarding
|
||||
{...props}
|
||||
@@ -142,8 +267,6 @@ export default function App() {
|
||||
/>
|
||||
)}
|
||||
</Stack.Screen>
|
||||
|
||||
{/* <Stack.Screen name="select-lang" component={SelectLangPage} /> */}
|
||||
<Stack.Screen name="select-auth" component={SelectAuth} />
|
||||
<Stack.Screen name="Login" component={Login} />
|
||||
<Stack.Screen name="Login-Confirm" component={Confirm} />
|
||||
@@ -151,32 +274,15 @@ export default function App() {
|
||||
<Stack.Screen name="Confirm" component={SecondStep} />
|
||||
<Stack.Screen name="SettingsLock" component={SettingsLock} />
|
||||
<Stack.Screen name="AddLock" component={AddedLock} />
|
||||
|
||||
<Stack.Screen
|
||||
name="Home"
|
||||
component={Home}
|
||||
options={{
|
||||
gestureEnabled: false,
|
||||
animation: 'none',
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="Status"
|
||||
component={Status}
|
||||
options={{
|
||||
animation: 'none',
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen name="Home" component={Home} />
|
||||
<Stack.Screen name="Status" component={Status} />
|
||||
<Stack.Screen name="Passports" component={Passport} />
|
||||
<Stack.Screen name="CargoPrices" component={CargoPrices} />
|
||||
|
||||
<Stack.Screen name="create-password" component={CreatePassword} />
|
||||
|
||||
<Stack.Screen name="Wallet" component={Wallet} />
|
||||
<Stack.Screen name="PaymentMethod" component={PaymentMethod} />
|
||||
<Stack.Screen name="EnterCard" component={EnterCard} />
|
||||
<Stack.Screen name="PaymentQrCode" component={PaymentQrCode} />
|
||||
|
||||
<Stack.Screen name="Profile" component={Profile} />
|
||||
<Stack.Screen name="Settings" component={Settings} />
|
||||
<Stack.Screen name="Notifications" component={Notifications} />
|
||||
|
||||
@@ -165,3 +165,5 @@ dependencies {
|
||||
// Performance dependencies
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
}
|
||||
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "628048576398",
|
||||
"project_id": "cpcargo-77d93",
|
||||
"storage_bucket": "cpcargo-77d93.firebasestorage.app"
|
||||
"project_number": "1030089382290",
|
||||
"project_id": "cpcargo-aee14",
|
||||
"storage_bucket": "cpcargo-aee14.firebasestorage.app"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:628048576398:android:f93293c00f463267a92edf",
|
||||
"mobilesdk_app_id": "1:1030089382290:android:668f0669ad4ac3f74dc94b",
|
||||
"android_client_info": {
|
||||
"package_name": "uz.felix.cpost"
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyBnFzHK6XAjxzcQAsg0hFbeRcon8ZMDvVw"
|
||||
"current_key": "AIzaSyBEwWi1TuZBNj2hkFGGIaWZNNDCoiC__lE"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
@@ -26,4 +26,4 @@
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,9 @@
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<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
|
||||
android:name=".MainApplication"
|
||||
|
||||
@@ -15,6 +15,7 @@ buildscript {
|
||||
classpath("com.android.tools.build:gradle")
|
||||
classpath("com.facebook.react:react-native-gradle-plugin")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin")
|
||||
classpath('com.google.gms:google-services:4.4.2')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BIN
assets/bootsplash/shablon.jpg
Normal file
BIN
assets/bootsplash/shablon.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 99 KiB |
34
index.js
34
index.js
@@ -1,9 +1,41 @@
|
||||
/**
|
||||
* @format
|
||||
*/
|
||||
|
||||
import notifee, { AndroidImportance } from '@notifee/react-native';
|
||||
import messaging from '@react-native-firebase/messaging';
|
||||
import { AppRegistry } from 'react-native';
|
||||
import App from './App';
|
||||
import { name as appName } from './app.json';
|
||||
|
||||
// 📌 Background/Killed xabarlarni ushlash
|
||||
messaging().setBackgroundMessageHandler(async remoteMessage => {
|
||||
console.log('Background message:', remoteMessage);
|
||||
|
||||
const channelId = await notifee.createChannel({
|
||||
id: 'default',
|
||||
name: 'Umumiy bildirishnomalar',
|
||||
sound: 'default',
|
||||
importance: AndroidImportance.HIGH,
|
||||
});
|
||||
|
||||
await notifee.displayNotification({
|
||||
title:
|
||||
remoteMessage.notification?.title ||
|
||||
remoteMessage.data?.title ||
|
||||
'Yangi xabar',
|
||||
body:
|
||||
remoteMessage.notification?.body ||
|
||||
remoteMessage.data?.body ||
|
||||
'Matn yo‘q',
|
||||
android: {
|
||||
channelId,
|
||||
smallIcon: 'ic_launcher',
|
||||
sound: 'default',
|
||||
pressAction: {
|
||||
id: 'default',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
AppRegistry.registerComponent(appName, () => App);
|
||||
|
||||
1152
package-lock.json
generated
1152
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -14,12 +14,15 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^5.2.0",
|
||||
"@notifee/react-native": "^9.1.8",
|
||||
"@react-native-async-storage/async-storage": "^2.2.0",
|
||||
"@react-native-clipboard/clipboard": "^1.16.3",
|
||||
"@react-native-community/datetimepicker": "^8.4.2",
|
||||
"@react-native-community/geolocation": "^3.4.0",
|
||||
"@react-native-community/image-editor": "^4.3.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/new-app-screen": "0.80.1",
|
||||
"@react-navigation/native": "^7.1.17",
|
||||
@@ -41,7 +44,9 @@
|
||||
"react-native-biometrics": "^3.0.1",
|
||||
"react-native-config": "^1.5.6",
|
||||
"react-native-confirmation-code-field": "^8.0.1",
|
||||
"react-native-device-info": "^14.0.4",
|
||||
"react-native-dotenv": "^3.4.11",
|
||||
"react-native-fs": "^2.20.0",
|
||||
"react-native-geolocation-service": "^5.3.1",
|
||||
"react-native-gesture-handler": "^2.28.0",
|
||||
"react-native-gesture-password": "^0.4.0",
|
||||
@@ -64,6 +69,7 @@
|
||||
"react-native-safe-area-context": "^5.6.0",
|
||||
"react-native-screens": "^4.14.1",
|
||||
"react-native-send-intent": "^1.3.0",
|
||||
"react-native-share": "^12.2.0",
|
||||
"react-native-skia": "^0.0.1",
|
||||
"react-native-smooth-pincode-input": "^1.0.9",
|
||||
"react-native-splash-screen": "^3.3.0",
|
||||
|
||||
@@ -11,3 +11,6 @@ export const RESEND_OTP = '/mobile/auth/resend-otp';
|
||||
export const CALENDAR = '/mobile/calendar';
|
||||
export const PACKETS = '/mobile/packets';
|
||||
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';
|
||||
|
||||
@@ -6,12 +6,20 @@ export interface registerPayload {
|
||||
phoneNumber: string;
|
||||
recommend: string;
|
||||
branchId: number;
|
||||
fcmToken: string;
|
||||
deviceId: string;
|
||||
deviceType: string;
|
||||
deviceName: string;
|
||||
address: string;
|
||||
}
|
||||
|
||||
export interface otpPayload {
|
||||
phoneNumber: string;
|
||||
otp: string;
|
||||
otpType: 'LOGIN' | 'RESET_PASSWORD' | 'REGISTRATION';
|
||||
fcmToken: string;
|
||||
deviceId: string;
|
||||
deviceName: string;
|
||||
}
|
||||
export interface resendPayload {
|
||||
phoneNumber: string;
|
||||
@@ -21,8 +29,11 @@ export interface resendPayload {
|
||||
export interface loginPayload {
|
||||
phoneNumber: string;
|
||||
passportSerial: string;
|
||||
// passportNumber: string;
|
||||
branchId: number;
|
||||
fcmToken: string;
|
||||
deviceId: string;
|
||||
deviceType: string;
|
||||
deviceName: string;
|
||||
}
|
||||
|
||||
export interface getMeData {
|
||||
|
||||
19
src/api/exchanges/index.ts
Normal file
19
src/api/exchanges/index.ts
Normal 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;
|
||||
26
src/api/reference/index.ts
Normal file
26
src/api/reference/index.ts
Normal 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;
|
||||
16
src/api/warhouses/index.ts
Normal file
16
src/api/warhouses/index.ts
Normal 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;
|
||||
@@ -9,30 +9,37 @@ import {
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {
|
||||
Asset,
|
||||
ImagePickerResponse,
|
||||
launchImageLibrary,
|
||||
MediaType,
|
||||
} from 'react-native-image-picker';
|
||||
import Download from 'svg/Download';
|
||||
|
||||
interface FileData {
|
||||
export interface FileData {
|
||||
uri: string;
|
||||
name: string;
|
||||
type: string;
|
||||
base64: string;
|
||||
}
|
||||
|
||||
interface SingleFileDropProps {
|
||||
export interface SingleFileDropProps {
|
||||
title: string;
|
||||
onFileSelected?: (file: FileData) => void;
|
||||
/**
|
||||
* Ruxsat berilgan MIME tipi (masalan: "image/png" yoki "image/jpeg")
|
||||
*/
|
||||
type?: string;
|
||||
}
|
||||
|
||||
const SingleFileDrop: React.FC<SingleFileDropProps> = ({
|
||||
title,
|
||||
onFileSelected,
|
||||
type = 'image/png',
|
||||
}) => {
|
||||
const [selectedImage, setSelectedImage] = useState<string | null>(null);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const imagePickerOptions = useMemo(
|
||||
() => ({
|
||||
mediaType: 'photo' as MediaType,
|
||||
@@ -49,29 +56,27 @@ const SingleFileDrop: React.FC<SingleFileDropProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.assets && response.assets[0]) {
|
||||
const asset = response.assets[0];
|
||||
if (!asset.uri || !asset.type || !asset.base64) return;
|
||||
const asset: Asset | undefined = response.assets?.[0];
|
||||
if (!asset || !asset.uri || !asset.type || !asset.base64) return;
|
||||
|
||||
// faqat PNG fayllarni qabul qilish
|
||||
if (asset.type !== 'image/png') {
|
||||
Alert.alert('Xato', 'Faqat PNG fayllarni yuklashingiz mumkin');
|
||||
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);
|
||||
// faqat belgilangan tipdagi fayllarni qabul qilish
|
||||
if (!asset.type.startsWith('image/')) {
|
||||
Alert.alert('Xato', 'Faqat rasm fayllarni yuklashingiz mumkin');
|
||||
return;
|
||||
}
|
||||
|
||||
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 => {
|
||||
@@ -101,17 +106,8 @@ const SingleFileDrop: React.FC<SingleFileDropProps> = ({
|
||||
<>
|
||||
<View style={styles.innerContainer}>
|
||||
<View style={styles.topContent}>
|
||||
{selectedImage ? (
|
||||
<Image
|
||||
source={{ uri: selectedImage }}
|
||||
style={styles.previewImage}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<UploadIcon />
|
||||
<Text style={styles.sectionTitle}>{title}</Text>
|
||||
</>
|
||||
)}
|
||||
<UploadIcon />
|
||||
<Text style={styles.sectionTitle}>{title}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.bottomContent}>
|
||||
@@ -158,11 +154,6 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
iconText: {
|
||||
fontSize: 20,
|
||||
color: '#007bff',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 16,
|
||||
fontWeight: '500',
|
||||
@@ -191,7 +182,6 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginVertical: 16,
|
||||
position: 'relative',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
dividerLine: {
|
||||
@@ -203,18 +193,15 @@ const styles = StyleSheet.create({
|
||||
paddingHorizontal: 12,
|
||||
fontSize: 14,
|
||||
color: '#999',
|
||||
zIndex: 1,
|
||||
},
|
||||
innerContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'space-between',
|
||||
width: '100%',
|
||||
},
|
||||
|
||||
topContent: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
bottomContent: {
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
Dimensions,
|
||||
Image,
|
||||
Linking,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
@@ -98,14 +97,12 @@ const Navbar = () => {
|
||||
/>
|
||||
</TouchableOpacity> */}
|
||||
|
||||
{Platform.OS === 'android' && (
|
||||
<TouchableOpacity
|
||||
onPress={() => navigation.navigate('Notifications')}
|
||||
>
|
||||
<Bell color="#fff" width={24} height={24} />
|
||||
{/* <View style={styles.bellDot} /> */}
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
<TouchableOpacity
|
||||
onPress={() => navigation.navigate('Notifications')}
|
||||
>
|
||||
<Bell color="#fff" width={24} height={24} />
|
||||
{/* <View style={styles.bellDot} /> */}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
|
||||
@@ -193,7 +193,7 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
},
|
||||
brand: {
|
||||
fontWeight: '700',
|
||||
fontWeight: '500',
|
||||
color: '#fff',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -170,6 +170,8 @@
|
||||
"so‘m": "сум",
|
||||
"Umumiy narx": "Общая цена",
|
||||
"Yopish": "Закрыть",
|
||||
"Manzilingizni kiriting": "Введите свой адрес",
|
||||
"Toshkent Shahri, Mirzo Ulug'bek tumani...": "г. Ташкент, Мирзо-Улугбекский район...",
|
||||
"Passportlarim": "Мои паспорта",
|
||||
"Hali pasport qo'shilmagan": "Паспорт еще не добавлен",
|
||||
"Yangi pasport qo'shish uchun tugmani bosing": "Нажмите кнопку, чтобы добавить новый паспорт",
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
"7 ta raqam kerak": "7 ta raqam kerak",
|
||||
"Filialni tanlang": "Filialni tanlang",
|
||||
"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",
|
||||
"Ismingiz": "Ismingiz",
|
||||
"Familiya": "Familiya",
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { getApp } from '@react-native-firebase/app';
|
||||
import { getMessaging, getToken } from '@react-native-firebase/messaging';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
@@ -17,9 +19,9 @@ import {
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import Logo from 'screens/../../assets/bootsplash/logo_512.png';
|
||||
import { useModalStore } from 'screens/auth/registeration/lib/modalStore';
|
||||
import LanguageSelector from 'screens/auth/select-language/SelectLang';
|
||||
import ArrowLeft from 'svg/ArrowLeft';
|
||||
import { RootStackParamList } from 'types/types';
|
||||
@@ -36,27 +38,57 @@ const OTP_LENGTH = 4;
|
||||
const Confirm = () => {
|
||||
const navigation = useNavigation<VerificationCodeScreenNavigationProp>();
|
||||
const { t } = useTranslation();
|
||||
const [firebaseToken, setFirebseToken] = useState<{
|
||||
fcmToken: string;
|
||||
deviceId: string;
|
||||
deviceName: string;
|
||||
} | null>();
|
||||
const [code, setCode] = useState<string[]>(new Array(OTP_LENGTH).fill(''));
|
||||
const [timer, setTimer] = useState(60);
|
||||
const [errorConfirm, setErrorConfirm] = useState<string | null>(null);
|
||||
const [canResend, setCanResend] = useState(false);
|
||||
const inputRefs = useRef<Array<TextInput | null>>([]);
|
||||
const { phoneNumber } = useUserStore(state => state);
|
||||
|
||||
const 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({
|
||||
mutationFn: (payload: otpPayload) => authApi.verifyOtp(payload),
|
||||
onSuccess: async res => {
|
||||
await AsyncStorage.setItem('token', res.data.accessToken);
|
||||
navigation.navigate('Home');
|
||||
setErrorConfirm(null);
|
||||
console.log(res);
|
||||
},
|
||||
onError: (err: any) => {
|
||||
setErrorConfirm(err?.response.data.message);
|
||||
setErrorConfirm(err?.response?.data?.message || 'Xato yuz berdi');
|
||||
},
|
||||
});
|
||||
|
||||
const { mutate: resendMutate } = useMutation({
|
||||
mutationFn: (payload: resendPayload) => authApi.resendOtp(payload),
|
||||
onSuccess: async res => {
|
||||
onSuccess: async () => {
|
||||
setTimer(60);
|
||||
setCanResend(false);
|
||||
setCode(new Array(OTP_LENGTH).fill(''));
|
||||
@@ -64,17 +96,14 @@ const Confirm = () => {
|
||||
setErrorConfirm(null);
|
||||
},
|
||||
onError: (err: any) => {
|
||||
setErrorConfirm(err?.response.data.message);
|
||||
setErrorConfirm(err?.response?.data?.message || 'Xato yuz berdi');
|
||||
},
|
||||
});
|
||||
|
||||
const openModal = useModalStore(state => state.openModal);
|
||||
useEffect(() => {
|
||||
let interval: NodeJS.Timeout | null = null;
|
||||
if (timer > 0) {
|
||||
interval = setInterval(() => {
|
||||
setTimer(prevTimer => prevTimer - 1);
|
||||
}, 1000);
|
||||
interval = setInterval(() => setTimer(prev => prev - 1), 1000);
|
||||
} else {
|
||||
setCanResend(true);
|
||||
if (interval) clearInterval(interval);
|
||||
@@ -104,20 +133,21 @@ const Confirm = () => {
|
||||
};
|
||||
|
||||
const handleResendCode = () => {
|
||||
resendMutate({
|
||||
phoneNumber: phoneNumber,
|
||||
otpType: 'LOGIN',
|
||||
});
|
||||
resendMutate({ phoneNumber, otpType: 'LOGIN' });
|
||||
};
|
||||
|
||||
const handleVerifyCode = () => {
|
||||
const enteredCode = code.join('');
|
||||
mutate({
|
||||
phoneNumber: phoneNumber,
|
||||
otp: String(enteredCode),
|
||||
otpType: 'LOGIN',
|
||||
});
|
||||
// navigation.navigate('Home');
|
||||
if (firebaseToken) {
|
||||
mutate({
|
||||
phoneNumber,
|
||||
otp: enteredCode,
|
||||
otpType: 'LOGIN',
|
||||
deviceId: firebaseToken.deviceId,
|
||||
deviceName: firebaseToken.deviceName,
|
||||
fcmToken: firebaseToken.fcmToken,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -147,15 +177,13 @@ const Confirm = () => {
|
||||
<Text style={styles.title}>{t('Tasdiqlash kodini kiriting')}</Text>
|
||||
<Text style={styles.message}>
|
||||
{phoneNumber} {t('raqamiga yuborilgan')} {OTP_LENGTH}{' '}
|
||||
{t('xonali kodni kiriting.')}
|
||||
{t('xonali kodni kiriting.')}{' '}
|
||||
</Text>
|
||||
<View style={styles.otpContainer}>
|
||||
{code.map((digit, index) => (
|
||||
<TextInput
|
||||
key={index}
|
||||
ref={ref => {
|
||||
inputRefs.current[index] = ref;
|
||||
}}
|
||||
ref={ref => (inputRefs.current[index] = ref)}
|
||||
style={styles.otpInput}
|
||||
keyboardType="number-pad"
|
||||
maxLength={1}
|
||||
@@ -166,7 +194,7 @@ const Confirm = () => {
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
{errorConfirm !== null && (
|
||||
{errorConfirm && (
|
||||
<Text style={styles.errorText}>{errorConfirm}</Text>
|
||||
)}
|
||||
<View style={styles.resendContainer}>
|
||||
@@ -210,10 +238,7 @@ const Confirm = () => {
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
safeArea: {
|
||||
flex: 1,
|
||||
backgroundColor: '#f8f9fa',
|
||||
},
|
||||
safeArea: { flex: 1, backgroundColor: '#f8f9fa' },
|
||||
errorText: {
|
||||
color: 'red',
|
||||
fontSize: 14,
|
||||
@@ -221,33 +246,22 @@ const styles = StyleSheet.create({
|
||||
marginTop: 10,
|
||||
textAlign: 'center',
|
||||
},
|
||||
buttonTextDisabled: {
|
||||
color: 'white',
|
||||
},
|
||||
buttonTextDisabled: { color: 'white' },
|
||||
langContainer: {
|
||||
width: '100%',
|
||||
marginTop: 10,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
color: '#333',
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
padding: 20,
|
||||
},
|
||||
content: {
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
},
|
||||
content: { width: '100%', alignItems: 'center' },
|
||||
title: {
|
||||
fontSize: 24,
|
||||
fontWeight: 'bold',
|
||||
@@ -280,24 +294,10 @@ const styles = StyleSheet.create({
|
||||
color: '#333',
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
resendContainer: {
|
||||
marginBottom: 30,
|
||||
marginTop: 20,
|
||||
},
|
||||
timerText: {
|
||||
fontSize: 15,
|
||||
color: '#999',
|
||||
},
|
||||
resendButton: {
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 20,
|
||||
borderRadius: 8,
|
||||
},
|
||||
resendButtonText: {
|
||||
fontSize: 15,
|
||||
color: '#007bff',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
resendContainer: { marginBottom: 30, marginTop: 20 },
|
||||
timerText: { fontSize: 15, color: '#999' },
|
||||
resendButton: { paddingVertical: 10, paddingHorizontal: 20, borderRadius: 8 },
|
||||
resendButtonText: { fontSize: 15, color: '#007bff', fontWeight: 'bold' },
|
||||
verifyButton: {
|
||||
backgroundColor: '#007bff',
|
||||
paddingVertical: 15,
|
||||
@@ -311,11 +311,7 @@ const styles = StyleSheet.create({
|
||||
shadowOpacity: 0.2,
|
||||
shadowRadius: 4,
|
||||
},
|
||||
verifyButtonText: {
|
||||
color: '#fff',
|
||||
fontSize: 18,
|
||||
fontWeight: '600',
|
||||
},
|
||||
verifyButtonText: { color: '#fff', fontSize: 18, fontWeight: '600' },
|
||||
});
|
||||
|
||||
export default Confirm;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { getApp } from '@react-native-firebase/app';
|
||||
import { getMessaging, getToken } from '@react-native-firebase/messaging';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
@@ -6,7 +8,13 @@ import { authApi } from 'api/auth';
|
||||
import { loginPayload } from 'api/auth/type';
|
||||
import { Branch, branchApi } from 'api/branch';
|
||||
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 { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
@@ -20,6 +28,7 @@ import {
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import Logo from 'screens/../../assets/bootsplash/logo_512.png';
|
||||
import { LoginFormType, loginSchema } from 'screens/auth/login/lib/form';
|
||||
@@ -50,6 +59,36 @@ const Login = () => {
|
||||
queryKey: ['branchList'],
|
||||
queryFn: branchApi.branchList,
|
||||
});
|
||||
const [firebaseToken, setFirebseToken] = useState<{
|
||||
fcmToken: string;
|
||||
deviceId: string;
|
||||
deviceName: string;
|
||||
deviceType: string;
|
||||
} | null>();
|
||||
|
||||
const app = getApp();
|
||||
const messaging = getMessaging(app);
|
||||
|
||||
const getDeviceData = async () => {
|
||||
try {
|
||||
const fcmToken = await getToken(messaging);
|
||||
return {
|
||||
fcmToken,
|
||||
deviceId: await DeviceInfo.getUniqueId(),
|
||||
deviceName: await DeviceInfo.getDeviceName(),
|
||||
deviceType: await DeviceInfo.getDeviceType(),
|
||||
};
|
||||
} catch (e) {
|
||||
console.log('Xato:', e);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getDeviceData().then(data => {
|
||||
setFirebseToken(data);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const { mutate, isPending } = useMutation({
|
||||
mutationFn: (payload: loginPayload) => authApi.login(payload),
|
||||
@@ -84,6 +123,10 @@ const Login = () => {
|
||||
passportSerial: `${data.passportSeriya.toUpperCase()}${
|
||||
data.passportNumber
|
||||
}`,
|
||||
fcmToken: firebaseToken?.fcmToken || '',
|
||||
deviceId: firebaseToken?.deviceId || '',
|
||||
deviceType: firebaseToken?.deviceType || '',
|
||||
deviceName: firebaseToken?.deviceName || '',
|
||||
});
|
||||
// navigation.navigate('Login-Confirm');
|
||||
setUser({
|
||||
|
||||
@@ -4,6 +4,7 @@ import { z } from 'zod';
|
||||
export const FirstStepSchema = z.object({
|
||||
firstName: 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'),
|
||||
branchId: z.number().min(1, 'Filialni tanlang'),
|
||||
recommend: z.string().min(1, 'Majburiy maydon'),
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { getApp } from '@react-native-firebase/app';
|
||||
import { getMessaging, getToken } from '@react-native-firebase/messaging';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
@@ -17,6 +19,7 @@ import {
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import Logo from 'screens/../../assets/bootsplash/logo_512.png';
|
||||
import { useModalStore } from 'screens/auth/registeration/lib/modalStore';
|
||||
@@ -46,6 +49,34 @@ const Confirm = ({
|
||||
const [errorConfirm, setErrorConfirm] = useState<string | null>(null);
|
||||
const inputRefs = useRef<Array<TextInput | null>>([]);
|
||||
const { phoneNumber } = useUserStore(state => state);
|
||||
const [firebaseToken, setFirebseToken] = useState<{
|
||||
fcmToken: string;
|
||||
deviceId: string;
|
||||
deviceName: string;
|
||||
} | null>();
|
||||
|
||||
const 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({
|
||||
mutationFn: (payload: otpPayload) => authApi.verifyOtp(payload),
|
||||
onSuccess: async res => {
|
||||
@@ -54,6 +85,7 @@ const Confirm = ({
|
||||
setErrorConfirm(null);
|
||||
},
|
||||
onError: (err: any) => {
|
||||
console.dir(err);
|
||||
setErrorConfirm(err?.response.data.message);
|
||||
},
|
||||
});
|
||||
@@ -116,11 +148,16 @@ const Confirm = ({
|
||||
|
||||
const handleVerifyCode = () => {
|
||||
const enteredCode = code.join('');
|
||||
mutate({
|
||||
phoneNumber: phoneNumber,
|
||||
otp: String(enteredCode),
|
||||
otpType: 'REGISTRATION',
|
||||
});
|
||||
if (firebaseToken) {
|
||||
mutate({
|
||||
phoneNumber,
|
||||
otp: enteredCode,
|
||||
otpType: 'REGISTRATION',
|
||||
deviceId: firebaseToken.deviceId,
|
||||
deviceName: firebaseToken.deviceName,
|
||||
fcmToken: firebaseToken.fcmToken,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { getApp } from '@react-native-firebase/app';
|
||||
import { getMessaging, getToken } from '@react-native-firebase/messaging';
|
||||
import {
|
||||
type RouteProp,
|
||||
useNavigation,
|
||||
@@ -27,6 +29,7 @@ import {
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import Logo from 'screens/../../assets/bootsplash/logo_512.png';
|
||||
import {
|
||||
@@ -64,14 +67,43 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
|
||||
queryFn: branchApi.branchList,
|
||||
});
|
||||
|
||||
const [firebaseToken, setFirebseToken] = useState<{
|
||||
fcmToken: string;
|
||||
deviceId: string;
|
||||
deviceName: string;
|
||||
deviceType: string;
|
||||
} | null>();
|
||||
|
||||
const app = getApp();
|
||||
const messaging = getMessaging(app);
|
||||
|
||||
const getDeviceData = async () => {
|
||||
try {
|
||||
const fcmToken = await getToken(messaging);
|
||||
return {
|
||||
fcmToken,
|
||||
deviceId: await DeviceInfo.getUniqueId(),
|
||||
deviceName: await DeviceInfo.getDeviceName(),
|
||||
deviceType: await DeviceInfo.getDeviceType(),
|
||||
};
|
||||
} catch (e) {
|
||||
console.log('Xato:', e);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getDeviceData().then(data => {
|
||||
setFirebseToken(data);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const { mutate, isPending } = useMutation({
|
||||
mutationFn: (payload: registerPayload) => authApi.register(payload),
|
||||
onSuccess: res => {
|
||||
onNext();
|
||||
},
|
||||
onError: err => {
|
||||
console.dir(err);
|
||||
|
||||
setError('Xatolik yuz berdi');
|
||||
},
|
||||
});
|
||||
@@ -93,6 +125,7 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
|
||||
resolver: zodResolver(FirstStepSchema),
|
||||
defaultValues: {
|
||||
firstName: '',
|
||||
address: '',
|
||||
lastName: '',
|
||||
recommend: '',
|
||||
},
|
||||
@@ -104,7 +137,18 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
|
||||
lastName: data.lastName,
|
||||
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(() => {
|
||||
@@ -143,6 +187,7 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
|
||||
}).start();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ImageBackground
|
||||
source={Logo}
|
||||
@@ -314,6 +359,31 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
|
||||
</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
|
||||
control={control}
|
||||
name="recommend"
|
||||
|
||||
@@ -35,6 +35,7 @@ import ArrowLeft from 'svg/ArrowLeft';
|
||||
import Calendar from 'svg/Calendar';
|
||||
import { RootStackParamList } from 'types/types';
|
||||
import { SecondStepFormType, SecondStepSchema } from '../lib/form';
|
||||
import { useUserStore } from '../lib/userstore';
|
||||
import { RegisterStyle } from './styled';
|
||||
|
||||
interface FileData {
|
||||
@@ -54,6 +55,7 @@ const SecondStep = () => {
|
||||
const passportNumberRef = useRef<TextInput>(null);
|
||||
const [checkboxAnimation] = useState(new Animated.Value(1));
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const { firstName, lastName } = useUserStore(state => state);
|
||||
|
||||
const navigation =
|
||||
useNavigation<NativeStackNavigationProp<RootStackParamList, 'Login'>>();
|
||||
@@ -111,7 +113,7 @@ const SecondStep = () => {
|
||||
const isoBirthDate = `${y}-${m.padStart(2, '0')}-${d.padStart(2, '0')}`;
|
||||
|
||||
mutate({
|
||||
fullName: data.passportSeriya.toUpperCase(),
|
||||
fullName: `${firstName} ${lastName}`,
|
||||
birthDate: isoBirthDate,
|
||||
passportSerial: `${data.passportSeriya.toUpperCase()}${
|
||||
data.passportNumber
|
||||
|
||||
@@ -24,8 +24,6 @@ const SelectAuth = () => {
|
||||
const navigation = useNavigation<LoginScreenNavigationProp>();
|
||||
const { width } = useWindowDimensions();
|
||||
|
||||
const isSmallScreen = width < 360;
|
||||
|
||||
return (
|
||||
<SafeAreaView style={{ flex: 1 }}>
|
||||
<View style={styles.container}>
|
||||
@@ -41,20 +39,15 @@ const SelectAuth = () => {
|
||||
style={[
|
||||
styles.logoImage,
|
||||
{
|
||||
width: isSmallScreen ? 120 : 250,
|
||||
height: isSmallScreen ? 120 : 200,
|
||||
borderRadius: 20000,
|
||||
width: 180,
|
||||
height: 180,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Text
|
||||
style={[styles.logoText, { fontSize: isSmallScreen ? 22 : 50 }]}
|
||||
>
|
||||
CPOST
|
||||
</Text>
|
||||
<Text style={[styles.logoText, { fontSize: 24 }]}>CPOST</Text>
|
||||
</View>
|
||||
|
||||
<Text style={[styles.title, { fontSize: isSmallScreen ? 20 : 24 }]}>
|
||||
<Text style={[styles.title, { fontSize: 24 }]}>
|
||||
{t('Ro’yxatdan o’tganmisz')}
|
||||
</Text>
|
||||
|
||||
@@ -110,17 +103,18 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
logoWrapper: {
|
||||
alignItems: 'center',
|
||||
marginBottom: 40,
|
||||
marginBottom: 20,
|
||||
},
|
||||
logoImage: {
|
||||
resizeMode: 'stretch',
|
||||
},
|
||||
logoText: {
|
||||
fontWeight: '700',
|
||||
fontWeight: '500',
|
||||
marginTop: 4,
|
||||
color: '#28A7E8',
|
||||
},
|
||||
title: {
|
||||
fontWeight: '600',
|
||||
fontWeight: '500',
|
||||
textAlign: 'center',
|
||||
color: '#28A7E8',
|
||||
marginBottom: 20,
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
useWindowDimensions,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
@@ -21,9 +20,6 @@ type NavigationProp = NativeStackNavigationProp<RootStackParamList>;
|
||||
|
||||
const SelectLangPage = () => {
|
||||
const navigation = useNavigation<NavigationProp>();
|
||||
const { width } = useWindowDimensions();
|
||||
|
||||
const isSmallScreen = width < 380;
|
||||
|
||||
const selectLanguage = async (lang: 'uz' | 'ru') => {
|
||||
await changeLanguage(lang);
|
||||
@@ -45,24 +41,17 @@ const SelectLangPage = () => {
|
||||
style={[
|
||||
styles.logoImage,
|
||||
{
|
||||
width: isSmallScreen ? 120 : 250,
|
||||
height: isSmallScreen ? 120 : 200,
|
||||
borderRadius: 20000,
|
||||
width: 180,
|
||||
height: 180,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Text
|
||||
style={[styles.logoText, { fontSize: isSmallScreen ? 24 : 40 }]}
|
||||
>
|
||||
CPOST
|
||||
</Text>
|
||||
<Text style={[styles.logoText, { fontSize: 24 }]}>CPOST</Text>
|
||||
</View>
|
||||
|
||||
<Text style={[styles.title, { fontSize: isSmallScreen ? 18 : 24 }]}>
|
||||
<Text style={[styles.title, { fontSize: 24 }]}>
|
||||
Tilni tanlang{' '}
|
||||
<Text
|
||||
style={[styles.title, { fontSize: isSmallScreen ? 14 : 18 }]}
|
||||
>
|
||||
<Text style={[styles.title, { fontSize: 18 }]}>
|
||||
(Выберите язык)
|
||||
</Text>
|
||||
</Text>
|
||||
@@ -118,11 +107,11 @@ const styles = StyleSheet.create({
|
||||
marginBottom: 8,
|
||||
},
|
||||
logoText: {
|
||||
fontWeight: '700',
|
||||
fontWeight: '500',
|
||||
color: '#28A7E8',
|
||||
},
|
||||
title: {
|
||||
fontWeight: '600',
|
||||
fontWeight: '500',
|
||||
textAlign: 'center',
|
||||
color: '#28A7E8',
|
||||
marginBottom: 24,
|
||||
|
||||
@@ -51,11 +51,27 @@ const Branches = (props: BranchesProps) => {
|
||||
navigation.navigate('ListBranches', { branchId: e.id })
|
||||
}
|
||||
>
|
||||
<View>
|
||||
<Text style={styles.title}>{e.name}</Text>
|
||||
<Text style={styles.subtitle}>{e.address}</Text>
|
||||
<View
|
||||
style={{
|
||||
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>
|
||||
<ArrowRightUnderline color="#28A7E8" />
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</ScrollView>
|
||||
@@ -89,14 +105,14 @@ const styles = StyleSheet.create({
|
||||
elevation: 1,
|
||||
},
|
||||
title: {
|
||||
fontSize: 18,
|
||||
fontSize: 16,
|
||||
paddingHorizontal: 5,
|
||||
fontWeight: '600',
|
||||
color: '#000',
|
||||
marginBottom: 6,
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 16,
|
||||
fontSize: 14,
|
||||
paddingHorizontal: 5,
|
||||
fontWeight: '500',
|
||||
color: '#000000B2',
|
||||
|
||||
@@ -25,46 +25,58 @@ const ListBranches = () => {
|
||||
queryFn: branchApi.branchList,
|
||||
});
|
||||
|
||||
// ✅ ObjectManager orqali markerlarni yuboramiz
|
||||
useEffect(() => {
|
||||
if (webViewReady && route.params?.branchId) {
|
||||
const branch = data && data.find(b => b.id === route?.params?.branchId);
|
||||
if (webViewReady && data && data.length) {
|
||||
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) {
|
||||
setSelectedBranch(branch);
|
||||
setModalVisible(true);
|
||||
|
||||
const jsCode = `
|
||||
map.setCenter([${branch.latitude}, ${branch.longitude}], 14);
|
||||
placemark${branch.id}?.balloon.open();
|
||||
true;
|
||||
`;
|
||||
webviewRef.current?.injectJavaScript(jsCode);
|
||||
}
|
||||
}
|
||||
}, [webViewReady, route.params]);
|
||||
|
||||
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');
|
||||
};
|
||||
}, [webViewReady, route.params, data]);
|
||||
|
||||
const html = `
|
||||
<!DOCTYPE html>
|
||||
@@ -96,15 +108,12 @@ const ListBranches = () => {
|
||||
zoom: 6,
|
||||
controls: []
|
||||
});
|
||||
|
||||
${generatePlacemarks()}
|
||||
window.ReactNativeWebView.postMessage("map_ready");
|
||||
});
|
||||
|
||||
function zoomIn() {
|
||||
if (map) map.setZoom(map.getZoom() + 1);
|
||||
}
|
||||
|
||||
function zoomOut() {
|
||||
if (map) map.setZoom(map.getZoom() - 1);
|
||||
}
|
||||
@@ -143,8 +152,9 @@ const ListBranches = () => {
|
||||
}
|
||||
const parsed = JSON.parse(message);
|
||||
if (parsed.type === 'branch_click') {
|
||||
const branchItem =
|
||||
data && data.find((b: Branch) => b.id === parsed.id);
|
||||
const branchItem = data?.find(
|
||||
(b: Branch) => b.id === parsed.id,
|
||||
);
|
||||
if (branchItem) {
|
||||
setSelectedBranch(branchItem);
|
||||
setModalVisible(true);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
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 * as React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -16,39 +18,38 @@ interface CargoPricesProps {}
|
||||
|
||||
const CargoPrices = (props: CargoPricesProps) => {
|
||||
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 { data, refetch } = useQuery({
|
||||
queryKey: ['reference_list'],
|
||||
queryFn: () => reference_api.getReference({ cargoType: activeTab }),
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
refetch();
|
||||
}, [activeTab]);
|
||||
|
||||
return (
|
||||
<LayoutTwo title={t('Kargo narxlari')}>
|
||||
<ScrollView style={{ flex: 1 }}>
|
||||
<View style={styles.container}>
|
||||
<Tabs activeTab={activeTab} setActiveTab={setActiveTab} />
|
||||
{activeTab === 'avia' && (
|
||||
{activeTab === 'AVIA' && (
|
||||
<View style={{ marginTop: 10, gap: 10, marginBottom: 20 }}>
|
||||
<View style={styles.cardWhite}>
|
||||
<View style={styles.priceCard}>
|
||||
<Text style={styles.titleBlack}>
|
||||
{t('Oddiy maxsulotlar')}
|
||||
</Text>
|
||||
<Text style={[styles.titleBlack, { fontSize: 16 }]}>
|
||||
9.2$ /1kg
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={styles.desc}>
|
||||
{t('(Katta miqdordagi yuklar kelishuv asosida)')}
|
||||
</Text>
|
||||
</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>
|
||||
{data &&
|
||||
data.map(ref => (
|
||||
<View style={styles.cardWhite}>
|
||||
<View style={styles.priceCard}>
|
||||
<Text style={styles.titleBlack}>{ref.title}</Text>
|
||||
<Text style={[styles.titleBlack, { fontSize: 16 }]}>
|
||||
{ref.price}$/{ref.unitValue}
|
||||
{ref.unit}
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={styles.desc}>{ref.shortDescription}</Text>
|
||||
</View>
|
||||
))}
|
||||
<View style={styles.cardWhite}>
|
||||
<View style={styles.priceCard}>
|
||||
<Text style={styles.titleBlack}>
|
||||
@@ -127,34 +128,21 @@ const CargoPrices = (props: CargoPricesProps) => {
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
{activeTab === 'auto' && (
|
||||
{activeTab === 'AUTO' && (
|
||||
<View style={{ marginTop: 20, gap: 10, marginBottom: 20 }}>
|
||||
<View style={styles.cardWhite}>
|
||||
<View style={styles.priceCard}>
|
||||
<Text style={styles.titleBlack}>0-30 kg</Text>
|
||||
<Text style={[styles.titleBlack, { fontSize: 16 }]}>
|
||||
7$ /1kg
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.cardWhite}>
|
||||
<View style={styles.priceCard}>
|
||||
<Text style={styles.titleBlack}>30-100kg</Text>
|
||||
<Text style={[styles.titleBlack, { fontSize: 16 }]}>
|
||||
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>
|
||||
{data &&
|
||||
data.map(ref => (
|
||||
<View style={styles.cardWhite}>
|
||||
<View style={styles.priceCard}>
|
||||
<Text style={styles.titleBlack}>{ref.title}</Text>
|
||||
<Text style={[styles.titleBlack, { fontSize: 16 }]}>
|
||||
{ref.price}$/{ref.unitValue}
|
||||
{ref.unit}
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={styles.desc}>{ref.shortDescription}</Text>
|
||||
</View>
|
||||
))}
|
||||
<View style={styles.cardWhite}>
|
||||
<Text style={styles.desc}>
|
||||
{t(
|
||||
|
||||
@@ -12,7 +12,7 @@ import TabsAuto from './TabsAuto';
|
||||
import TabsAvia from './TabsAvia';
|
||||
|
||||
const Home = () => {
|
||||
const [activeTab, setActiveTab] = useState<'avia' | 'auto'>('avia');
|
||||
const [activeTab, setActiveTab] = useState<'AVIA' | 'AUTO'>('AVIA');
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const { width: screenWidth } = useWindowDimensions();
|
||||
const scale = screenWidth < 360 ? 0.85 : 1;
|
||||
@@ -52,9 +52,9 @@ const Home = () => {
|
||||
);
|
||||
|
||||
const activeTabContent = useMemo(() => {
|
||||
if (activeTab === 'auto') {
|
||||
if (activeTab === 'AUTO') {
|
||||
return <TabsAuto />;
|
||||
} else if (activeTab === 'avia') {
|
||||
} else if (activeTab === 'AVIA') {
|
||||
return <TabsAvia />;
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -8,7 +8,7 @@ import { HomeStyle } from './styled';
|
||||
|
||||
interface Props {
|
||||
activeTab: string;
|
||||
setActiveTab: Dispatch<SetStateAction<'avia' | 'auto'>>;
|
||||
setActiveTab: Dispatch<SetStateAction<'AVIA' | 'AUTO'>>;
|
||||
}
|
||||
|
||||
const Tabs = ({ activeTab, setActiveTab }: Props) => {
|
||||
@@ -30,12 +30,12 @@ const Tabs = ({ activeTab, setActiveTab }: Props) => {
|
||||
const tabsData = useMemo(
|
||||
() => [
|
||||
{
|
||||
type: 'avia' as const,
|
||||
type: 'AVIA' as const,
|
||||
label: t('Avia orqali yetkazish'),
|
||||
logo: AviaLogo,
|
||||
},
|
||||
{
|
||||
type: 'auto' as const,
|
||||
type: 'AUTO' as const,
|
||||
label: t('Avto orqali yetkazish'),
|
||||
logo: AutoLogo,
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { authApi } from 'api/auth';
|
||||
import warhouses_api from 'api/warhouses';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
@@ -26,35 +27,9 @@ const TabsAuto = () => {
|
||||
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[]) => {
|
||||
if (getMe?.status === 'active') {
|
||||
const textToCopy = info.join('\n');
|
||||
Clipboard.setString(textToCopy);
|
||||
if (getMe?.status === 'ACTIVE') {
|
||||
Clipboard.setString(info.join('\n'));
|
||||
Toast.show({
|
||||
type: 'success',
|
||||
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 (
|
||||
<FlatList
|
||||
data={addressList}
|
||||
data={formattedData}
|
||||
horizontal
|
||||
keyExtractor={item => item.id.toString()}
|
||||
keyExtractor={(_, index) => index.toString()}
|
||||
pagingEnabled
|
||||
showsHorizontalScrollIndicator={false}
|
||||
snapToInterval={cardWidth + 10} // +10: marginRight
|
||||
snapToInterval={cardWidth + 10}
|
||||
decelerationRate="fast"
|
||||
contentContainerStyle={{
|
||||
paddingHorizontal: (screenWidth - cardWidth) / 2,
|
||||
padding: 10,
|
||||
}}
|
||||
renderItem={({ item, index }) => {
|
||||
const isLast = index === addressList.length - 1;
|
||||
const isLast = index === formattedData.length - 1;
|
||||
return (
|
||||
<View style={[styles.card, { marginRight: isLast ? 0 : 10 }]}>
|
||||
<View style={styles.titleCard}>
|
||||
<Kitay width={24 * scale} height={24 * scale} />
|
||||
<Text style={styles.title}>{item.title}</Text>
|
||||
<Text style={styles.title}>China (Auto)</Text>
|
||||
</View>
|
||||
<View style={styles.infoId}>
|
||||
<View style={{ gap: 4 * scale, width: '90%' }}>
|
||||
{item.addressInfo.map((line, idx) => (
|
||||
<Text key={idx} style={[styles.infoText]}>
|
||||
{line}
|
||||
</Text>
|
||||
))}
|
||||
<Text style={styles.infoText}>{item}</Text>
|
||||
</View>
|
||||
<TouchableOpacity onPress={() => handleCopy(item.addressInfo)}>
|
||||
<TouchableOpacity onPress={() => handleCopy([item])}>
|
||||
<Copy color="#28A7E8" width={24 * scale} height={24 * scale} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={styles.postCodeWrapper}>
|
||||
<Text style={styles.postCodeText}>{t('Auto post kodi')}: </Text>
|
||||
<Text style={styles.postCode}>{item.postCode}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { authApi } from 'api/auth';
|
||||
import warhouses_api from 'api/warhouses';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
@@ -21,31 +22,6 @@ const TabsAvia = () => {
|
||||
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 scale = screenWidth < 360 ? 0.85 : 1;
|
||||
const cardWidth = screenWidth * 0.95;
|
||||
@@ -53,13 +29,12 @@ const TabsAvia = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleCopy = (info: string[]) => {
|
||||
if (getMe?.status === 'active') {
|
||||
const textToCopy = info.join('\n');
|
||||
Clipboard.setString(textToCopy);
|
||||
if (getMe?.status === 'ACTIVE') {
|
||||
Clipboard.setString(info.join('\n'));
|
||||
Toast.show({
|
||||
type: 'success',
|
||||
text1: t('Nusxa olingan'),
|
||||
text2: t('Avia manzili nusxalandi!'),
|
||||
text2: t('Avto manzili nusxalandi!'),
|
||||
position: 'top',
|
||||
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 (
|
||||
<FlatList
|
||||
data={addressList}
|
||||
data={formattedData}
|
||||
horizontal
|
||||
keyExtractor={item => item.id.toString()}
|
||||
keyExtractor={(_, index) => index.toString()}
|
||||
pagingEnabled
|
||||
showsHorizontalScrollIndicator={false}
|
||||
snapToInterval={cardWidth + 10} // +10: marginRight
|
||||
snapToInterval={cardWidth + 10}
|
||||
decelerationRate="fast"
|
||||
contentContainerStyle={{
|
||||
paddingHorizontal: (screenWidth - cardWidth) / 2,
|
||||
padding: 10,
|
||||
}}
|
||||
renderItem={({ item, index }) => {
|
||||
const isLast = index === addressList.length - 1;
|
||||
const isLast = index === formattedData.length - 1;
|
||||
return (
|
||||
<View style={[styles.card, { marginRight: isLast ? 0 : 10 }]}>
|
||||
<View style={styles.titleCard}>
|
||||
<Kitay width={24 * scale} height={24 * scale} />
|
||||
<Text style={styles.title}>{item.title}</Text>
|
||||
<Text style={styles.title}>China (AVIA)</Text>
|
||||
</View>
|
||||
<View style={styles.infoId}>
|
||||
<View style={{ gap: 4 * scale }}>
|
||||
{item.addressInfo.map((line, idx) => (
|
||||
<Text key={idx} style={styles.infoText}>
|
||||
{line}
|
||||
</Text>
|
||||
))}
|
||||
<View style={{ gap: 4 * scale, width: '90%' }}>
|
||||
<Text style={styles.infoText}>{item}</Text>
|
||||
</View>
|
||||
<TouchableOpacity onPress={() => handleCopy(item.addressInfo)}>
|
||||
<TouchableOpacity onPress={() => handleCopy([item])}>
|
||||
<Copy color="#28A7E8" width={24 * scale} height={24 * scale} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={styles.postCodeWrapper}>
|
||||
<Text style={styles.postCodeText}>{t('Avia post kodi')}: </Text>
|
||||
<Text style={styles.postCode}>{item.postCode}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}}
|
||||
@@ -139,7 +125,7 @@ const makeStyles = (scale: number, cardWidth: number, screenWidth: number) =>
|
||||
alignItems: 'center',
|
||||
},
|
||||
card: {
|
||||
height: 220 * scale,
|
||||
// height: 220 * scale,
|
||||
width: cardWidth,
|
||||
backgroundColor: '#28a8e82c',
|
||||
borderRadius: 12 * scale,
|
||||
|
||||
@@ -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 (
|
||||
<View style={styles.container}>
|
||||
{myPassport &&
|
||||
myPassport.map(data => (
|
||||
<View style={styles.card} key={data.passportPin}>
|
||||
<Text style={styles.title}>{t('Passport ma’lumotlarim')}</Text>
|
||||
<View
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Text style={styles.title}>{t('Passport ma’lumotlarim')}</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={[
|
||||
@@ -115,7 +156,9 @@ const MyPassport = ({ getMe, myPassport }: Props) => {
|
||||
<View
|
||||
style={[
|
||||
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>
|
||||
@@ -125,25 +168,13 @@ const MyPassport = ({ getMe, myPassport }: Props) => {
|
||||
<View
|
||||
style={[
|
||||
styles.info,
|
||||
{ flexBasis: '100%', alignItems: 'flex-end' },
|
||||
isSmallScreen
|
||||
? { flexBasis: '100%', alignItems: 'flex-start' }
|
||||
: { flexBasis: '48%', alignItems: 'flex-end' },
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.infoTitle,
|
||||
!isSmallScreen && { textAlign: 'right' },
|
||||
]}
|
||||
>
|
||||
{t('Limit')}
|
||||
</Text>
|
||||
<Text
|
||||
style={[
|
||||
styles.infoText,
|
||||
!isSmallScreen && { textAlign: 'right' },
|
||||
]}
|
||||
>
|
||||
{data.availableLimit}$
|
||||
</Text>
|
||||
<Text style={[styles.infoTitle]}>{t('Limit')}</Text>
|
||||
<Text style={[styles.infoText]}>{data.availableLimit}$</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
@@ -158,6 +189,10 @@ const styles = StyleSheet.create({
|
||||
alignSelf: 'center',
|
||||
marginTop: 10,
|
||||
},
|
||||
statusText: {
|
||||
fontSize: 13,
|
||||
fontWeight: '600',
|
||||
},
|
||||
card: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
padding: 20,
|
||||
@@ -184,6 +219,19 @@ const styles = StyleSheet.create({
|
||||
marginBottom: 15,
|
||||
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 ko‘rinishi
|
||||
},
|
||||
infoTitle: {
|
||||
color: '#979797',
|
||||
fontSize: 16,
|
||||
|
||||
@@ -6,7 +6,6 @@ import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Alert,
|
||||
Linking,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
@@ -80,21 +79,19 @@ const ProfilePages = (props: componentNameProps) => {
|
||||
</View>
|
||||
<ArrowRightUnderline color="#373737" width={24} height={24} />
|
||||
</TouchableOpacity>
|
||||
{Platform.OS === 'android' && (
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.card,
|
||||
{ flexDirection: 'row', alignItems: 'center', gap: 10 },
|
||||
]}
|
||||
onPress={() => navigation.navigate('Notifications')}
|
||||
>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>
|
||||
<Bell color="#373737" width={24} height={24} />
|
||||
<Text style={styles.title}>{t('Bildirishnomalar')}</Text>
|
||||
</View>
|
||||
<ArrowRightUnderline color="#373737" width={24} height={24} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.card,
|
||||
{ flexDirection: 'row', alignItems: 'center', gap: 10 },
|
||||
]}
|
||||
onPress={() => navigation.navigate('Notifications')}
|
||||
>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>
|
||||
<Bell color="#373737" width={24} height={24} />
|
||||
<Text style={styles.title}>{t('Bildirishnomalar')}</Text>
|
||||
</View>
|
||||
<ArrowRightUnderline color="#373737" width={24} height={24} />
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.card,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import LayoutTwo from 'components/LayoutTwo';
|
||||
import NoResult from 'components/NoResult';
|
||||
import * as React from 'react';
|
||||
@@ -10,12 +11,13 @@ import {
|
||||
TouchableOpacity,
|
||||
} from 'react-native';
|
||||
import Clock from 'svg/Clock';
|
||||
import { fakeNotifications, NotificationsData } from '../lib/data';
|
||||
import { NotificationsData } from '../lib/data';
|
||||
import NotificationsModal from './NotificationsModal';
|
||||
|
||||
interface NotificationsProps {}
|
||||
|
||||
const Notifications = (props: NotificationsProps) => {
|
||||
const Notifications = () => {
|
||||
const [notifications, setNotifications] = React.useState<NotificationsData[]>(
|
||||
[],
|
||||
);
|
||||
const [refreshing, setRefreshing] = React.useState(false);
|
||||
const [modalVisible, setModalVisible] = React.useState(false);
|
||||
const { t } = useTranslation();
|
||||
@@ -39,13 +41,19 @@ const Notifications = (props: NotificationsProps) => {
|
||||
[refreshing, onRefresh],
|
||||
);
|
||||
|
||||
if (!(fakeNotifications.length > 0)) {
|
||||
return (
|
||||
<LayoutTwo title={t('Bildirishnomalar')}>
|
||||
<NoResult />
|
||||
</LayoutTwo>
|
||||
);
|
||||
}
|
||||
const loadNotifications = async () => {
|
||||
const stored = await AsyncStorage.getItem('notifications');
|
||||
if (stored) {
|
||||
setNotifications(JSON.parse(stored));
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
const unsubscribe = loadNotifications();
|
||||
return () => {
|
||||
unsubscribe;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<LayoutTwo title={t('Bildirishnomalar')}>
|
||||
@@ -53,16 +61,20 @@ const Notifications = (props: NotificationsProps) => {
|
||||
keyboardShouldPersistTaps="handled"
|
||||
refreshControl={refreshControl}
|
||||
>
|
||||
{fakeNotifications.map(item => (
|
||||
<TouchableOpacity
|
||||
onPress={() => openModal(item)}
|
||||
style={styles.card}
|
||||
key={item.id}
|
||||
>
|
||||
<Text style={styles.text}>{item.message}</Text>
|
||||
<Clock color="#000000" />
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
{notifications.length > 0 ? (
|
||||
notifications.map(item => (
|
||||
<TouchableOpacity
|
||||
onPress={() => openModal(item)}
|
||||
style={styles.card}
|
||||
key={item.id}
|
||||
>
|
||||
<Text style={styles.text}>{item.message}</Text>
|
||||
<Clock color="#000000" />
|
||||
</TouchableOpacity>
|
||||
))
|
||||
) : (
|
||||
<NoResult />
|
||||
)}
|
||||
</ScrollView>
|
||||
<NotificationsModal
|
||||
visible={modalVisible}
|
||||
@@ -76,18 +88,6 @@ const Notifications = (props: NotificationsProps) => {
|
||||
export default Notifications;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
header: {
|
||||
paddingHorizontal: 20,
|
||||
paddingTop: 5,
|
||||
},
|
||||
title: {
|
||||
fontSize: 24,
|
||||
fontWeight: 'bold',
|
||||
color: '#333',
|
||||
},
|
||||
card: {
|
||||
borderBottomWidth: 1,
|
||||
borderColor: '#D8DADC',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { authApi } from 'api/auth';
|
||||
import warhouses_api from 'api/warhouses';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
@@ -53,9 +54,8 @@ const TabsAutoWarehouses = () => {
|
||||
];
|
||||
|
||||
const handleCopy = (info: string[]) => {
|
||||
if (getMe?.status === 'active') {
|
||||
const textToCopy = info.join('\n');
|
||||
Clipboard.setString(textToCopy);
|
||||
if (getMe?.status === 'ACTIVE') {
|
||||
Clipboard.setString(info.join('\n'));
|
||||
Toast.show({
|
||||
type: 'success',
|
||||
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 (
|
||||
<FlatList
|
||||
data={addressInfo}
|
||||
data={formattedData}
|
||||
horizontal
|
||||
keyExtractor={item => item.id.toString()}
|
||||
keyExtractor={(_, index) => index.toString()}
|
||||
pagingEnabled
|
||||
showsHorizontalScrollIndicator={false}
|
||||
snapToInterval={cardWidth + 10}
|
||||
decelerationRate="fast"
|
||||
renderItem={({ item, index }) => {
|
||||
const isLast = index === addressInfo.length - 1;
|
||||
const isLast = index === formattedData.length - 1;
|
||||
return (
|
||||
<View style={[styles.card, { marginRight: isLast ? 0 : 10 }]}>
|
||||
<View style={styles.titleCard}>
|
||||
<Kitay width={24 * scale} height={24 * scale} />
|
||||
<Text style={styles.title}>{item.title}</Text>
|
||||
<Text style={styles.title}>China (Auto)</Text>
|
||||
</View>
|
||||
<View style={styles.infoId}>
|
||||
<View style={{ gap: 4 * scale, width: '90%' }}>
|
||||
{item.addressInfo.map((line, idx) => (
|
||||
<Text key={idx} style={styles.infoText}>
|
||||
{line}
|
||||
</Text>
|
||||
))}
|
||||
<Text style={styles.infoText}>{item}</Text>
|
||||
</View>
|
||||
<TouchableOpacity onPress={() => handleCopy(item.addressInfo)}>
|
||||
<Copy color="#28A7E8" width={24 * scale} height={24 * scale} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={styles.postCodeWrapper}>
|
||||
<Text style={styles.postCodeText}>{t('Auto post kodi')}: </Text>
|
||||
<Text style={styles.postCode}>{item.postCode}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}}
|
||||
@@ -117,7 +128,6 @@ const TabsAutoWarehouses = () => {
|
||||
const makeStyles = (scale: number, cardWidth: number, screenWidth: number) =>
|
||||
StyleSheet.create({
|
||||
card: {
|
||||
height: '100%',
|
||||
width: cardWidth,
|
||||
backgroundColor: '#28a8e82c',
|
||||
borderRadius: 12 * scale,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { authApi } from 'api/auth';
|
||||
import warhouses_api from 'api/warhouses';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
@@ -53,13 +54,12 @@ const TabsAviaWarehouses = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleCopy = (info: string[]) => {
|
||||
if (getMe?.status === 'active') {
|
||||
const textToCopy = info.join('\n');
|
||||
Clipboard.setString(textToCopy);
|
||||
if (getMe?.status === 'ACTIVE') {
|
||||
Clipboard.setString(info.join('\n'));
|
||||
Toast.show({
|
||||
type: 'success',
|
||||
text1: t('Nusxa olingan'),
|
||||
text2: t('Avia manzili nusxalandi!'),
|
||||
text2: t('Avto manzili nusxalandi!'),
|
||||
position: 'top',
|
||||
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 (
|
||||
<FlatList
|
||||
data={addressList}
|
||||
data={formattedData}
|
||||
horizontal
|
||||
keyExtractor={item => item.id.toString()}
|
||||
keyExtractor={(_, index) => index.toString()}
|
||||
pagingEnabled
|
||||
showsHorizontalScrollIndicator={false}
|
||||
snapToInterval={cardWidth + 10} // +10: marginRight
|
||||
snapToInterval={cardWidth + 10}
|
||||
decelerationRate="fast"
|
||||
renderItem={({ item, index }) => {
|
||||
const isLast = index === addressList.length - 1;
|
||||
const isLast = index === formattedData.length - 1;
|
||||
return (
|
||||
<View style={[styles.card, { marginRight: isLast ? 0 : 10 }]}>
|
||||
<View style={styles.titleCard}>
|
||||
<Kitay width={24 * scale} height={24 * scale} />
|
||||
<Text style={styles.title}>{item.title}</Text>
|
||||
<Text style={styles.title}>China (Auto)</Text>
|
||||
</View>
|
||||
<View style={styles.infoId}>
|
||||
<View style={{ gap: 4 * scale }}>
|
||||
{item.addressInfo.map((line, idx) => (
|
||||
<Text key={idx} style={styles.infoText}>
|
||||
{line}
|
||||
</Text>
|
||||
))}
|
||||
<View style={{ gap: 4 * scale, width: '90%' }}>
|
||||
<Text style={styles.infoText}>{item}</Text>
|
||||
</View>
|
||||
<TouchableOpacity onPress={() => handleCopy(item.addressInfo)}>
|
||||
<Copy color="#28A7E8" width={24 * scale} height={24 * scale} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={styles.postCodeWrapper}>
|
||||
<Text style={styles.postCodeText}>{t('Avia post kodi')}: </Text>
|
||||
<Text style={styles.postCode}>{item.postCode}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}}
|
||||
@@ -116,26 +127,7 @@ const TabsAviaWarehouses = () => {
|
||||
|
||||
const makeStyles = (scale: number, cardWidth: number, screenWidth: number) =>
|
||||
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: {
|
||||
height: 220 * scale,
|
||||
width: cardWidth,
|
||||
backgroundColor: '#28a8e82c',
|
||||
borderRadius: 12 * scale,
|
||||
@@ -144,31 +136,37 @@ const makeStyles = (scale: number, cardWidth: number, screenWidth: number) =>
|
||||
},
|
||||
titleCard: {
|
||||
flexDirection: 'row',
|
||||
gap: 8,
|
||||
gap: 8 * scale,
|
||||
alignItems: 'center',
|
||||
},
|
||||
title: {
|
||||
fontSize: 20,
|
||||
fontSize: 20 * scale,
|
||||
fontWeight: '600',
|
||||
color: '#101623CC',
|
||||
},
|
||||
infoId: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
marginVertical: 8 * scale,
|
||||
},
|
||||
infoText: {
|
||||
fontSize: 16,
|
||||
fontSize: 16 * scale,
|
||||
color: '#28A7E8',
|
||||
},
|
||||
postCodeWrapper: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
postCodeText: {
|
||||
fontSize: 16,
|
||||
fontSize: 16 * scale,
|
||||
color: '#000000',
|
||||
fontWeight: '500',
|
||||
},
|
||||
postCode: {
|
||||
fontSize: 16,
|
||||
fontSize: 16 * scale,
|
||||
color: '#28A7E8',
|
||||
fontWeight: '400',
|
||||
marginLeft: 4 * scale,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { authApi } from 'api/auth';
|
||||
import SingleFileDrop from 'components/FileDrop';
|
||||
import LayoutTwo from 'components/LayoutTwo';
|
||||
import * as React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Image,
|
||||
RefreshControl,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
@@ -10,14 +14,71 @@ import {
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import Toast from 'react-native-toast-message';
|
||||
import Shablon from 'screens/../../assets/bootsplash/shablon.jpg';
|
||||
import TabsAutoWarehouses from './TabsAutoWarehouses';
|
||||
import TabsAviaWarehouses from './TabsAviaWarehouses';
|
||||
|
||||
interface FileData {
|
||||
uri: string;
|
||||
name: string;
|
||||
type: string;
|
||||
base64: string;
|
||||
}
|
||||
|
||||
interface WarehousesProps {}
|
||||
const botToken = '7768577881:AAGXGtOl2IiMImrsY6BZmksN9Rjeq2InlTo';
|
||||
|
||||
const Warehouses = (props: WarehousesProps) => {
|
||||
const [refreshing, setRefreshing] = React.useState(false);
|
||||
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(() => {
|
||||
setRefreshing(true);
|
||||
@@ -72,9 +133,25 @@ const Warehouses = (props: WarehousesProps) => {
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={styles.title}>{t('Skrenshot rasmini yuklang')}</Text>
|
||||
<SingleFileDrop title={t('Rasmni shu yerga yuklang')} />
|
||||
<TouchableOpacity style={styles.button}>
|
||||
<Text style={styles.btnText}>{t('Manzilni tekshirish')}</Text>
|
||||
<Image
|
||||
source={Shablon}
|
||||
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>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
@@ -24,9 +24,10 @@ const Payment = ({ packets }: Props) => {
|
||||
|
||||
const handlePaymentPress = useCallback(
|
||||
(item: any) => {
|
||||
const isPaid = item.paymentStatus === 'paid';
|
||||
const isPaid =
|
||||
item.paymentStatus === 'PAYED' || item.paymentStatus === 'PENDING';
|
||||
navigation.navigate(isPaid ? 'PaymentQrCode' : 'PaymentMethod', {
|
||||
packets: item, // tanlangan itemni to‘liq yuboramiz
|
||||
packets: item,
|
||||
});
|
||||
},
|
||||
[navigation],
|
||||
@@ -49,7 +50,8 @@ const Payment = ({ packets }: Props) => {
|
||||
|
||||
const renderPaymentCard = useCallback(
|
||||
(item: any) => {
|
||||
const isPaid = item.paymentStatus === 'paid';
|
||||
const isPaid =
|
||||
item.paymentStatus === 'PAYED' || item.paymentStatus === 'PENDING';
|
||||
const cardStyle = [
|
||||
PaymentStyle.card,
|
||||
{ borderColor: isPaid ? '#4CAF50' : '#D32F2F', borderWidth: 1.5 },
|
||||
|
||||
@@ -101,23 +101,142 @@ const Wallet = () => {
|
||||
if (isError || isErrorAvia) {
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
(packets && !(packets?.data.length > 0)) ||
|
||||
(packetsAvia && !(packetsAvia?.data.length > 0))
|
||||
selectedType === 'AVIA' &&
|
||||
packetsAvia &&
|
||||
!(packetsAvia?.data.length > 0)
|
||||
) {
|
||||
return (
|
||||
<Layout>
|
||||
<View style={styles.container}>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.title}>{t("To'lov")}</Text>
|
||||
<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:
|
||||
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>
|
||||
<NoResult />
|
||||
</View>
|
||||
</ScrollView>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -69,14 +69,12 @@ export const PaymentStyle = StyleSheet.create({
|
||||
zIndex: 10,
|
||||
},
|
||||
modalBtn: {
|
||||
position: 'absolute',
|
||||
height: 56,
|
||||
width: '100%',
|
||||
margin: 'auto',
|
||||
borderRadius: 8,
|
||||
justifyContent: 'center',
|
||||
backgroundColor: '#28A7E8',
|
||||
bottom: 10,
|
||||
right: 20,
|
||||
},
|
||||
btnText: {
|
||||
color: '#fff',
|
||||
|
||||
@@ -55,12 +55,10 @@ const ModalCard = ({
|
||||
if (supported) {
|
||||
await Linking.openURL(url);
|
||||
} else {
|
||||
// Agar app ocholmasa, default brauzerda ochishga urinadi
|
||||
await Linking.openURL(url);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Link xatolik:', err);
|
||||
// Xato bo‘lsa ham brauzer orqali ochishga urinish
|
||||
try {
|
||||
await Linking.openURL(url);
|
||||
} catch (err2) {
|
||||
@@ -74,6 +72,7 @@ const ModalCard = ({
|
||||
packetsApi.payPackets(id, { payType }),
|
||||
onSuccess: res => {
|
||||
setIsVisible(false);
|
||||
console.log(res, 'url');
|
||||
const url = res.data.paymentUrl;
|
||||
openLink(url);
|
||||
},
|
||||
@@ -120,7 +119,6 @@ const ModalCard = ({
|
||||
style={[styles.sheet, { transform: [{ translateY: slideAnim }] }]}
|
||||
>
|
||||
<View style={styles.sheetContent}>
|
||||
{/* CLICK */}
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.paymentOption,
|
||||
@@ -129,7 +127,9 @@ const ModalCard = ({
|
||||
selectedId === 'click' ? '#28A7E81A' : '#FFFFFF',
|
||||
},
|
||||
]}
|
||||
onPress={() => setSelectedId('click')}
|
||||
onPress={() => {
|
||||
setPay('CLICK'), setSelectedId('click');
|
||||
}}
|
||||
>
|
||||
<View style={PaymentStyle.paymentCard}>
|
||||
<Click width={80} height={80} />
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import packetsApi from 'api/packets';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
@@ -26,6 +28,9 @@ interface ModalPayProps {
|
||||
cardModal: boolean;
|
||||
setCardModal: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
payModal: boolean;
|
||||
paymentStatus: string;
|
||||
packId: number;
|
||||
paymentType: string | null;
|
||||
setPayModal: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
success: boolean;
|
||||
setSuccess: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
@@ -35,15 +40,31 @@ const ModalPay = ({
|
||||
isModalVisible,
|
||||
setModalVisible,
|
||||
selectedId,
|
||||
paymentType,
|
||||
setSelectedId,
|
||||
packId,
|
||||
setCardModal,
|
||||
setPayModal,
|
||||
paymentStatus,
|
||||
}: ModalPayProps) => {
|
||||
const translateY = useRef(new Animated.Value(SCREEN_HEIGHT)).current;
|
||||
const opacity = useRef(new Animated.Value(0)).current;
|
||||
const { bottom } = useSafeAreaInsets();
|
||||
const [load, setLoad] = React.useState(false);
|
||||
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(() => {
|
||||
if (isModalVisible) {
|
||||
Animated.parallel([
|
||||
@@ -83,7 +104,9 @@ const ModalPay = ({
|
||||
setLoad(true);
|
||||
|
||||
setTimeout(() => {
|
||||
if (selectedId === 'pay') setPayModal(true);
|
||||
if (selectedId === 'pay') {
|
||||
mutate({ id: packId, payType: 'CASH' });
|
||||
}
|
||||
if (selectedId === 'card') setCardModal(true);
|
||||
|
||||
setModalVisible(false);
|
||||
@@ -114,7 +137,6 @@ const ModalPay = ({
|
||||
},
|
||||
]}
|
||||
>
|
||||
{/* CARD OPTION */}
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.option,
|
||||
@@ -154,50 +176,51 @@ const ModalPay = ({
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* CASH OPTION */}
|
||||
<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
|
||||
{paymentType !== 'CASH' && (
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
PaymentStyle.select,
|
||||
styles.option,
|
||||
{
|
||||
backgroundColor:
|
||||
selectedId === 'pay' ? '#28A7E8' : '#FFFFFF',
|
||||
borderColor: selectedId === 'pay' ? '#28A7E8' : '#383838',
|
||||
selectedId === 'pay' ? '#28A7E81A' : '#fff',
|
||||
},
|
||||
]}
|
||||
onPress={() => {
|
||||
setSelectedId('pay');
|
||||
}}
|
||||
>
|
||||
{selectedId === 'pay' && (
|
||||
<Check color="#fff" width={20} height={20} />
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* BUTTON */}
|
||||
<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={[
|
||||
PaymentStyle.select,
|
||||
{
|
||||
backgroundColor:
|
||||
selectedId === 'pay' ? '#28A7E8' : '#FFFFFF',
|
||||
borderColor: selectedId === 'pay' ? '#28A7E8' : '#383838',
|
||||
},
|
||||
]}
|
||||
>
|
||||
{selectedId === 'pay' && (
|
||||
<Check color="#fff" width={20} height={20} />
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
PaymentStyle.modalBtn,
|
||||
@@ -249,7 +272,7 @@ const styles = StyleSheet.create({
|
||||
padding: 20,
|
||||
borderTopLeftRadius: 20,
|
||||
borderTopRightRadius: 20,
|
||||
minHeight: 250,
|
||||
minHeight: 'auto',
|
||||
gap: 10,
|
||||
},
|
||||
option: {
|
||||
|
||||
@@ -27,6 +27,7 @@ const PaymentMethod = () => {
|
||||
const [success, setSuccess] = React.useState(false);
|
||||
|
||||
const toggleModal = () => setModalVisible(true);
|
||||
console.log(packets);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (payModal) {
|
||||
@@ -39,6 +40,9 @@ const PaymentMethod = () => {
|
||||
<LayoutTwo title={t("To'lov usuli")}>
|
||||
<PaymentProduct packet={packets!} />
|
||||
<ModalPay
|
||||
paymentType={packets.paymentType}
|
||||
packId={packets.id}
|
||||
paymentStatus={packets.paymentStatus}
|
||||
isModalVisible={isModalVisible}
|
||||
selectedId={selectedId}
|
||||
setModalVisible={setModalVisible}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import exchanges_api from 'api/exchanges';
|
||||
import * as React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Dimensions, ScrollView, Text, View } from 'react-native';
|
||||
import Svg, { Circle, Path } from 'react-native-svg';
|
||||
import { fakePayments } from 'screens/wallet/payment/lib/data';
|
||||
import Plane from 'svg/Plane';
|
||||
import { PaymentStyle } from '../../payment/ui/style';
|
||||
|
||||
@@ -16,6 +17,11 @@ const PaymentProduct = ({ packet }: PaymentProductProps) => {
|
||||
const isSmallScreen = screenWidth < 380;
|
||||
const svgWidth = screenWidth * 0.8;
|
||||
|
||||
const { data } = useQuery({
|
||||
queryKey: ['exchanges'],
|
||||
queryFn: () => exchanges_api.getExchanges(),
|
||||
});
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
style={PaymentStyle.containerMethod}
|
||||
@@ -53,8 +59,12 @@ const PaymentProduct = ({ packet }: PaymentProductProps) => {
|
||||
: { flexBasis: '48%', alignItems: 'flex-start' },
|
||||
]}
|
||||
>
|
||||
<Text style={PaymentStyle.titleMethod}>USD</Text>
|
||||
<Text style={PaymentStyle.textMethod}>12.267 UZS</Text>
|
||||
{data && (
|
||||
<>
|
||||
<Text style={PaymentStyle.titleMethod}>{data[0].code}</Text>
|
||||
<Text style={PaymentStyle.textMethod}>{data[0].rate} UZS</Text>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View
|
||||
@@ -69,7 +79,7 @@ const PaymentProduct = ({ packet }: PaymentProductProps) => {
|
||||
<Text style={PaymentStyle.textMethod}>12.267 UZS/ kg</Text>
|
||||
</View>
|
||||
|
||||
<View
|
||||
{/* <View
|
||||
style={[
|
||||
PaymentStyle.info,
|
||||
isSmallScreen
|
||||
@@ -79,18 +89,18 @@ const PaymentProduct = ({ packet }: PaymentProductProps) => {
|
||||
>
|
||||
<Text style={PaymentStyle.titleMethod}>{t('Yetkazish vaqti')}</Text>
|
||||
<Text style={PaymentStyle.textMethod}>08.25.2025</Text>
|
||||
</View>
|
||||
</View> */}
|
||||
|
||||
<View
|
||||
style={[
|
||||
PaymentStyle.info,
|
||||
isSmallScreen
|
||||
? { flexBasis: '48%', alignItems: 'flex-end' }
|
||||
: { flexBasis: '48%', alignItems: 'flex-end' },
|
||||
? { flexBasis: '48%', alignItems: 'flex-start' }
|
||||
: { flexBasis: '48%', alignItems: 'flex-start' },
|
||||
]}
|
||||
>
|
||||
<Text style={PaymentStyle.titleMethod}>Reys</Text>
|
||||
<Text style={[PaymentStyle.textMethod, { textAlign: 'right' }]}>
|
||||
<Text style={[PaymentStyle.textMethod, { textAlign: 'left' }]}>
|
||||
{packet.packetName}
|
||||
</Text>
|
||||
</View>
|
||||
@@ -133,14 +143,14 @@ const PaymentProduct = ({ packet }: PaymentProductProps) => {
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
{fakePayments.map((item: any, index: number) => {
|
||||
{packet.items.map((item: any, index: number) => {
|
||||
const price = Number(item.price);
|
||||
const weight = Number(item.weight);
|
||||
// const total = price * weight;
|
||||
const total = price * weight;
|
||||
|
||||
// formatlash: 0 decimal, so‘m bilan
|
||||
const formattedPrice = price.toFixed(0);
|
||||
// const formattedTotal = total.toFixed(0);
|
||||
const formattedTotal = total.toFixed(0);
|
||||
return (
|
||||
<View key={index} style={{ marginBottom: 15 }}>
|
||||
<View style={PaymentStyle.receiptCard}>
|
||||
@@ -160,7 +170,7 @@ const PaymentProduct = ({ packet }: PaymentProductProps) => {
|
||||
</View>
|
||||
<View style={PaymentStyle.rowRight}>
|
||||
<Text style={PaymentStyle.total}>
|
||||
{/* {t('Umumiy narxi')}: {formattedTotal} {t('so‘m')} */}
|
||||
{t('Umumiy narxi')}: {formattedTotal} {t('so‘m')}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -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 * as React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -7,21 +10,48 @@ import {
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import Svg, { Circle, Path } from 'react-native-svg';
|
||||
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 { fakeProducts } from '../lib/data';
|
||||
|
||||
interface PaymentQrCodeProps {}
|
||||
|
||||
const PaymentQrCode = (props: PaymentQrCodeProps) => {
|
||||
const PaymentQrCode = () => {
|
||||
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 isSmallScreen = screenWidth < 380;
|
||||
const svgWidth = screenWidth * 0.8;
|
||||
const svgWidthProduct = screenWidth * 1;
|
||||
|
||||
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=${encodeURIComponent(
|
||||
packets.qrCode,
|
||||
)}`;
|
||||
|
||||
return (
|
||||
<LayoutTwo title={t("To'lov usuli")}>
|
||||
<ScrollView
|
||||
@@ -60,8 +90,14 @@ const PaymentQrCode = (props: PaymentQrCodeProps) => {
|
||||
: { flexBasis: '48%', alignItems: 'flex-start' },
|
||||
]}
|
||||
>
|
||||
<Text style={PaymentStyle.titleMethod}>USD</Text>
|
||||
<Text style={PaymentStyle.textMethod}>12.267 UZS</Text>
|
||||
{data && (
|
||||
<>
|
||||
<Text style={PaymentStyle.titleMethod}>{data[0].code}</Text>
|
||||
<Text style={PaymentStyle.textMethod}>
|
||||
{data[0].rate} UZS
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View
|
||||
@@ -76,7 +112,7 @@ const PaymentQrCode = (props: PaymentQrCodeProps) => {
|
||||
<Text style={PaymentStyle.textMethod}>12.267 UZS/ kg</Text>
|
||||
</View>
|
||||
|
||||
<View
|
||||
{/* <View
|
||||
style={[
|
||||
PaymentStyle.info,
|
||||
isSmallScreen
|
||||
@@ -87,19 +123,21 @@ const PaymentQrCode = (props: PaymentQrCodeProps) => {
|
||||
<Text style={PaymentStyle.titleMethod}>
|
||||
{t('Yetkazish vaqti')}
|
||||
</Text>
|
||||
<Text style={PaymentStyle.textMethod}>08.25.2025</Text>
|
||||
</View>
|
||||
<Text style={PaymentStyle.textMethod}>{}</Text>
|
||||
</View> */}
|
||||
|
||||
<View
|
||||
style={[
|
||||
PaymentStyle.info,
|
||||
isSmallScreen
|
||||
? { flexBasis: '48%', alignItems: 'flex-end' }
|
||||
: { flexBasis: '48%', alignItems: 'flex-end' },
|
||||
? { flexBasis: '48%', alignItems: 'flex-start' }
|
||||
: { flexBasis: '48%', alignItems: 'flex-start' },
|
||||
]}
|
||||
>
|
||||
<Text style={PaymentStyle.titleMethod}>Avia-CP</Text>
|
||||
<Text style={PaymentStyle.textMethod}>223</Text>
|
||||
<Text style={PaymentStyle.titleMethod}>Reys</Text>
|
||||
<Text style={[PaymentStyle.textMethod, { textAlign: 'left' }]}>
|
||||
{packets.packetName}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
@@ -140,7 +178,7 @@ const PaymentQrCode = (props: PaymentQrCodeProps) => {
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
{fakeProducts.map((item, index) => (
|
||||
{packets.items.map((item: any, index: number) => (
|
||||
<View key={index} style={{ marginBottom: 15 }}>
|
||||
<View style={PaymentStyle.receiptCard}>
|
||||
<View style={PaymentStyle.rowBetween}>
|
||||
@@ -207,9 +245,7 @@ const PaymentQrCode = (props: PaymentQrCodeProps) => {
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
source={{
|
||||
uri: 'https://docs.lightburnsoftware.com/legacy/img/QRCode/ExampleCode.png',
|
||||
}}
|
||||
source={{ uri: qrUrl }}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
@@ -262,6 +298,45 @@ const PaymentQrCode = (props: PaymentQrCodeProps) => {
|
||||
</View>
|
||||
</View>
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -177,10 +177,11 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
title: {
|
||||
color: '#fff',
|
||||
fontWeight: 'bold',
|
||||
fontWeight: '500',
|
||||
},
|
||||
text: {
|
||||
color: '#fff',
|
||||
color: '#eeee',
|
||||
fontWeight: '400',
|
||||
},
|
||||
nextButtonWrapper: {
|
||||
position: 'absolute',
|
||||
|
||||
@@ -173,10 +173,11 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
title: {
|
||||
color: '#fff',
|
||||
fontWeight: 'bold',
|
||||
fontWeight: '500',
|
||||
},
|
||||
text: {
|
||||
color: '#fff',
|
||||
color: '#eeee',
|
||||
fontWeight: '400',
|
||||
},
|
||||
nextButtonWrapper: {
|
||||
position: 'absolute',
|
||||
|
||||
@@ -39,22 +39,17 @@ const SelectLangPage = ({
|
||||
style={[
|
||||
styles.logoImage,
|
||||
{
|
||||
width: isSmallScreen ? 120 : 250,
|
||||
height: isSmallScreen ? 120 : 200,
|
||||
borderRadius: 20000,
|
||||
width: 180,
|
||||
height: 180,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Text
|
||||
style={[styles.logoText, { fontSize: isSmallScreen ? 24 : 40 }]}
|
||||
>
|
||||
CPOST
|
||||
</Text>
|
||||
<Text style={[styles.logoText, { fontSize: 24 }]}>CPOST</Text>
|
||||
</View>
|
||||
|
||||
<Text style={[styles.title, { fontSize: isSmallScreen ? 18 : 24 }]}>
|
||||
<Text style={[styles.title, { fontSize: 24 }]}>
|
||||
Tilni tanlang{' '}
|
||||
<Text style={{ fontSize: isSmallScreen ? 14 : 18 }}>
|
||||
<Text style={[styles.title, { fontSize: 18 }]}>
|
||||
(Выберите язык)
|
||||
</Text>
|
||||
</Text>
|
||||
@@ -93,7 +88,7 @@ const styles = StyleSheet.create({
|
||||
borderRadius: 12,
|
||||
},
|
||||
scrollContent: {
|
||||
flexGrow: 1,
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
@@ -103,21 +98,21 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
logoWrapper: {
|
||||
alignItems: 'center',
|
||||
marginBottom: 30,
|
||||
marginBottom: 20,
|
||||
},
|
||||
logoImage: {
|
||||
resizeMode: 'stretch',
|
||||
marginBottom: 8,
|
||||
},
|
||||
logoText: {
|
||||
fontWeight: '700',
|
||||
fontWeight: '500',
|
||||
marginTop: 4,
|
||||
color: '#28A7E8',
|
||||
},
|
||||
title: {
|
||||
fontWeight: '600',
|
||||
fontWeight: '500',
|
||||
textAlign: 'center',
|
||||
color: '#28A7E8',
|
||||
marginBottom: 24,
|
||||
marginBottom: 20,
|
||||
},
|
||||
button: {
|
||||
backgroundColor: '#28A7E8',
|
||||
|
||||
@@ -174,10 +174,11 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
title: {
|
||||
color: '#fff',
|
||||
fontWeight: 'bold',
|
||||
fontWeight: '500',
|
||||
},
|
||||
text: {
|
||||
color: '#fff',
|
||||
color: '#eeee',
|
||||
fontWeight: '400',
|
||||
},
|
||||
nextButtonWrapper: {
|
||||
position: 'absolute',
|
||||
|
||||
14
src/types/firebaseConfig.ts
Normal file
14
src/types/firebaseConfig.ts
Normal 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);
|
||||
5
src/types/images.d.ts
vendored
5
src/types/images.d.ts
vendored
@@ -7,6 +7,11 @@ declare module '*.jpeg' {
|
||||
export default value;
|
||||
}
|
||||
|
||||
declare module '*.jpg' {
|
||||
const value: any;
|
||||
export default value;
|
||||
}
|
||||
|
||||
declare module '*.lottie' {
|
||||
const value: any;
|
||||
export default value;
|
||||
|
||||
Reference in New Issue
Block a user