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

204
App.tsx
View File

@@ -2,7 +2,10 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import { 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 yoq',
sentTime: remoteMessage.sentTime || Date.now(),
};
await AsyncStorage.setItem(
'notifications',
JSON.stringify([newNotification, ...notifications]),
);
} catch (e) {
console.error('Notification saqlashda xato:', e);
}
};
async function onDisplayNotification(remoteMessage: any) {
const channelId = await notifee.createChannel({
id: 'default',
name: 'Umumiy bildirishnomalar',
sound: 'default',
importance: AndroidImportance.HIGH,
});
await notifee.displayNotification({
title:
remoteMessage.notification?.title ||
remoteMessage.data?.title ||
'Yangi xabar',
body:
remoteMessage.notification?.body ||
remoteMessage.data?.body ||
'Matn yoq',
android: {
channelId,
smallIcon: 'ic_launcher_foreground',
sound: 'default',
pressAction: {
id: 'default',
},
},
});
}
async function requestNotificationPermission() {
if (Platform.OS === 'android' && Platform.Version >= 33) {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS,
);
console.log('POST_NOTIFICATIONS permission:', granted);
}
}
export default function App() {
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} />

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

View File

@@ -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 yoq',
android: {
channelId,
smallIcon: 'ic_launcher',
sound: 'default',
pressAction: {
id: 'default',
},
},
});
});
AppRegistry.registerComponent(appName, () => App);

1152
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -14,12 +14,15 @@
},
"dependencies": {
"@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",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,30 +9,37 @@ import {
View,
} 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',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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('Royxatdan otganmisz')}
</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,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -36,12 +36,53 @@ const MyPassport = ({ getMe, myPassport }: Props) => {
});
};
const getStatusMeta = (status?: string) => {
const key = (status || '').toLowerCase();
switch (key) {
case 'active':
return { label: t('Faol'), bg: '#E6F7EE', fg: '#1F9254' };
case 'pending':
return { label: t('Kutilmoqda'), bg: '#FFF7E6', fg: '#B26A00' };
case 'inactive':
case 'blocked':
return { label: t('Faol emas'), bg: '#FDECEF', fg: '#A61D24' };
default:
return { label: t('Faol emas'), bg: '#EDF2F7', fg: '#A61D24' };
}
};
return (
<View style={styles.container}>
{myPassport &&
myPassport.map(data => (
<View style={styles.card} key={data.passportPin}>
<Text style={styles.title}>{t('Passport malumotlarim')}</Text>
<View
style={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<Text style={styles.title}>{t('Passport malumotlarim')}</Text>
{getMe?.status && (
<View
style={[
styles.statusBadge,
{ backgroundColor: getStatusMeta(getMe.status).bg },
]}
>
<Text
style={[
styles.statusText,
{ color: getStatusMeta(getMe.status).fg },
]}
>
{getStatusMeta(getMe.status).label}
</Text>
</View>
)}
</View>
<View style={styles.infoCard}>
<View
style={[
@@ -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 korinishi
},
infoTitle: {
color: '#979797',
fontSize: 16,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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: {

View File

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

View File

@@ -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, som 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('som')} */}
{t('Umumiy narxi')}: {formattedTotal} {t('som')}
</Text>
</View>
</View>

View File

@@ -1,3 +1,6 @@
import { RouteProp, useRoute } from '@react-navigation/native';
import { useQuery } from '@tanstack/react-query';
import exchanges_api from 'api/exchanges';
import LayoutTwo from 'components/LayoutTwo';
import * 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>
);
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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