diff --git a/App.tsx b/App.tsx index 9b09c25..120f0e1 100644 --- a/App.tsx +++ b/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(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} > - + {props => ( )} - - {/* */} @@ -151,32 +274,15 @@ export default function App() { - - - + + - - - diff --git a/android/app/build.gradle b/android/app/build.gradle index 2512b96..f0c82c4 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -165,3 +165,5 @@ dependencies { // Performance dependencies implementation 'androidx.multidex:multidex:2.0.1' } + +apply plugin: 'com.google.gms.google-services' \ No newline at end of file diff --git a/android/app/google-services.json b/android/app/google-services.json index 6c89f64..bbc628c 100644 --- a/android/app/google-services.json +++ b/android/app/google-services.json @@ -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" -} +} \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 939e4e9..5301495 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -4,6 +4,9 @@ + + + { + 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); diff --git a/package-lock.json b/package-lock.json index bb1014c..b85691c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,15 @@ "version": "0.0.1", "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", @@ -36,7 +39,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", @@ -59,6 +64,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", @@ -2393,6 +2399,827 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@firebase/ai": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@firebase/ai/-/ai-2.1.0.tgz", + "integrity": "sha512-4HvFr4YIzNFh0MowJLahOjJDezYSTjQar0XYVu/sAycoxQ+kBsfXuTPRLVXCYfMR5oNwQgYe4Q2gAOYKKqsOyA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.3", + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/ai/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@firebase/analytics": { + "version": "0.10.18", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.18.tgz", + "integrity": "sha512-iN7IgLvM06iFk8BeFoWqvVpRFW3Z70f+Qe2PfCJ7vPIgLPjHXDE774DhCT5Y2/ZU/ZbXPDPD60x/XPWEoZLNdg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/installations": "0.6.19", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/analytics-compat": { + "version": "0.2.24", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.24.tgz", + "integrity": "sha512-jE+kJnPG86XSqGQGhXXYt1tpTbCTED8OQJ/PQ90SEw14CuxRxx/H+lFbWA1rlFtFSsTCptAJtgyRBwr/f00vsw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/analytics": "0.10.18", + "@firebase/analytics-types": "0.8.3", + "@firebase/component": "0.7.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/analytics-compat/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@firebase/analytics-types": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.3.tgz", + "integrity": "sha512-VrIp/d8iq2g501qO46uGz3hjbDb8xzYMrbu8Tp0ovzIzrvJZ2fvmj649gTjge/b7cCCcjT0H37g1gVtlNhnkbg==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/analytics/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@firebase/app": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.14.1.tgz", + "integrity": "sha512-jxTrDbxnGoX7cGz7aP9E7v9iKvBbQfZ8Gz4TH3SfrrkcyIojJM3+hJnlbGnGxHrABts844AxRcg00arMZEyA6Q==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/app-check": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.11.0.tgz", + "integrity": "sha512-XAvALQayUMBJo58U/rxW02IhsesaxxfWVmVkauZvGEz3vOAjMEQnzFlyblqkc2iAaO82uJ2ZVyZv9XzPfxjJ6w==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/app-check-compat": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.4.0.tgz", + "integrity": "sha512-UfK2Q8RJNjYM/8MFORltZRG9lJj11k0nW84rrffiKvcJxLf1jf6IEjCIkCamykHE73C6BwqhVfhIBs69GXQV0g==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check": "0.11.0", + "@firebase/app-check-types": "0.5.3", + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/app-check-compat/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@firebase/app-check-interop-types": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz", + "integrity": "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/app-check-types": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.3.tgz", + "integrity": "sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/app-check/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@firebase/app-compat": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.1.tgz", + "integrity": "sha512-BEy1L6Ufd85ZSP79HDIv0//T9p7d5Bepwy+2mKYkgdXBGKTbFm2e2KxyF1nq4zSQ6RRBxWi0IY0zFVmoBTZlUA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app": "0.14.1", + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/app-compat/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@firebase/app-types": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz", + "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/app/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@firebase/auth-compat": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.6.0.tgz", + "integrity": "sha512-J0lGSxXlG/lYVi45wbpPhcWiWUMXevY4fvLZsN1GHh+po7TZVng+figdHBVhFheaiipU8HZyc7ljw1jNojM2nw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/auth": "1.11.0", + "@firebase/auth-types": "0.13.0", + "@firebase/component": "0.7.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/auth-compat/node_modules/@firebase/auth": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.11.0.tgz", + "integrity": "sha512-5j7+ua93X+IRcJ1oMDTClTo85l7Xe40WSkoJ+shzPrX7OISlVWLdE1mKC57PSD+/LfAbdhJmvKixINBw2ESK6w==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@react-native-async-storage/async-storage": "^1.18.1" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, + "node_modules/@firebase/auth-compat/node_modules/@react-native-async-storage/async-storage": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.24.0.tgz", + "integrity": "sha512-W4/vbwUOYOjco0x3toB8QCr7EjIP6nE9G7o8PMguvvjYT5Awg09lyV4enACRx4s++PPulBiBSjL0KTFx2u0Z/g==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || >=0.60 <1.0" + } + }, + "node_modules/@firebase/auth-compat/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz", + "integrity": "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/auth-types": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.13.0.tgz", + "integrity": "sha512-S/PuIjni0AQRLF+l9ck0YpsMOdE8GO2KU6ubmBB7P+7TJUCQDa3R1dlgYm9UzGbbePMZsp0xzB93f2b/CgxMOg==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/component": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.7.0.tgz", + "integrity": "sha512-wR9En2A+WESUHexjmRHkqtaVH94WLNKt6rmeqZhSLBybg4Wyf0Umk04SZsS6sBq4102ZsDBFwoqMqJYj2IoDSg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/component/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@firebase/data-connect": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.3.11.tgz", + "integrity": "sha512-G258eLzAD6im9Bsw+Qm1Z+P4x0PGNQ45yeUuuqe5M9B1rn0RJvvsQCRHXgE52Z+n9+WX1OJd/crcuunvOGc7Vw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/auth-interop-types": "0.2.4", + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/data-connect/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@firebase/database": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.1.0.tgz", + "integrity": "sha512-gM6MJFae3pTyNLoc9VcJNuaUDej0ctdjn3cVtILo3D5lpp0dmUHHLFN/pUKe7ImyeB1KAvRlEYxvIHNF04Filg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.3", + "@firebase/auth-interop-types": "0.2.4", + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.1.0.tgz", + "integrity": "sha512-8nYc43RqxScsePVd1qe1xxvWNf0OBnbwHxmXJ7MHSuuTVYFO3eLyLW3PiCKJ9fHnmIz4p4LbieXwz+qtr9PZDg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/database": "1.1.0", + "@firebase/database-types": "1.0.16", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/database-compat/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@firebase/database-types": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.16.tgz", + "integrity": "sha512-xkQLQfU5De7+SPhEGAXFBnDryUWhhlFXelEg2YeZOQMCdoe7dL64DDAd77SQsR+6uoXIZY5MB4y/inCs4GTfcw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-types": "0.9.3", + "@firebase/util": "1.13.0" + } + }, + "node_modules/@firebase/database/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@firebase/firestore": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.9.0.tgz", + "integrity": "sha512-5zl0+/h1GvlCSLt06RMwqFsd7uqRtnNZt4sW99k2rKRd6k/ECObIWlEnvthm2cuOSnUmwZknFqtmd1qyYSLUuQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "@firebase/webchannel-wrapper": "1.0.4", + "@grpc/grpc-js": "~1.9.0", + "@grpc/proto-loader": "^0.7.8", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/firestore-compat": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.4.0.tgz", + "integrity": "sha512-4O7v4VFeSEwAZtLjsaj33YrMHMRjplOIYC2CiYsF6o/MboOhrhe01VrTt8iY9Y5EwjRHuRz4pS6jMBT8LfQYJA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/firestore": "4.9.0", + "@firebase/firestore-types": "3.0.3", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/firestore-compat/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@firebase/firestore-types": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.3.tgz", + "integrity": "sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/firestore/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@firebase/functions": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.13.0.tgz", + "integrity": "sha512-2/LH5xIbD8aaLOWSFHAwwAybgSzHIM0dB5oVOL0zZnxFG1LctX2bc1NIAaPk1T+Zo9aVkLKUlB5fTXTkVUQprQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.3", + "@firebase/auth-interop-types": "0.2.4", + "@firebase/component": "0.7.0", + "@firebase/messaging-interop-types": "0.2.3", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/functions-compat": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.4.0.tgz", + "integrity": "sha512-VPgtvoGFywWbQqtvgJnVWIDFSHV1WE6Hmyi5EGI+P+56EskiGkmnw6lEqc/MEUfGpPGdvmc4I9XMU81uj766/g==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/functions": "0.13.0", + "@firebase/functions-types": "0.6.3", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/functions-compat/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@firebase/functions-types": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.3.tgz", + "integrity": "sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/functions/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@firebase/installations": { + "version": "0.6.19", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.19.tgz", + "integrity": "sha512-nGDmiwKLI1lerhwfwSHvMR9RZuIH5/8E3kgUWnVRqqL7kGVSktjLTWEMva7oh5yxQ3zXfIlIwJwMcaM5bK5j8Q==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/util": "1.13.0", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/installations-compat": { + "version": "0.2.19", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.19.tgz", + "integrity": "sha512-khfzIY3EI5LePePo7vT19/VEIH1E3iYsHknI/6ek9T8QCozAZshWT9CjlwOzZrKvTHMeNcbpo/VSOSIWDSjWdQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/installations": "0.6.19", + "@firebase/installations-types": "0.5.3", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/installations-compat/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@firebase/installations-types": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.3.tgz", + "integrity": "sha512-2FJI7gkLqIE0iYsNQ1P751lO3hER+Umykel+TkLwHj6plzWVxqvfclPUZhcKFVQObqloEBTmpi2Ozn7EkCABAA==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/installations/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@firebase/logger": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.5.0.tgz", + "integrity": "sha512-cGskaAvkrnh42b3BA3doDWeBmuHFO/Mx5A83rbRDYakPjO9bJtRL3dX7javzc2Rr/JHZf4HlterTW2lUkfeN4g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/logger/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@firebase/messaging": { + "version": "0.12.23", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.23.tgz", + "integrity": "sha512-cfuzv47XxqW4HH/OcR5rM+AlQd1xL/VhuaeW/wzMW1LFrsFcTn0GND/hak1vkQc2th8UisBcrkVcQAnOnKwYxg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/installations": "0.6.19", + "@firebase/messaging-interop-types": "0.2.3", + "@firebase/util": "1.13.0", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/messaging-compat": { + "version": "0.2.23", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.23.tgz", + "integrity": "sha512-SN857v/kBUvlQ9X/UjAqBoQ2FEaL1ZozpnmL1ByTe57iXkmnVVFm9KqAsTfmf+OEwWI4kJJe9NObtN/w22lUgg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/messaging": "0.12.23", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/messaging-compat/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@firebase/messaging-interop-types": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.3.tgz", + "integrity": "sha512-xfzFaJpzcmtDjycpDeCUj0Ge10ATFi/VHVIvEEjDNc3hodVBQADZ7BWQU7CuFpjSHE+eLuBI13z5F/9xOoGX8Q==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/messaging/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@firebase/performance": { + "version": "0.7.9", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.9.tgz", + "integrity": "sha512-UzybENl1EdM2I1sjYm74xGt/0JzRnU/0VmfMAKo2LSpHJzaj77FCLZXmYQ4oOuE+Pxtt8Wy2BVJEENiZkaZAzQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/installations": "0.6.19", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0", + "web-vitals": "^4.2.4" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/performance-compat": { + "version": "0.2.22", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.22.tgz", + "integrity": "sha512-xLKxaSAl/FVi10wDX/CHIYEUP13jXUjinL+UaNXT9ByIvxII5Ne5150mx6IgM8G6Q3V+sPiw9C8/kygkyHUVxg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/performance": "0.7.9", + "@firebase/performance-types": "0.2.3", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/performance-compat/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@firebase/performance-types": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.3.tgz", + "integrity": "sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/performance/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@firebase/remote-config": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.6.6.tgz", + "integrity": "sha512-Yelp5xd8hM4NO1G1SuWrIk4h5K42mNwC98eWZ9YLVu6Z0S6hFk1mxotAdCRmH2luH8FASlYgLLq6OQLZ4nbnCA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/installations": "0.6.19", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/remote-config-compat": { + "version": "0.2.19", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.19.tgz", + "integrity": "sha512-y7PZAb0l5+5oIgLJr88TNSelxuASGlXyAKj+3pUc4fDuRIdPNBoONMHaIUa9rlffBR5dErmaD2wUBJ7Z1a513Q==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/remote-config": "0.6.6", + "@firebase/remote-config-types": "0.4.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/remote-config-compat/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@firebase/remote-config-types": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.4.0.tgz", + "integrity": "sha512-7p3mRE/ldCNYt8fmWMQ/MSGRmXYlJ15Rvs9Rk17t8p0WwZDbeK7eRmoI1tvCPaDzn9Oqh+yD6Lw+sGLsLg4kKg==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/remote-config/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@firebase/storage": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.14.0.tgz", + "integrity": "sha512-xWWbb15o6/pWEw8H01UQ1dC5U3rf8QTAzOChYyCpafV6Xki7KVp3Yaw2nSklUwHEziSWE9KoZJS7iYeyqWnYFA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/storage-compat": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.4.0.tgz", + "integrity": "sha512-vDzhgGczr1OfcOy285YAPur5pWDEvD67w4thyeCUh6Ys0izN9fNYtA1MJERmNBfqjqu0lg0FM5GLbw0Il21M+g==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/storage": "0.14.0", + "@firebase/storage-types": "0.8.3", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/storage-compat/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@firebase/storage-types": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.3.tgz", + "integrity": "sha512-+Muk7g9uwngTpd8xn9OdF/D48uiQ7I1Fae7ULsWPuKoCH3HU7bfFPhxtJYzyhjdniowhuDpQcfPmuNRAqZEfvg==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/storage/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@firebase/util": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.13.0.tgz", + "integrity": "sha512-0AZUyYUfpMNcztR5l09izHwXkZpghLgCUaAGjtMwXnCg3bj4ml5VgiwqOMOxJ+Nw4qN/zJAaOQBcJ7KGkWStqQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/util/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@firebase/webchannel-wrapper": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.4.tgz", + "integrity": "sha512-6m8+P+dE/RPl4OPzjTxcTbQ0rGeRyeTvAi9KwIffBVCiAMKrfXfLZaqD1F+m8t4B5/Q5aHsMozOgirkH1F5oMQ==", + "license": "Apache-2.0" + }, + "node_modules/@grpc/grpc-js": { + "version": "1.9.15", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz", + "integrity": "sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.7.8", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", + "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -3028,6 +3855,79 @@ "node": ">= 8" } }, + "node_modules/@notifee/react-native": { + "version": "9.1.8", + "resolved": "https://registry.npmjs.org/@notifee/react-native/-/react-native-9.1.8.tgz", + "integrity": "sha512-Az/dueoPerJsbbjRxu8a558wKY+gONUrfoy3Hs++5OqbeMsR0dYe6P+4oN6twrLFyzAhEA1tEoZRvQTFDRmvQg==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native": "*" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, "node_modules/@react-native-async-storage/async-storage": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz", @@ -3359,6 +4259,40 @@ "react-native": ">=0.58.4" } }, + "node_modules/@react-native-firebase/app": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/@react-native-firebase/app/-/app-23.2.0.tgz", + "integrity": "sha512-Ol9d0hnr1j2pPXYaWC+0UsQEcIiyVF7XufvyCifnMdnzfhPgrqnFwgqBCZm/jmWfosHIwhMexEuvaUuWtHDxrg==", + "license": "Apache-2.0", + "dependencies": { + "firebase": "12.1.0" + }, + "peerDependencies": { + "expo": ">=47.0.0", + "react": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + } + } + }, + "node_modules/@react-native-firebase/messaging": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/@react-native-firebase/messaging/-/messaging-23.2.0.tgz", + "integrity": "sha512-k5pr7VWPvApqLjMEyrBLHtGSb9B7vV/wFkNwRJ36KRurSgwQoMIf5lNiD+8Liw0ROQ01Sfm4YRqHfKkc8xvsqg==", + "license": "Apache-2.0", + "peerDependencies": { + "@react-native-firebase/app": "23.2.0", + "expo": ">=47.0.0" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + } + } + }, "node_modules/@react-native-picker/picker": { "version": "2.11.1", "resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-2.11.1.tgz", @@ -5352,6 +6286,11 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/base-64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", + "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==" + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -7617,6 +8556,18 @@ "reusify": "^1.0.4" } }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -7850,6 +8801,86 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/firebase": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-12.1.0.tgz", + "integrity": "sha512-oZucxvfWKuAW4eHHRqGKzC43fLiPqPwHYBHPRNsnkgonqYaq0VurYgqgBosRlEulW+TWja/5Tpo2FpUU+QrfEQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/ai": "2.1.0", + "@firebase/analytics": "0.10.18", + "@firebase/analytics-compat": "0.2.24", + "@firebase/app": "0.14.1", + "@firebase/app-check": "0.11.0", + "@firebase/app-check-compat": "0.4.0", + "@firebase/app-compat": "0.5.1", + "@firebase/app-types": "0.9.3", + "@firebase/auth": "1.11.0", + "@firebase/auth-compat": "0.6.0", + "@firebase/data-connect": "0.3.11", + "@firebase/database": "1.1.0", + "@firebase/database-compat": "2.1.0", + "@firebase/firestore": "4.9.0", + "@firebase/firestore-compat": "0.4.0", + "@firebase/functions": "0.13.0", + "@firebase/functions-compat": "0.4.0", + "@firebase/installations": "0.6.19", + "@firebase/installations-compat": "0.2.19", + "@firebase/messaging": "0.12.23", + "@firebase/messaging-compat": "0.2.23", + "@firebase/performance": "0.7.9", + "@firebase/performance-compat": "0.2.22", + "@firebase/remote-config": "0.6.6", + "@firebase/remote-config-compat": "0.2.19", + "@firebase/storage": "0.14.0", + "@firebase/storage-compat": "0.4.0", + "@firebase/util": "1.13.0" + } + }, + "node_modules/firebase/node_modules/@firebase/auth": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.11.0.tgz", + "integrity": "sha512-5j7+ua93X+IRcJ1oMDTClTo85l7Xe40WSkoJ+shzPrX7OISlVWLdE1mKC57PSD+/LfAbdhJmvKixINBw2ESK6w==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@react-native-async-storage/async-storage": "^1.18.1" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, + "node_modules/firebase/node_modules/@react-native-async-storage/async-storage": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.24.0.tgz", + "integrity": "sha512-W4/vbwUOYOjco0x3toB8QCr7EjIP6nE9G7o8PMguvvjYT5Awg09lyV4enACRx4s++PPulBiBSjL0KTFx2u0Z/g==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || >=0.60 <1.0" + } + }, + "node_modules/firebase/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -8415,6 +9446,12 @@ "node": ">= 0.8" } }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "license": "MIT" + }, "node_modules/https-proxy-agent": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", @@ -8500,6 +9537,12 @@ "node": ">=0.10.0" } }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", + "license": "ISC" + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -10470,6 +11513,12 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -10649,6 +11698,12 @@ "node": ">=6" } }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -12714,6 +13769,30 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -13067,6 +14146,15 @@ } } }, + "node_modules/react-native-device-info": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-14.0.4.tgz", + "integrity": "sha512-NX0wMAknSDBeFnEnSFQ8kkAcQrFHrG4Cl0mVjoD+0++iaKrOupiGpBXqs8xR0SeJyPC5zpdPl4h/SaBGly6UxA==", + "license": "MIT", + "peerDependencies": { + "react-native": "*" + } + }, "node_modules/react-native-dotenv": { "version": "3.4.11", "resolved": "https://registry.npmjs.org/react-native-dotenv/-/react-native-dotenv-3.4.11.tgz", @@ -13079,6 +14167,25 @@ "@babel/runtime": "^7.20.6" } }, + "node_modules/react-native-fs": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/react-native-fs/-/react-native-fs-2.20.0.tgz", + "integrity": "sha512-VkTBzs7fIDUiy/XajOSNk0XazFE9l+QlMAce7lGuebZcag5CnjszB+u4BdqzwaQOdcYb5wsJIsqq4kxInIRpJQ==", + "license": "MIT", + "dependencies": { + "base-64": "^0.1.0", + "utf8": "^3.0.0" + }, + "peerDependencies": { + "react-native": "*", + "react-native-windows": "*" + }, + "peerDependenciesMeta": { + "react-native-windows": { + "optional": true + } + } + }, "node_modules/react-native-geolocation-service": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/react-native-geolocation-service/-/react-native-geolocation-service-5.3.1.tgz", @@ -13384,6 +14491,15 @@ "react-native": "*" } }, + "node_modules/react-native-share": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/react-native-share/-/react-native-share-12.2.0.tgz", + "integrity": "sha512-f6MB9BsKa9xVvu0DKbxq5jw4IyYHqQeqUlCNkD8eAFoJx6SD31ObPAn7SQ6NG9AOuhCy6aYuSJYJvx25DaoMZQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/react-native-skia": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/react-native-skia/-/react-native-skia-0.0.1.tgz", @@ -14068,7 +15184,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "devOptional": true, "funding": [ { "type": "github", @@ -15315,6 +16430,12 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", + "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", + "license": "MIT" + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -15396,12 +16517,41 @@ "defaults": "^1.0.3" } }, + "node_modules/web-vitals": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz", + "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==", + "license": "Apache-2.0" + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "license": "BSD-2-Clause" }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/whatwg-fetch": { "version": "3.6.20", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", diff --git a/package.json b/package.json index d084d3f..293e4ed 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/api/URL.ts b/src/api/URL.ts index 707dd03..3e5fb6d 100644 --- a/src/api/URL.ts +++ b/src/api/URL.ts @@ -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'; diff --git a/src/api/auth/type.ts b/src/api/auth/type.ts index ea3541e..ed92735 100644 --- a/src/api/auth/type.ts +++ b/src/api/auth/type.ts @@ -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 { diff --git a/src/api/exchanges/index.ts b/src/api/exchanges/index.ts new file mode 100644 index 0000000..1b9f057 --- /dev/null +++ b/src/api/exchanges/index.ts @@ -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 { + const data = await axiosInstance.get(GET_EXCHANGES); + return data.data; + }, +}; + +export default exchanges_api; diff --git a/src/api/reference/index.ts b/src/api/reference/index.ts new file mode 100644 index 0000000..5044f87 --- /dev/null +++ b/src/api/reference/index.ts @@ -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 { + const res = await axiosInstance.get(GET_REFERENCE, { + params: { + cargoType: params.cargoType, + }, + }); + return res.data; + }, +}; + +export default reference_api; diff --git a/src/api/warhouses/index.ts b/src/api/warhouses/index.ts new file mode 100644 index 0000000..63d4629 --- /dev/null +++ b/src/api/warhouses/index.ts @@ -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 { + const data = await axiosInstance.get(GET_WARHOUSES, { + params: { + ...params, + }, + }); + + return data.data; + }, +}; + +export default warhouses_api; diff --git a/src/components/FileDrop.tsx b/src/components/FileDrop.tsx index 9fca85e..6deb64b 100644 --- a/src/components/FileDrop.tsx +++ b/src/components/FileDrop.tsx @@ -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 = ({ title, onFileSelected, + type = 'image/png', }) => { const [selectedImage, setSelectedImage] = useState(null); const { t } = useTranslation(); + const imagePickerOptions = useMemo( () => ({ mediaType: 'photo' as MediaType, @@ -49,29 +56,27 @@ const SingleFileDrop: React.FC = ({ 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 = ({ <> - {selectedImage ? ( - - ) : ( - <> - - {title} - - )} + + {title} @@ -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', diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index b1daed7..4b1bd53 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -5,7 +5,6 @@ import { Dimensions, Image, Linking, - Platform, StyleSheet, Text, TouchableOpacity, @@ -98,14 +97,12 @@ const Navbar = () => { /> */} - {Platform.OS === 'android' && ( - navigation.navigate('Notifications')} - > - - {/* */} - - )} + navigation.navigate('Notifications')} + > + + {/* */} + diff --git a/src/components/SplashScreen.tsx b/src/components/SplashScreen.tsx index 5f32c4e..3d7abc9 100644 --- a/src/components/SplashScreen.tsx +++ b/src/components/SplashScreen.tsx @@ -193,7 +193,7 @@ const styles = StyleSheet.create({ alignItems: 'center', }, brand: { - fontWeight: '700', + fontWeight: '500', color: '#fff', }, }); diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json index 09fc1a3..5f5f531 100644 --- a/src/i18n/locales/ru.json +++ b/src/i18n/locales/ru.json @@ -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": "Нажмите кнопку, чтобы добавить новый паспорт", diff --git a/src/i18n/locales/uz.json b/src/i18n/locales/uz.json index ebeb7be..1f48be5 100644 --- a/src/i18n/locales/uz.json +++ b/src/i18n/locales/uz.json @@ -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", diff --git a/src/screens/auth/login/ui/Confirm.tsx b/src/screens/auth/login/ui/Confirm.tsx index 5f6a45c..fd31210 100644 --- a/src/screens/auth/login/ui/Confirm.tsx +++ b/src/screens/auth/login/ui/Confirm.tsx @@ -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(); const { t } = useTranslation(); + const [firebaseToken, setFirebseToken] = useState<{ + fcmToken: string; + deviceId: string; + deviceName: string; + } | null>(); const [code, setCode] = useState(new Array(OTP_LENGTH).fill('')); const [timer, setTimer] = useState(60); const [errorConfirm, setErrorConfirm] = useState(null); const [canResend, setCanResend] = useState(false); const inputRefs = useRef>([]); 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 = () => { {t('Tasdiqlash kodini kiriting')} {phoneNumber} {t('raqamiga yuborilgan')} {OTP_LENGTH}{' '} - {t('xonali kodni kiriting.')} + {t('xonali kodni kiriting.')}{' '} {code.map((digit, index) => ( { - inputRefs.current[index] = ref; - }} + ref={ref => (inputRefs.current[index] = ref)} style={styles.otpInput} keyboardType="number-pad" maxLength={1} @@ -166,7 +194,7 @@ const Confirm = () => { /> ))} - {errorConfirm !== null && ( + {errorConfirm && ( {errorConfirm} )} @@ -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; diff --git a/src/screens/auth/login/ui/index.tsx b/src/screens/auth/login/ui/index.tsx index 2987d51..6e2b373 100644 --- a/src/screens/auth/login/ui/index.tsx +++ b/src/screens/auth/login/ui/index.tsx @@ -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({ diff --git a/src/screens/auth/registeration/lib/form.ts b/src/screens/auth/registeration/lib/form.ts index f130b15..0c352fb 100644 --- a/src/screens/auth/registeration/lib/form.ts +++ b/src/screens/auth/registeration/lib/form.ts @@ -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'), diff --git a/src/screens/auth/registeration/ui/Confirm.tsx b/src/screens/auth/registeration/ui/Confirm.tsx index 5171df3..cb63e6d 100644 --- a/src/screens/auth/registeration/ui/Confirm.tsx +++ b/src/screens/auth/registeration/ui/Confirm.tsx @@ -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(null); const inputRefs = useRef>([]); 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 ( diff --git a/src/screens/auth/registeration/ui/FirstStep.tsx b/src/screens/auth/registeration/ui/FirstStep.tsx index 5131bf1..0082335 100644 --- a/src/screens/auth/registeration/ui/FirstStep.tsx +++ b/src/screens/auth/registeration/ui/FirstStep.tsx @@ -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 ( void }) => { )} /> + ( + + + {t('Manzilingizni kiriting')} + + + {errors.lastName && ( + + {t(errors.lastName.message || '')} + + )} + + )} + /> { const passportNumberRef = useRef(null); const [checkboxAnimation] = useState(new Animated.Value(1)); const [inputValue, setInputValue] = useState(''); + const { firstName, lastName } = useUserStore(state => state); const navigation = useNavigation>(); @@ -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 diff --git a/src/screens/auth/select-auth/SelectAuth.tsx b/src/screens/auth/select-auth/SelectAuth.tsx index a82420c..684df7f 100644 --- a/src/screens/auth/select-auth/SelectAuth.tsx +++ b/src/screens/auth/select-auth/SelectAuth.tsx @@ -24,8 +24,6 @@ const SelectAuth = () => { const navigation = useNavigation(); const { width } = useWindowDimensions(); - const isSmallScreen = width < 360; - return ( @@ -41,20 +39,15 @@ const SelectAuth = () => { style={[ styles.logoImage, { - width: isSmallScreen ? 120 : 250, - height: isSmallScreen ? 120 : 200, - borderRadius: 20000, + width: 180, + height: 180, }, ]} /> - - CPOST - + CPOST - + {t('Ro’yxatdan o’tganmisz')} @@ -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, diff --git a/src/screens/auth/select-language/SelectLangPage.tsx b/src/screens/auth/select-language/SelectLangPage.tsx index a2c6f04..d0814c5 100644 --- a/src/screens/auth/select-language/SelectLangPage.tsx +++ b/src/screens/auth/select-language/SelectLangPage.tsx @@ -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; const SelectLangPage = () => { const navigation = useNavigation(); - 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, }, ]} /> - - CPOST - + CPOST - + Tilni tanlang{' '} - + (Выберите язык) @@ -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, diff --git a/src/screens/home/branches/ui/Branches.tsx b/src/screens/home/branches/ui/Branches.tsx index cc27266..d5d62da 100644 --- a/src/screens/home/branches/ui/Branches.tsx +++ b/src/screens/home/branches/ui/Branches.tsx @@ -51,11 +51,27 @@ const Branches = (props: BranchesProps) => { navigation.navigate('ListBranches', { branchId: e.id }) } > - - {e.name} - {e.address} + + + {e.name} + {e.address} + + - ))} @@ -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', diff --git a/src/screens/home/branches/ui/ListBranches.tsx b/src/screens/home/branches/ui/ListBranches.tsx index 0c0fabc..795fb09 100644 --- a/src/screens/home/branches/ui/ListBranches.tsx +++ b/src/screens/home/branches/ui/ListBranches.tsx @@ -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 = ` @@ -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); diff --git a/src/screens/home/cargoPrices/ui/CargoPrices.tsx b/src/screens/home/cargoPrices/ui/CargoPrices.tsx index da401b0..de1665a 100644 --- a/src/screens/home/cargoPrices/ui/CargoPrices.tsx +++ b/src/screens/home/cargoPrices/ui/CargoPrices.tsx @@ -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>(); + + const { data, refetch } = useQuery({ + queryKey: ['reference_list'], + queryFn: () => reference_api.getReference({ cargoType: activeTab }), + }); + + React.useEffect(() => { + refetch(); + }, [activeTab]); + return ( - {activeTab === 'avia' && ( + {activeTab === 'AVIA' && ( - - - - {t('Oddiy maxsulotlar')} - - - 9.2$ /1kg - - - - {t('(Katta miqdordagi yuklar kelishuv asosida)')} - - - - - {t('Brend buyumlar')} - - 12.2$ /1kg - - - - {t('(Karobka,dokumentlar bilan birga)')} - - + {data && + data.map(ref => ( + + + {ref.title} + + {ref.price}$/{ref.unitValue} + {ref.unit} + + + {ref.shortDescription} + + ))} @@ -127,34 +128,21 @@ const CargoPrices = (props: CargoPricesProps) => { )} - {activeTab === 'auto' && ( + {activeTab === 'AUTO' && ( - - - 0-30 kg - - 7$ /1kg - - - - - - 30-100kg - - 6.5$ /1kg - - - - - - - 100kg {t('dan boshlab')} - - - 6$ /1kg - - - + {data && + data.map(ref => ( + + + {ref.title} + + {ref.price}$/{ref.unitValue} + {ref.unit} + + + {ref.shortDescription} + + ))} {t( diff --git a/src/screens/home/home/ui/Home.tsx b/src/screens/home/home/ui/Home.tsx index 908a83f..ec472dd 100644 --- a/src/screens/home/home/ui/Home.tsx +++ b/src/screens/home/home/ui/Home.tsx @@ -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 ; - } else if (activeTab === 'avia') { + } else if (activeTab === 'AVIA') { return ; } return null; diff --git a/src/screens/home/home/ui/Tabs.tsx b/src/screens/home/home/ui/Tabs.tsx index 7418e7b..89d99cd 100644 --- a/src/screens/home/home/ui/Tabs.tsx +++ b/src/screens/home/home/ui/Tabs.tsx @@ -8,7 +8,7 @@ import { HomeStyle } from './styled'; interface Props { activeTab: string; - setActiveTab: Dispatch>; + setActiveTab: Dispatch>; } 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, }, diff --git a/src/screens/home/home/ui/TabsAuto.tsx b/src/screens/home/home/ui/TabsAuto.tsx index 49a8477..58b5381 100644 --- a/src/screens/home/home/ui/TabsAuto.tsx +++ b/src/screens/home/home/ui/TabsAuto.tsx @@ -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 ( 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 ( - {item.title} + China (Auto) - {item.addressInfo.map((line, idx) => ( - - {line} - - ))} + {item} - handleCopy(item.addressInfo)}> + handleCopy([item])}> - - {t('Auto post kodi')}: - {item.postCode} - ); }} diff --git a/src/screens/home/home/ui/TabsAvia.tsx b/src/screens/home/home/ui/TabsAvia.tsx index 5c8a269..5f425fa 100644 --- a/src/screens/home/home/ui/TabsAvia.tsx +++ b/src/screens/home/home/ui/TabsAvia.tsx @@ -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 ( 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 ( - {item.title} + China (AVIA) - - {item.addressInfo.map((line, idx) => ( - - {line} - - ))} + + {item} - handleCopy(item.addressInfo)}> + handleCopy([item])}> - - {t('Avia post kodi')}: - {item.postCode} - ); }} @@ -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, diff --git a/src/screens/passport/myPassport/ui/MyPassport.tsx b/src/screens/passport/myPassport/ui/MyPassport.tsx index a450e27..d1563c2 100644 --- a/src/screens/passport/myPassport/ui/MyPassport.tsx +++ b/src/screens/passport/myPassport/ui/MyPassport.tsx @@ -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 ( {myPassport && myPassport.map(data => ( - {t('Passport ma’lumotlarim')} + + {t('Passport ma’lumotlarim')} + {getMe?.status && ( + + + {getStatusMeta(getMe.status).label} + + + )} + { {t('Telefon raqami')} @@ -125,25 +168,13 @@ const MyPassport = ({ getMe, myPassport }: Props) => { - - {t('Limit')} - - - {data.availableLimit}$ - + {t('Limit')} + {data.availableLimit}$ @@ -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, diff --git a/src/screens/profile/myProfile/ui/ProfilePages.tsx b/src/screens/profile/myProfile/ui/ProfilePages.tsx index 8ba5f97..42549d6 100644 --- a/src/screens/profile/myProfile/ui/ProfilePages.tsx +++ b/src/screens/profile/myProfile/ui/ProfilePages.tsx @@ -6,7 +6,6 @@ import { useTranslation } from 'react-i18next'; import { Alert, Linking, - Platform, StyleSheet, Text, TouchableOpacity, @@ -80,21 +79,19 @@ const ProfilePages = (props: componentNameProps) => { - {Platform.OS === 'android' && ( - navigation.navigate('Notifications')} - > - - - {t('Bildirishnomalar')} - - - - )} + navigation.navigate('Notifications')} + > + + + {t('Bildirishnomalar')} + + + { +const Notifications = () => { + const [notifications, setNotifications] = React.useState( + [], + ); 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 ( - - - - ); - } + const loadNotifications = async () => { + const stored = await AsyncStorage.getItem('notifications'); + if (stored) { + setNotifications(JSON.parse(stored)); + } + }; + + React.useEffect(() => { + const unsubscribe = loadNotifications(); + return () => { + unsubscribe; + }; + }, []); return ( @@ -53,16 +61,20 @@ const Notifications = (props: NotificationsProps) => { keyboardShouldPersistTaps="handled" refreshControl={refreshControl} > - {fakeNotifications.map(item => ( - openModal(item)} - style={styles.card} - key={item.id} - > - {item.message} - - - ))} + {notifications.length > 0 ? ( + notifications.map(item => ( + openModal(item)} + style={styles.card} + key={item.id} + > + {item.message} + + + )) + ) : ( + + )} { 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', diff --git a/src/screens/profile/warehouses/ui/TabsAutoWarehouses.tsx b/src/screens/profile/warehouses/ui/TabsAutoWarehouses.tsx index df131ea..41f2c8c 100644 --- a/src/screens/profile/warehouses/ui/TabsAutoWarehouses.tsx +++ b/src/screens/profile/warehouses/ui/TabsAutoWarehouses.tsx @@ -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 ( 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 ( - {item.title} + China (Auto) - {item.addressInfo.map((line, idx) => ( - - {line} - - ))} + {item} handleCopy(item.addressInfo)}> - - {t('Auto post kodi')}: - {item.postCode} - ); }} @@ -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, diff --git a/src/screens/profile/warehouses/ui/TabsAviaWarehouses.tsx b/src/screens/profile/warehouses/ui/TabsAviaWarehouses.tsx index eb863ad..3e1eb56 100644 --- a/src/screens/profile/warehouses/ui/TabsAviaWarehouses.tsx +++ b/src/screens/profile/warehouses/ui/TabsAviaWarehouses.tsx @@ -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 ( 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 ( - {item.title} + China (Auto) - - {item.addressInfo.map((line, idx) => ( - - {line} - - ))} + + {item} handleCopy(item.addressInfo)}> - - {t('Avia post kodi')}: - {item.postCode} - ); }} @@ -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, }, }); diff --git a/src/screens/profile/warehouses/ui/Warehouses.tsx b/src/screens/profile/warehouses/ui/Warehouses.tsx index 8c5aeef..7ac4131 100644 --- a/src/screens/profile/warehouses/ui/Warehouses.tsx +++ b/src/screens/profile/warehouses/ui/Warehouses.tsx @@ -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(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) => { {t('Skrenshot rasmini yuklang')} - - - {t('Manzilni tekshirish')} + + + + {isLoading ? ( + + ) : ( + {t('Manzilni tekshirish')} + )} diff --git a/src/screens/wallet/payment/ui/Payment.tsx b/src/screens/wallet/payment/ui/Payment.tsx index 9b99ce6..e63a35e 100644 --- a/src/screens/wallet/payment/ui/Payment.tsx +++ b/src/screens/wallet/payment/ui/Payment.tsx @@ -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 }, diff --git a/src/screens/wallet/payment/ui/Wallet.tsx b/src/screens/wallet/payment/ui/Wallet.tsx index ad5bf46..5ee3a74 100644 --- a/src/screens/wallet/payment/ui/Wallet.tsx +++ b/src/screens/wallet/payment/ui/Wallet.tsx @@ -101,23 +101,142 @@ const Wallet = () => { if (isError || isErrorAvia) { return ( - + + + ); } + if (selectedType === 'AUTO' && packets && !(packets?.data.length > 0)) { + return ( + + + + + {t("To'lov")} + + setSelectedType('AVIA')} + > + + AVIA + + + + setSelectedType('AUTO')} + > + + AUTO + + + + + + + + + ); + } if ( - (packets && !(packets?.data.length > 0)) || - (packetsAvia && !(packetsAvia?.data.length > 0)) + selectedType === 'AVIA' && + packetsAvia && + !(packetsAvia?.data.length > 0) ) { return ( - - - {t("To'lov")} + + + + {t("To'lov")} + + setSelectedType('AVIA')} + > + + AVIA + + + + setSelectedType('AUTO')} + > + + AUTO + + + + + - - + ); } diff --git a/src/screens/wallet/payment/ui/style.ts b/src/screens/wallet/payment/ui/style.ts index 78ebf6f..a971ac9 100644 --- a/src/screens/wallet/payment/ui/style.ts +++ b/src/screens/wallet/payment/ui/style.ts @@ -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', diff --git a/src/screens/wallet/paymentMethod/ui/ModalCard.tsx b/src/screens/wallet/paymentMethod/ui/ModalCard.tsx index c39c72b..64bd418 100644 --- a/src/screens/wallet/paymentMethod/ui/ModalCard.tsx +++ b/src/screens/wallet/paymentMethod/ui/ModalCard.tsx @@ -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 }] }]} > - {/* CLICK */} setSelectedId('click')} + onPress={() => { + setPay('CLICK'), setSelectedId('click'); + }} > diff --git a/src/screens/wallet/paymentMethod/ui/ModalPay.tsx b/src/screens/wallet/paymentMethod/ui/ModalPay.tsx index 3ed0a4c..751d107 100644 --- a/src/screens/wallet/paymentMethod/ui/ModalPay.tsx +++ b/src/screens/wallet/paymentMethod/ui/ModalPay.tsx @@ -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>; payModal: boolean; + paymentStatus: string; + packId: number; + paymentType: string | null; setPayModal: React.Dispatch>; success: boolean; setSuccess: React.Dispatch>; @@ -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 */} - - {/* CASH OPTION */} - setSelectedId('pay')} - > - - - - {t('Naqt pul')} - - - { + setSelectedId('pay'); + }} > - {selectedId === 'pay' && ( - - )} - - - - {/* BUTTON */} + + + + {t('Naqt pul')} + + + + {selectedId === 'pay' && ( + + )} + + + )} { const [success, setSuccess] = React.useState(false); const toggleModal = () => setModalVisible(true); + console.log(packets); React.useEffect(() => { if (payModal) { @@ -39,6 +40,9 @@ const PaymentMethod = () => { { const isSmallScreen = screenWidth < 380; const svgWidth = screenWidth * 0.8; + const { data } = useQuery({ + queryKey: ['exchanges'], + queryFn: () => exchanges_api.getExchanges(), + }); + return ( { : { flexBasis: '48%', alignItems: 'flex-start' }, ]} > - USD - 12.267 UZS + {data && ( + <> + {data[0].code} + {data[0].rate} UZS + + )} { 12.267 UZS/ kg - { > {t('Yetkazish vaqti')} 08.25.2025 - + */} Reys - + {packet.packetName} @@ -133,14 +143,14 @@ const PaymentProduct = ({ packet }: PaymentProductProps) => { }} /> - {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 ( @@ -160,7 +170,7 @@ const PaymentProduct = ({ packet }: PaymentProductProps) => { - {/* {t('Umumiy narxi')}: {formattedTotal} {t('so‘m')} */} + {t('Umumiy narxi')}: {formattedTotal} {t('so‘m')} diff --git a/src/screens/wallet/successPayment/ui/PaymentQrCode.tsx b/src/screens/wallet/successPayment/ui/PaymentQrCode.tsx index cac1304..5d08287 100644 --- a/src/screens/wallet/successPayment/ui/PaymentQrCode.tsx +++ b/src/screens/wallet/successPayment/ui/PaymentQrCode.tsx @@ -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>(); + 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 ( { : { flexBasis: '48%', alignItems: 'flex-start' }, ]} > - USD - 12.267 UZS + {data && ( + <> + {data[0].code} + + {data[0].rate} UZS + + + )} { 12.267 UZS/ kg - { {t('Yetkazish vaqti')} - 08.25.2025 - + {} + */} - Avia-CP - 223 + Reys + + {packets.packetName} + { }} /> - {fakeProducts.map((item, index) => ( + {packets.items.map((item: any, index: number) => ( @@ -207,9 +245,7 @@ const PaymentQrCode = (props: PaymentQrCodeProps) => { }} > @@ -262,6 +298,45 @@ const PaymentQrCode = (props: PaymentQrCodeProps) => { + + {cardModal && ( + + )} + {success && ( + + )} + {packets.paymentStatus !== 'PAYED' && ( + + {t("To'lash")} + + )} ); }; diff --git a/src/screens/welcome/FirstStep.tsx b/src/screens/welcome/FirstStep.tsx index 089fe6b..54dce9e 100644 --- a/src/screens/welcome/FirstStep.tsx +++ b/src/screens/welcome/FirstStep.tsx @@ -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', diff --git a/src/screens/welcome/SecondStep.tsx b/src/screens/welcome/SecondStep.tsx index a7a2e08..bd19209 100644 --- a/src/screens/welcome/SecondStep.tsx +++ b/src/screens/welcome/SecondStep.tsx @@ -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', diff --git a/src/screens/welcome/SelectLangPage.tsx b/src/screens/welcome/SelectLangPage.tsx index 593cc2e..074e15f 100644 --- a/src/screens/welcome/SelectLangPage.tsx +++ b/src/screens/welcome/SelectLangPage.tsx @@ -39,22 +39,17 @@ const SelectLangPage = ({ style={[ styles.logoImage, { - width: isSmallScreen ? 120 : 250, - height: isSmallScreen ? 120 : 200, - borderRadius: 20000, + width: 180, + height: 180, }, ]} /> - - CPOST - + CPOST - + Tilni tanlang{' '} - + (Выберите язык) @@ -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', diff --git a/src/screens/welcome/ThirdStep.tsx b/src/screens/welcome/ThirdStep.tsx index 0ba3320..189d6df 100644 --- a/src/screens/welcome/ThirdStep.tsx +++ b/src/screens/welcome/ThirdStep.tsx @@ -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', diff --git a/src/types/firebaseConfig.ts b/src/types/firebaseConfig.ts new file mode 100644 index 0000000..eaca98f --- /dev/null +++ b/src/types/firebaseConfig.ts @@ -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); diff --git a/src/types/images.d.ts b/src/types/images.d.ts index a66972c..e6a95ed 100644 --- a/src/types/images.d.ts +++ b/src/types/images.d.ts @@ -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;