diff --git a/api/URLs.ts b/api/URLs.ts index e90b9bc..93ce264 100644 --- a/api/URLs.ts +++ b/api/URLs.ts @@ -28,7 +28,7 @@ export const API_URLS = { User_Update: 'auth/user-update/', Employee_List: 'api/employee/', My_Ads: 'api/my-ads/', - My_Ads_Detail: (id: number) => `api/my-ads/${id}`, + Ads_Detail: (id: number) => `api/my-ads/${id}/`, My_Bonuses: 'api/cashback/', My_Refferals: 'api/referral/', Goverment_Service: '/api/goverment-service/', diff --git a/app/_layout.tsx b/app/_layout.tsx index a50eb1f..9fe881e 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -10,6 +10,7 @@ import { I18nextProvider } from 'react-i18next'; import { View } from 'react-native'; import 'react-native-reanimated'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import ToastManager from "toastify-react-native"; function AppContent() { useNotifications(); @@ -43,6 +44,7 @@ export default function RootLayout() { + diff --git a/app/profile/categories.tsx b/app/profile/categories.tsx index c011c6a..ce6635f 100644 --- a/app/profile/categories.tsx +++ b/app/profile/categories.tsx @@ -14,10 +14,10 @@ import { ScrollView, StyleSheet, Text, - ToastAndroid, TouchableOpacity, View, } from 'react-native'; +import { Toast } from 'toastify-react-native'; export default function PersonalInfoScreen() { const router = useRouter(); @@ -76,7 +76,7 @@ export default function PersonalInfoScreen() { onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['get_me'] }); router.push('/profile/personal-info'); - ToastAndroid.show(t("Ma'lumotlar yangilandi"), ToastAndroid.TOP); + Toast.success(t("Ma'lumotlar yangilandi")); }, onError: () => { Alert.alert(t('Xatolik yzu berdi'), t("Ma'lumotlarni yangilashda xatolik yuz berdi")); diff --git a/app/profile/personal-info.tsx b/app/profile/personal-info.tsx index d3e78b5..3978731 100644 --- a/app/profile/personal-info.tsx +++ b/app/profile/personal-info.tsx @@ -9,17 +9,16 @@ import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { ActivityIndicator, - Alert, Image, Pressable, ScrollView, StyleSheet, Text, TextInput, - ToastAndroid, TouchableOpacity, - View, + View } from 'react-native'; +import { Toast } from 'toastify-react-native'; export default function PersonalInfoScreen() { const router = useRouter(); @@ -71,10 +70,10 @@ export default function PersonalInfoScreen() { queryClient.invalidateQueries({ queryKey: ['get_me'] }); setIsEditing(false); setShowCategories(false); - ToastAndroid.show(t("Ma'lumotlar yangilandi"), ToastAndroid.TOP); + Toast.success(t("Ma'lumotlar yangilandi")); }, onError: () => { - Alert.alert(t('Xatolik yuz berdi'), t('Yangilashda xatolik yuz berdi')); + Toast.error(t('Yangilashda xatolik yuz berdi')); }, }); diff --git a/assets/announcements-video/video_en.mp4 b/assets/announcements-video/video_en.mp4 new file mode 100644 index 0000000..7cf44d9 Binary files /dev/null and b/assets/announcements-video/video_en.mp4 differ diff --git a/assets/announcements-video/video_en.webm b/assets/announcements-video/video_en.webm deleted file mode 100644 index dd38173..0000000 Binary files a/assets/announcements-video/video_en.webm and /dev/null differ diff --git a/assets/announcements-video/video_ru.mp4 b/assets/announcements-video/video_ru.mp4 new file mode 100644 index 0000000..e152f94 Binary files /dev/null and b/assets/announcements-video/video_ru.mp4 differ diff --git a/assets/announcements-video/video_ru.webm b/assets/announcements-video/video_ru.webm deleted file mode 100644 index 558baf5..0000000 Binary files a/assets/announcements-video/video_ru.webm and /dev/null differ diff --git a/assets/announcements-video/video_uz.mp4 b/assets/announcements-video/video_uz.mp4 new file mode 100644 index 0000000..b6dd522 Binary files /dev/null and b/assets/announcements-video/video_uz.mp4 differ diff --git a/assets/announcements-video/video_uz.webm b/assets/announcements-video/video_uz.webm deleted file mode 100644 index 5244549..0000000 Binary files a/assets/announcements-video/video_uz.webm and /dev/null differ diff --git a/assets/goverment/video_ru.mp4 b/assets/goverment/video_ru.mp4 new file mode 100644 index 0000000..5976e2f Binary files /dev/null and b/assets/goverment/video_ru.mp4 differ diff --git a/assets/goverment/video_ru.webm b/assets/goverment/video_ru.webm deleted file mode 100644 index b5a62fb..0000000 Binary files a/assets/goverment/video_ru.webm and /dev/null differ diff --git a/assets/manual/manual_video_en.mp4 b/assets/manual/manual_video_en.mp4 new file mode 100644 index 0000000..ec03703 Binary files /dev/null and b/assets/manual/manual_video_en.mp4 differ diff --git a/assets/manual/manual_video_en.webm b/assets/manual/manual_video_en.webm deleted file mode 100644 index 4015cf7..0000000 Binary files a/assets/manual/manual_video_en.webm and /dev/null differ diff --git a/assets/manual/manual_video_ru.mp4 b/assets/manual/manual_video_ru.mp4 new file mode 100644 index 0000000..4e1fce5 Binary files /dev/null and b/assets/manual/manual_video_ru.mp4 differ diff --git a/assets/manual/manual_video_ru.webm b/assets/manual/manual_video_ru.webm deleted file mode 100644 index e710499..0000000 Binary files a/assets/manual/manual_video_ru.webm and /dev/null differ diff --git a/assets/manual/manual_video_uz.mp4 b/assets/manual/manual_video_uz.mp4 new file mode 100644 index 0000000..83e3b56 Binary files /dev/null and b/assets/manual/manual_video_uz.mp4 differ diff --git a/assets/manual/manual_video_uz.webm b/assets/manual/manual_video_uz.webm deleted file mode 100644 index d35c99d..0000000 Binary files a/assets/manual/manual_video_uz.webm and /dev/null differ diff --git a/components/ui/ProductList.tsx b/components/ui/ProductList.tsx index b52d98a..bb0338d 100644 --- a/components/ui/ProductList.tsx +++ b/components/ui/ProductList.tsx @@ -1,8 +1,9 @@ import { useTheme } from '@/components/ThemeContext'; import { products_api } from '@/screens/home/lib/api'; import { ProductResponse } from '@/screens/home/lib/types'; +import { user_api } from '@/screens/profile/lib/api'; import { BottomSheetBackdrop, BottomSheetModal, BottomSheetScrollView } from '@gorhom/bottom-sheet'; -import { useInfiniteQuery } from '@tanstack/react-query'; +import { useInfiniteQuery, useQuery } from '@tanstack/react-query'; import { ResizeMode, Video } from 'expo-av'; import { Info, Package, PlayCircle } from 'lucide-react-native'; import React, { useCallback, useRef, useState } from 'react'; @@ -50,6 +51,17 @@ export default function ProductList({ query }: Props) { const allProducts = data?.pages.flatMap((p) => p.results) ?? []; + const { + data: detail, + isLoading: loadingDetail, + isError: detailError, + } = useQuery({ + queryKey: ['my_ads_id', selectedProduct], + queryFn: () => user_api.detail_service(Number(selectedProduct?.id)), + select: (res) => res.data.data, + enabled: !!selectedProduct, + }); + const handlePresentModalPress = useCallback((product: ProductResponse) => { setSelectedProduct(product); setCurrentImageIndex(0); @@ -181,12 +193,29 @@ export default function ProductList({ query }: Props) { style={styles.sheetContent} contentContainerStyle={styles.sheetContentContainer} > - {selectedProduct && ( + {/* Loading holati */} + {loadingDetail && ( + + + + )} + + {/* Error holati */} + {detailError && ( + + + {t('Xatolik yuz berdi')} + + + )} + + {/* Detail mavjud bo‘lsa */} + {detail && ( <> item.id.toString()} horizontal @@ -197,9 +226,9 @@ export default function ProductList({ query }: Props) { setCurrentImageIndex(index); }} /> - {selectedProduct.files.length > 1 && ( + {detail.files.length > 1 && ( - {selectedProduct.files.map((_, i) => ( + {detail.files.map((_, i) => ( - {selectedProduct.title} + {detail.title} - {selectedProduct.company} + {detail.company} @@ -230,10 +259,8 @@ export default function ProductList({ query }: Props) { {t("Batafsil ma'lumot")} - - {selectedProduct.description || "Ma'lumot mavjud emas."} + + {detail.description || "Ma'lumot mavjud emas."} diff --git a/i18n/locales/en.json b/i18n/locales/en.json index 6c3a461..848cb0a 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -139,7 +139,7 @@ "Keyingi": "Next", "Xizmat sarlavhasi": "Service title", "Yangilashda xato yuz berdi": "Error occurred while updating", - "Xizmatni tahrirlash (1/2)": "Edit service (1/2)", + "Xizmatni tahrirlash": "Edit service", "Xizmatni tahrirlash (2/2)": "Edit service (2/2)", "Tilni tanlang": "Select language", "Rejimni tanlang": "Select mode", @@ -226,5 +226,6 @@ "INN kiriting (9 raqam)": "Enter INN (9 digits)", "Referal kodi": "Referral code", "Direktor JSHSHR": "Director JSHSHR", - "Direktor JSHSHR (14 raqam)": "Director JSHSHR (number 14)" + "Direktor JSHSHR (14 raqam)": "Director JSHSHR (number 14)", + "Hozircha bildirishnomalar yo'q": "No notifications yet" } \ No newline at end of file diff --git a/i18n/locales/ru.json b/i18n/locales/ru.json index 4decdd8..23d6ca1 100644 --- a/i18n/locales/ru.json +++ b/i18n/locales/ru.json @@ -139,7 +139,7 @@ "Keyingi": "Далее", "Xizmat sarlavhasi": "Заголовок услуги", "Yangilashda xato yuz berdi": "Произошла ошибка при обновлении", - "Xizmatni tahrirlash (1/2)": "Редактирование услуги (1/2)", + "Xizmatni tahrirlash": "Редактирование услуги", "Xizmatni tahrirlash (2/2)": "Редактирование услуги (2/2)", "Tilni tanlang": "Выберите язык", "Rejimni tanlang": "Выберите режим", @@ -225,5 +225,6 @@ "INN kiriting (9 raqam)": "Введите ИНН (9 цифр)", "Referal kodi": "Реферальный код", "Direktor JSHSHR": "Директор ПИНФЛ", - "Direktor JSHSHR kiriting (14 raqam)": "Введите ПИНФЛ директора (14 цифр)" + "Direktor JSHSHR kiriting (14 raqam)": "Введите ПИНФЛ директора (14 цифр)", + "Hozircha bildirishnomalar yo'q": "Пока нет уведомлений" } \ No newline at end of file diff --git a/i18n/locales/uz.json b/i18n/locales/uz.json index e11f95a..8e4214f 100644 --- a/i18n/locales/uz.json +++ b/i18n/locales/uz.json @@ -139,7 +139,7 @@ "Yangi xizmat (2/2)": "Yangi xizmat (2/2)", "Keyingi": "Keyingi", "Xizmat sarlavhasi": "Xizmat sarlavhasi", - "Xizmatni tahrirlash (1/2)": "Xizmatni tahrirlash (1/2)", + "Xizmatni tahrirlash": "Xizmatni tahrirlash", "Xizmatni tahrirlash (2/2)": "Xizmatni tahrirlash (2/2)", "Tilni tanlang": "Tilni tanlang", "Rejimni tanlang": "Rejimni tanlang", @@ -177,6 +177,7 @@ "Faqat tasdiqlangan profillarga ishonch bilan murojaat qiling.": "Faqat tasdiqlangan profillarga ishonch bilan murojaat qiling.", "Qo'llanma video": "Qo'llanma video", "Bildirishnomalar": "Bildirishnomalar", + "Hozircha bildirishnomalar yo'q": "Hozircha bildirishnomalar yo'q", "Bildirishnomalarni yuklashda muammo bo'ldi": "Bildirishnomalarni yuklashda muammo bo'ldi", "Hozir": "Hozir", "daqiqa oldin": "daqiqa oldin", diff --git a/package-lock.json b/package-lock.json index a28d5b6..50946c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -71,6 +71,7 @@ "react-native-worklets": "^0.5.1", "react-stately": "^3.39.0", "tailwind-variants": "^0.1.20", + "toastify-react-native": "^7.2.3", "zustand": "^5.0.10" }, "devDependencies": { @@ -15396,6 +15397,73 @@ "react-native-pager-view": ">= 6.0.0" } }, + "node_modules/react-native-vector-icons": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-10.3.0.tgz", + "integrity": "sha512-IFQ0RE57819hOUdFvgK4FowM5aMXg7C7XKsuGLevqXkkIJatc3QopN0wYrb2IrzUgmdpfP+QVIbI3S6h7M0btw==", + "deprecated": "react-native-vector-icons package has moved to a new model of per-icon-family packages. See the https://github.com/oblador/react-native-vector-icons/blob/master/MIGRATION.md on how to migrate", + "license": "MIT", + "dependencies": { + "prop-types": "^15.7.2", + "yargs": "^16.1.1" + }, + "bin": { + "fa-upgrade.sh": "bin/fa-upgrade.sh", + "fa5-upgrade": "bin/fa5-upgrade.sh", + "fa6-upgrade": "bin/fa6-upgrade.sh", + "generate-icon": "bin/generate-icon.js" + } + }, + "node_modules/react-native-vector-icons/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/react-native-vector-icons/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/react-native-vector-icons/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/react-native-vector-icons/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/react-native-web": { "version": "0.21.2", "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz", @@ -17202,6 +17270,19 @@ "node": ">=8.0" } }, + "node_modules/toastify-react-native": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/toastify-react-native/-/toastify-react-native-7.2.3.tgz", + "integrity": "sha512-ngmpTKlTo0IRddwSsNWK+YKbB2veqotHy7Zpil4eksoLAlq0RPSgdVOk5QDEDUONJQ4r7ljGYeRW68KBztirsg==", + "license": "MIT", + "dependencies": { + "react-native-vector-icons": "*" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", diff --git a/package.json b/package.json index 0baf5a3..d411c01 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "react-native-worklets": "^0.5.1", "react-stately": "^3.39.0", "tailwind-variants": "^0.1.20", + "toastify-react-native": "^7.2.3", "zustand": "^5.0.10" }, "devDependencies": { diff --git a/screens/announcements/ui/AnnouncementsList.tsx b/screens/announcements/ui/AnnouncementsList.tsx index 61524f0..db23f88 100644 --- a/screens/announcements/ui/AnnouncementsList.tsx +++ b/screens/announcements/ui/AnnouncementsList.tsx @@ -101,14 +101,14 @@ export default function DashboardScreen() { // Announcement videos const videos = { - uz: require('@/assets/announcements-video/video_uz.webm'), - ru: require('@/assets/announcements-video/video_ru.webm'), - en: require('@/assets/announcements-video/video_en.webm'), + uz: require('@/assets/announcements-video/video_uz.mp4'), + ru: require('@/assets/announcements-video/video_ru.mp4'), + en: require('@/assets/announcements-video/video_en.mp4'), }; // Government videos: faqat RU mavjud const govermentVideos: Partial> = { - ru: require('@/assets/goverment/video_ru.webm'), + ru: require('@/assets/goverment/video_ru.mp4'), }; // Update selected language diff --git a/screens/auth/confirm/ConfirmScreen.tsx b/screens/auth/confirm/ConfirmScreen.tsx index 976ed65..d825a81 100644 --- a/screens/auth/confirm/ConfirmScreen.tsx +++ b/screens/auth/confirm/ConfirmScreen.tsx @@ -16,12 +16,12 @@ import { Platform, StyleSheet, Text, - ToastAndroid, TouchableOpacity, - View, + View } from 'react-native'; import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'; import { SafeAreaView } from 'react-native-safe-area-context'; +import { Toast } from 'toastify-react-native'; import { auth_api } from '../login/lib/api'; import useTokenStore from '../login/lib/hook'; import ConfirmForm from './ConfirmForm'; @@ -98,11 +98,11 @@ const ConfirmScreen = () => { Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); setResendTimer(60); if (Platform.OS === 'android') { - ToastAndroid.show(t('Kod qayta yuborildi'), ToastAndroid.SHORT); + Toast.info(t('Kod qayta yuborildi')); } }, onError: () => { - Alert.alert(t('Xatolik yuz berdi'), t('Kodni qayta yuborishda xatolik yuz berdi')); + Toast.error(t('Kodni qayta yuborishda xatolik yuz berdi')); }, }); diff --git a/screens/auth/register-confirm/ConfirmScreen.tsx b/screens/auth/register-confirm/ConfirmScreen.tsx index 50c7779..97918fd 100644 --- a/screens/auth/register-confirm/ConfirmScreen.tsx +++ b/screens/auth/register-confirm/ConfirmScreen.tsx @@ -16,12 +16,12 @@ import { Platform, StyleSheet, Text, - ToastAndroid, TouchableOpacity, - View, + View } from 'react-native'; import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'; import { SafeAreaView } from 'react-native-safe-area-context'; +import { Toast } from 'toastify-react-native'; import { auth_api } from '../login/lib/api'; import useTokenStore from '../login/lib/hook'; import ConfirmForm from './ConfirmForm'; @@ -93,7 +93,7 @@ const RegisterConfirmScreen = () => { Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); setResendTimer(60); if (Platform.OS === 'android') { - ToastAndroid.show(t('Kod qayta yuborildi'), ToastAndroid.SHORT); + Toast.info(t('Kod qayta yuborildi')); } }, onError: () => { diff --git a/screens/auth/register/RegisterForm.tsx b/screens/auth/register/RegisterForm.tsx index 32bd0af..db7167a 100644 --- a/screens/auth/register/RegisterForm.tsx +++ b/screens/auth/register/RegisterForm.tsx @@ -360,7 +360,10 @@ export default function RegisterFormScreen() { }; return ( - + - + ); } diff --git a/screens/create-ads/ui/CreateAdsScreens.tsx b/screens/create-ads/ui/CreateAdsScreens.tsx index 2c3b6ec..58b311c 100644 --- a/screens/create-ads/ui/CreateAdsScreens.tsx +++ b/screens/create-ads/ui/CreateAdsScreens.tsx @@ -190,8 +190,9 @@ export default function CreateAdsScreens() { router.push('/(dashboard)/announcements'); } }, - onError: (err) => { - Alert.alert('Xatolik yuz berdi', err.message); + onError: (err: AxiosError) => { + const errMessage = (err.response?.data as { referral_amount: string }).referral_amount + Alert.alert(t('Xatolik yuz berdi'), errMessage || err.message); }, }); diff --git a/screens/create-ads/ui/StepTwo.tsx b/screens/create-ads/ui/StepTwo.tsx index 6c348ff..415b289 100644 --- a/screens/create-ads/ui/StepTwo.tsx +++ b/screens/create-ads/ui/StepTwo.tsx @@ -3,7 +3,8 @@ import CategorySelection from '@/components/ui/IndustrySelection'; import { XIcon } from 'lucide-react-native'; import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { FlatList, StyleSheet, Text, ToastAndroid, TouchableOpacity, View } from 'react-native'; +import { FlatList, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import { Toast } from 'toastify-react-native'; type StepProps = { formData: any; @@ -36,7 +37,7 @@ const StepTwo = forwardRef(({ formData, updateForm }: StepProps, ref) => { const validate = () => { if (selectedCategories.length === 0) { setError('Iltimos, kompaniyalarni tanlang'); - ToastAndroid.show(t('Iltimos, kompaniyalarni tanlang'), ToastAndroid.TOP); + Toast.info(t('Iltimos, kompaniyalarni tanlang')); return false; } return true; diff --git a/screens/e-services/ui/EServicesCategoryScreen.tsx b/screens/e-services/ui/EServicesCategoryScreen.tsx index 8b888f5..9edb7b0 100644 --- a/screens/e-services/ui/EServicesCategoryScreen.tsx +++ b/screens/e-services/ui/EServicesCategoryScreen.tsx @@ -3,19 +3,19 @@ import { useTheme } from "@/components/ThemeContext"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { Image } from "expo-image"; import { router } from "expo-router"; +import * as WebBrowser from "expo-web-browser"; import { useState } from "react"; +import { useTranslation } from "react-i18next"; import { ActivityIndicator, FlatList, Text, - ToastAndroid, TouchableOpacity, - View, + View } from "react-native"; import { RefreshControl } from "react-native-gesture-handler"; -import * as WebBrowser from "expo-web-browser"; +import { Toast } from "toastify-react-native"; import { eservices_api } from "../lib/api"; -import { useTranslation } from "react-i18next"; const dark = { bg: "#0f172a", @@ -41,7 +41,7 @@ export default function EServicesCategoryScreen() { try { await WebBrowser.openBrowserAsync(fileUrl); } catch (error) { - ToastAndroid.show(t("Xatolik yuz berdi"), ToastAndroid.TOP); + Toast.error(t("Xatolik yuz berdi")); } }; diff --git a/screens/e-services/ui/EServicesScreen.tsx b/screens/e-services/ui/EServicesScreen.tsx index 66cd54b..9b40cab 100644 --- a/screens/e-services/ui/EServicesScreen.tsx +++ b/screens/e-services/ui/EServicesScreen.tsx @@ -2,6 +2,7 @@ import { useTheme } from "@/components/ThemeContext"; import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query"; import { Image } from "expo-image"; import { router, useLocalSearchParams } from "expo-router"; +import * as WebBrowser from "expo-web-browser"; import { ChevronLeft } from "lucide-react-native"; import { useCallback, useState } from "react"; import { @@ -10,14 +11,13 @@ import { FlatList, StyleSheet, Text, - ToastAndroid, TouchableOpacity, - View, + View } from "react-native"; -import * as WebBrowser from "expo-web-browser"; import { useTranslation } from "react-i18next"; import { RefreshControl } from "react-native-gesture-handler"; +import { Toast } from "toastify-react-native"; import { eservices_api } from "../lib/api"; const { width: SCREEN_WIDTH } = Dimensions.get("window"); @@ -74,11 +74,10 @@ export default function EServicesScreen() { }; const handleOpenBrowser = async (fileUrl: string) => { - ToastAndroid.show(t("Xatolik yuz berdi"), ToastAndroid.TOP); try { await WebBrowser.openBrowserAsync(fileUrl); } catch (error) { - ToastAndroid.show(t("Xatolik yuz berdi"), ToastAndroid.TOP); + Toast.error(t("Xatolik yuz berdi")); } }; diff --git a/screens/profile/lib/api.ts b/screens/profile/lib/api.ts index 5618afe..a3e9448 100644 --- a/screens/profile/lib/api.ts +++ b/screens/profile/lib/api.ts @@ -3,12 +3,12 @@ import { API_URLS } from '@/api/URLs'; import { ProductBody, ProductResponse } from '@/screens/home/lib/types'; import { AxiosResponse } from 'axios'; import { - ExployeesResponse, - MyAdsData, - MyAdsDataRes, - MyBonusesData, - NotificationListRes, - UserInfoResponseData, + ExployeesResponse, + MyAdsData, + MyAdsDataRes, + MyBonusesData, + NotificationListRes, + UserInfoResponseData, } from './type'; export const user_api = { @@ -61,10 +61,6 @@ export const user_api = { return res; }, - async my_ads_detail(id: number): Promise> { - const res = await httpClient.get(API_URLS.My_Ads_Detail(id)); - return res; - }, async my_bonuses(params: { page: number; @@ -109,6 +105,11 @@ export const user_api = { return res; }, + async ads_detail(id: number): Promise> { + const res = await httpClient.get(API_URLS.Ads_Detail(id)); + return res; + }, + async my_referrals(params: { page: number; page_size: number }) { const res = await httpClient.get(API_URLS.My_Refferals, { params }); return res; diff --git a/screens/profile/ui/AddEmployee.tsx b/screens/profile/ui/AddEmployee.tsx index 3e862d2..ea89b7c 100644 --- a/screens/profile/ui/AddEmployee.tsx +++ b/screens/profile/ui/AddEmployee.tsx @@ -3,7 +3,7 @@ import { formatPhone, normalizeDigits } from '@/constants/formatPhone'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { AxiosError } from 'axios'; import { useRouter } from 'expo-router'; -import { ArrowLeft } from 'lucide-react-native'; +import { ArrowLeft, Check } from 'lucide-react-native'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -14,9 +14,9 @@ import { StyleSheet, Text, TextInput, - ToastAndroid, - View, + View } from 'react-native'; +import { Toast } from 'toastify-react-native'; import { user_api } from '../lib/api'; export default function AddEmployee() { @@ -60,14 +60,14 @@ export default function AddEmployee() { const handleSave = () => { if (!firstName.trim() || !lastName.trim() || !phoneNumber.trim()) { - ToastAndroid.show(t("Barcha maydonlarni to'ldiring"), ToastAndroid.SHORT); + Toast.info(t("Barcha maydonlarni to'ldiring")); return; } mutate({ first_name: firstName.trim(), last_name: lastName.trim(), - phone: phoneNumber.trim(), + phone: `998${phoneNumber.trim()}`, }); }; @@ -77,12 +77,12 @@ export default function AddEmployee() { router.push('/profile/employees')}> - {t("Yangi xodim qo'shish")} + {t("Xodim qo'shish")} {isPending ? ( ) : ( - {t('Saqlash')} + )} diff --git a/screens/profile/ui/AnnouncementsTab.tsx b/screens/profile/ui/AnnouncementsTab.tsx index 93dd0ab..bfc6ceb 100644 --- a/screens/profile/ui/AnnouncementsTab.tsx +++ b/screens/profile/ui/AnnouncementsTab.tsx @@ -52,7 +52,7 @@ export function AnnouncementsTab() { }; const [refreshing, setRefreshing] = useState(false); - const [selectedAnnouncement, setSelectedAnnouncement] = useState(null); + const [selectedAnnouncement, setSelectedAnnouncement] = useState(null); const [sheetOpen, setSheetOpen] = useState(false); const bottomSheetModalRef = useRef(null); const { data, isLoading, isError, fetchNextPage, hasNextPage, refetch } = useInfiniteQuery({ @@ -82,14 +82,14 @@ export function AnnouncementsTab() { isLoading: loadingDetail, isError: detailError, } = useQuery({ - queryKey: ['my_ads_id', selectedAnnouncement?.id], - queryFn: () => user_api.my_ads_detail(selectedAnnouncement?.id!), + queryKey: ['my_ads_id', selectedAnnouncement], + queryFn: () => user_api.ads_detail(Number(selectedAnnouncement)), select: (res) => res.data.data, - enabled: !!selectedAnnouncement && sheetOpen, + enabled: !!selectedAnnouncement, }); const openSheet = (item: MyAdsDataRes) => { - setSelectedAnnouncement(item); + setSelectedAnnouncement(item.id); setSheetOpen(true); requestAnimationFrame(() => bottomSheetRef.current?.present()); }; @@ -176,7 +176,7 @@ export function AnnouncementsTab() { if (isError) { return ( - {t('Xatolik yuz berdi')} + {t('Xatolik yuz berdi')} ); } @@ -196,7 +196,7 @@ export function AnnouncementsTab() { item.id.toString()} - contentContainerStyle={styles.list} + contentContainerStyle={[styles.list, { flexGrow: 1 }]} refreshControl={ } @@ -204,7 +204,7 @@ export function AnnouncementsTab() { renderItem={({ item }) => ( openSheet(item)} + onPress={() => { openSheet(item); setSelectedAnnouncement(item.id) }} > {item.files?.[0]?.file && ( @@ -243,6 +243,14 @@ export function AnnouncementsTab() { )} + ListEmptyComponent={() => ( + + + + {t('Hozircha hech qanday eʼlon mavjud emas')} + + + )} /> { - setSheetOpen(false); - setSelectedAnnouncement(null); + setSelectedAnnouncement(null); // shu yetarli }} > {loadingDetail && } {detailError && ( - {t('Xatolik yuz berdi')} + {t('Xatolik yuz berdi')} )} {detail && ( @@ -374,7 +381,7 @@ export function AnnouncementsTab() { isDark ? styles.darkPaymentItem : styles.lightPaymentItem, { flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'center' }, ]} - onPress={() => sendPayment({ id: selectedAnnouncement?.id!, type: 'payme' })} + onPress={() => sendPayment({ id: selectedAnnouncement!, type: 'payme' })} > @@ -384,7 +391,7 @@ export function AnnouncementsTab() { styles.paymentItem, isDark ? styles.darkPaymentItem : styles.lightPaymentItem, ]} - onPress={() => sendPayment({ id: selectedAnnouncement?.id!, type: 'referral' })} + onPress={() => sendPayment({ id: selectedAnnouncement!, type: 'referral' })} > {t('Referal orqali')} @@ -498,6 +505,22 @@ const styles = StyleSheet.create({ fontWeight: '700', }, - loading: {}, - error: {}, + emptyContainer: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 60, + }, + + emptyTitle: { + fontSize: 18, + fontWeight: '600', + marginTop: 12, + }, + + emptyDesc: { + fontSize: 14, + marginTop: 6, + textAlign: 'center', + }, }); diff --git a/screens/profile/ui/BonusesScreen.tsx b/screens/profile/ui/BonusesScreen.tsx index 0e21cac..526701f 100644 --- a/screens/profile/ui/BonusesScreen.tsx +++ b/screens/profile/ui/BonusesScreen.tsx @@ -164,6 +164,7 @@ const styles = StyleSheet.create({ padding: 16, gap: 16, paddingBottom: 30, + flexGrow: 1 }, card: { borderRadius: 20, @@ -236,6 +237,7 @@ const styles = StyleSheet.create({ fontWeight: '600' as const, }, emptyContainer: { + flex: 1, alignItems: 'center', justifyContent: 'center', paddingVertical: 80, diff --git a/screens/profile/ui/CreateReferrals.tsx b/screens/profile/ui/CreateReferrals.tsx index 0fc1cde..94a6f7d 100644 --- a/screens/profile/ui/CreateReferrals.tsx +++ b/screens/profile/ui/CreateReferrals.tsx @@ -12,9 +12,9 @@ import { Switch, Text, TextInput, - ToastAndroid, - View, + View } from 'react-native'; +import { Toast } from 'toastify-react-native'; import { user_api } from '../lib/api'; type FormType = { @@ -44,12 +44,12 @@ export default function CreateReferrals() { is_agent: boolean; }) => user_api.create_referral(body), onSuccess: () => { - ToastAndroid.show(t('Referral yaratildi'), ToastAndroid.SHORT); + Toast.success(t('Referral yaratildi')); queryClient.refetchQueries({ queryKey: ['my_referrals'] }); router.back(); }, onError: () => { - ToastAndroid.show(t('Xatolik yuz berdi'), ToastAndroid.SHORT); + Toast.error(t('Xatolik yuz berdi')); }, }); diff --git a/screens/profile/ui/EditServices.tsx b/screens/profile/ui/EditServices.tsx index fa53d94..e57c0e5 100644 --- a/screens/profile/ui/EditServices.tsx +++ b/screens/profile/ui/EditServices.tsx @@ -151,7 +151,7 @@ export default function EditService() { - {step === 1 ? t('Xizmatni tahrirlash (1/2)') : t('Xizmatni tahrirlash (2/2)')} + {t('Xizmatni tahrirlash')} item.phone} renderItem={renderItem} - contentContainerStyle={styles.list} + contentContainerStyle={[styles.list, { flexGrow: 1 }]} onEndReached={() => { if (hasNextPage && !isFetchingNextPage) { fetchNextPage(); @@ -176,7 +176,12 @@ const styles = StyleSheet.create({ infoContainer: { flex: 1, gap: 4 }, name: { fontSize: 17, fontWeight: '700' }, phone: { fontSize: 15, fontWeight: '500' }, - emptyContainer: { alignItems: 'center', justifyContent: 'center', paddingVertical: 80, gap: 16 }, + emptyContainer: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 60, + }, emptyText: { fontSize: 17, fontWeight: '600' }, emptyButton: { flexDirection: 'row', diff --git a/screens/profile/ui/ManualTab.tsx b/screens/profile/ui/ManualTab.tsx index 10f1801..10261f7 100644 --- a/screens/profile/ui/ManualTab.tsx +++ b/screens/profile/ui/ManualTab.tsx @@ -68,9 +68,9 @@ export function ManualTab() { /** 🔹 Video manbalari (SELECT ga bog‘liq) */ const videos = { - uz: require('@/assets/manual/manual_video_uz.webm'), - ru: require('@/assets/manual/manual_video_ru.webm'), - en: require('@/assets/manual/manual_video_en.webm'), + uz: require('@/assets/manual/manual_video_uz.mp4'), + ru: require('@/assets/manual/manual_video_ru.mp4'), + en: require('@/assets/manual/manual_video_en.mp4'), }; const player = useVideoPlayer(videos[selectedLang], (player) => { @@ -158,8 +158,6 @@ export function ManualTab() { [imageLang] ); - const selectedLanguage = languages.find((l) => l.code === selectedLang); - useEffect(() => { // listener qo'shish const subscription = player.addListener('playingChange', (state) => { @@ -350,7 +348,7 @@ const styles = StyleSheet.create({ container: { flex: 1 }, hero: { padding: 20 }, topHeader: { flexDirection: 'row', alignItems: 'center', gap: 12 }, - headerTitle: { fontSize: 22, fontWeight: '700', marginHorizontal: 16 }, + headerTitle: { fontSize: 18, fontWeight: '700', marginHorizontal: 16 }, subtitle: { fontSize: 16, marginTop: 5, fontWeight: '500', marginHorizontal: 16 }, section: { marginBottom: 28 }, diff --git a/screens/profile/ui/MyServices.tsx b/screens/profile/ui/MyServices.tsx index 78c7256..b79f3b3 100644 --- a/screens/profile/ui/MyServices.tsx +++ b/screens/profile/ui/MyServices.tsx @@ -116,7 +116,7 @@ export default function MyServicesScreen() { item.id.toString()} - contentContainerStyle={styles.list} + contentContainerStyle={[styles.list, { flexGrow: 1 }]} onEndReached={() => hasNextPage && fetchNextPage()} refreshControl={ {t('Bildirishnomalar')} - - {notifications.some((n) => !n.is_read) && ( - markAllAsRead()} - disabled={isMarkingAllRead} - > - {isMarkingAllRead ? ( - - ) : ( - - {t("Barchasi o'qildi")} - - )} - - )} item.id.toString()} contentContainerStyle={styles.listContent} showsVerticalScrollIndicator={false} - renderItem={({ item, index }) => } + ListHeaderComponent={() => { + if (notifications.length === 0) { + return ( + + + {t("Hozircha bildirishnomalar yo'q")} + + + + {t("Yangi xabarlar shu yerda paydo bo‘ladi")} + + + ); + } + + if (notifications.some((n) => !n.is_read)) { + return ( + + markAllAsRead()} + disabled={isMarkingAllRead} + > + {isMarkingAllRead ? ( + + ) : ( + + {t("Barchasi o'qildi")} + + )} + + + ); + } + + return ( + + + {t("Barcha bildirishnomalar o‘qilgan")} + + + ); + }} + renderItem={({ item }) => } onEndReached={() => { if (hasNextPage && !isFetchingNextPage) { fetchNextPage(); @@ -130,6 +175,15 @@ export function NotificationTab() { } refreshing={isLoading} onRefresh={refetch} + ListEmptyComponent={() => + !isLoading && ( + + + {t("Hozircha bildirishnomalar yo'q")} + + + ) + } /> ); @@ -266,6 +320,7 @@ const styles = StyleSheet.create({ listContent: { padding: 16, paddingBottom: 32, + flexGrow: 1 }, /* Card Styles */ @@ -292,6 +347,38 @@ const styles = StyleSheet.create({ flex: 1, justifyContent: 'center', }, + headerActions: { + marginBottom: 16, + alignItems: 'flex-end', + }, + + emptyHeader: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 60, + }, + + allReadContainer: { + marginBottom: 16, + alignItems: 'center', + }, + + allReadText: { + fontSize: 14, + }, + + emptyTitle: { + fontSize: 18, + fontWeight: '700', + marginBottom: 8, + textAlign: 'center', + }, + + emptyDesc: { + fontSize: 14, + textAlign: 'center', + }, cardHeader: { flexDirection: 'row', alignItems: 'center', @@ -407,7 +494,7 @@ const styles = StyleSheet.create({ paddingVertical: 8, borderRadius: 8, borderWidth: 1, - minWidth: 80, + width: "auto", alignItems: 'center', justifyContent: 'center', }, @@ -415,4 +502,10 @@ const styles = StyleSheet.create({ fontSize: 13, fontWeight: '600', }, + emptyContainer: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + paddingHorizontal: 30, + }, }); diff --git a/screens/profile/ui/ProductServicesTab.tsx b/screens/profile/ui/ProductServicesTab.tsx index 76d228d..a280629 100644 --- a/screens/profile/ui/ProductServicesTab.tsx +++ b/screens/profile/ui/ProductServicesTab.tsx @@ -260,7 +260,7 @@ export function ProductServicesTab() { style={[ styles.categoryOptionText, selectedCategories.includes(category) && - styles.categoryOptionTextSelected, + styles.categoryOptionTextSelected, ]} > {category} @@ -313,6 +313,7 @@ const styles = StyleSheet.create({ }, list: { gap: 16, + flexGrow: 1 }, card: { backgroundColor: '#1e293b', @@ -372,6 +373,7 @@ const styles = StyleSheet.create({ fontWeight: '500' as const, }, emptyContainer: { + flex: 1, alignItems: 'center', justifyContent: 'center', paddingVertical: 80, diff --git a/screens/profile/ui/RefferallsTab.tsx b/screens/profile/ui/RefferallsTab.tsx index 0f71d57..72661a2 100644 --- a/screens/profile/ui/RefferallsTab.tsx +++ b/screens/profile/ui/RefferallsTab.tsx @@ -2,7 +2,7 @@ import { useTheme } from '@/components/ThemeContext'; import { useInfiniteQuery } from '@tanstack/react-query'; import * as Clipboard from 'expo-clipboard'; import { useRouter } from 'expo-router'; -import { ArrowLeft, CopyIcon, HandCoins, Plus, Users } from 'lucide-react-native'; +import { ArrowLeft, CopyIcon, Gift, HandCoins, Plus, Users } from 'lucide-react-native'; import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -14,11 +14,11 @@ import { Share, StyleSheet, Text, - ToastAndroid, TouchableOpacity, - View, + View } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; +import { Toast } from 'toastify-react-native'; import { user_api } from '../lib/api'; const PAGE_SIZE = 10; @@ -84,7 +84,7 @@ export function ReferralsTab() { } if (Platform.OS === 'android') { - ToastAndroid.show(t('Refferal kopiya qilindi'), ToastAndroid.SHORT); + Toast.success(t('Refferal kopiya qilindi')); } }; @@ -153,11 +153,22 @@ export function ReferralsTab() { )} - ListEmptyComponent={ - - {t('Refferallar topilmadi')} - - } + ListEmptyComponent={() => ( + + + + + + + {t("Refferallar mavjud emas")} + + + )} /> ); @@ -175,7 +186,7 @@ const styles = StyleSheet.create({ }, headerTitle: { fontSize: 18, fontWeight: '700' }, - list: { padding: 16, gap: 12, paddingBottom: 30 }, + list: { padding: 16, gap: 12, paddingBottom: 30, flexGrow: 1 }, card: { borderRadius: 16, @@ -217,4 +228,46 @@ const styles = StyleSheet.create({ amount: { fontWeight: '700', }, + emptyContainer: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + paddingHorizontal: 30, + }, + + emptyIconWrapper: { + width: 80, + height: 80, + borderRadius: 40, + alignItems: 'center', + justifyContent: 'center', + marginBottom: 20, + elevation: 4, + }, + + emptyTitle: { + fontSize: 18, + fontWeight: '700', + marginBottom: 8, + textAlign: 'center', + }, + + emptyDesc: { + fontSize: 14, + textAlign: 'center', + marginBottom: 20, + lineHeight: 20, + }, + + emptyButton: { + paddingHorizontal: 24, + paddingVertical: 12, + borderRadius: 12, + }, + + emptyButtonText: { + color: '#fff', + fontWeight: '600', + fontSize: 15, + }, });