Compare commits
10 Commits
a54af819c5
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a34cf75c57 | ||
|
|
382b214e3e | ||
|
|
c71651ec4b | ||
|
|
4d5cc84850 | ||
|
|
ab363ca3b9 | ||
|
|
bdc205b538 | ||
|
|
a0e5582fc7 | ||
|
|
dc7e39a7e0 | ||
|
|
ad0d0d796c | ||
|
|
7aa130bdd6 |
@@ -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/',
|
||||
|
||||
8
app.json
8
app.json
@@ -2,7 +2,7 @@
|
||||
"expo": {
|
||||
"name": "Info target",
|
||||
"slug": "infotarget",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.2",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/images/logo.png",
|
||||
"scheme": "infotarget",
|
||||
@@ -13,12 +13,14 @@
|
||||
"infoPlist": {
|
||||
"UIBackgroundModes": [
|
||||
"remote-notification"
|
||||
]
|
||||
],
|
||||
"UIViewControllerBasedStatusBarAppearance": true
|
||||
},
|
||||
"bundleIdentifier": "com.felix.infotarget"
|
||||
},
|
||||
"android": {
|
||||
"useNextNotificationsApi": true,
|
||||
"softwareKeyboardLayoutMode": "resize",
|
||||
"adaptiveIcon": {
|
||||
"backgroundColor": "#E6F4FE",
|
||||
"foregroundImage": "./assets/images/logo.png"
|
||||
@@ -88,4 +90,4 @@
|
||||
},
|
||||
"owner": "samandar111"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,9 @@ import { useHomeStore } from '@/screens/home/lib/hook';
|
||||
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
|
||||
import { router, Tabs } from 'expo-router';
|
||||
import { Home, Megaphone, PlusCircle, User } from 'lucide-react-native';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Animated, Easing, Platform, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { Animated, Easing, Keyboard, Platform, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
||||
|
||||
export default function TabsLayout() {
|
||||
@@ -15,6 +15,20 @@ export default function TabsLayout() {
|
||||
const { t } = useTranslation();
|
||||
const { setShowFilter, setStep } = useHomeStore();
|
||||
const rotateAnim = useRef(new Animated.Value(0)).current;
|
||||
const [keyboard, setKeyboard] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const showSub = Keyboard.addListener('keyboardDidShow', () => setKeyboard(true));
|
||||
const hideSub = Keyboard.addListener('keyboardDidHide', () => setKeyboard(false));
|
||||
|
||||
return () => {
|
||||
showSub.remove();
|
||||
hideSub.remove();
|
||||
};
|
||||
}, []);
|
||||
|
||||
console.log(keyboard);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
Animated.loop(
|
||||
@@ -52,9 +66,10 @@ export default function TabsLayout() {
|
||||
headerShadowVisible: false,
|
||||
tabBarStyle: {
|
||||
position: 'absolute',
|
||||
display: keyboard ? "none" : "flex",
|
||||
left: 16,
|
||||
right: 16,
|
||||
bottom: 4,
|
||||
bottom: 8,
|
||||
height: 70,
|
||||
paddingTop: 8,
|
||||
paddingBottom: 12,
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { useTheme } from '@/components/ThemeContext';
|
||||
import { FilterProvider } from '@/components/ui/FilterContext';
|
||||
import { CustomHeader } from '@/components/ui/Header';
|
||||
import CreateAdsScreens from '@/screens/create-ads/ui/CreateAdsScreens';
|
||||
|
||||
export default function CreateAnnouncements() {
|
||||
const { isDark } = useTheme();
|
||||
return (
|
||||
<FilterProvider>
|
||||
<CustomHeader />
|
||||
|
||||
@@ -7,34 +7,21 @@ import { ProfileDataProvider } from '@/screens/profile/lib/ProfileDataContext';
|
||||
import { Stack } from 'expo-router';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { View } from 'react-native';
|
||||
import 'react-native-reanimated';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import ToastManager from "toastify-react-native";
|
||||
|
||||
function AppContent() {
|
||||
useNotifications();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* iOS status bar fon */}
|
||||
<View
|
||||
style={{
|
||||
height: insets.top,
|
||||
backgroundColor: '#000',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* StatusBar */}
|
||||
<StatusBar
|
||||
style="light"
|
||||
backgroundColor="#000" // Android
|
||||
/>
|
||||
|
||||
<SafeAreaView style={{ flex: 1, backgroundColor: "#000" }}>
|
||||
<StatusBar style='light' backgroundColor='#000' />
|
||||
<Stack screenOptions={{ headerShown: false }} />
|
||||
</>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
export default function RootLayout() {
|
||||
return (
|
||||
<I18nextProvider i18n={i18n}>
|
||||
@@ -43,6 +30,7 @@ export default function RootLayout() {
|
||||
<ProfileDataProvider>
|
||||
<AuthProvider>
|
||||
<AppContent />
|
||||
<ToastManager />
|
||||
</AuthProvider>
|
||||
</ProfileDataProvider>
|
||||
</ThemeProvider>
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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'));
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
BIN
assets/announcements-video/video_en.mp4
Normal file
BIN
assets/announcements-video/video_en.mp4
Normal file
Binary file not shown.
Binary file not shown.
BIN
assets/announcements-video/video_ru.mp4
Normal file
BIN
assets/announcements-video/video_ru.mp4
Normal file
Binary file not shown.
Binary file not shown.
BIN
assets/announcements-video/video_uz.mp4
Normal file
BIN
assets/announcements-video/video_uz.mp4
Normal file
Binary file not shown.
Binary file not shown.
BIN
assets/goverment/video_ru.mp4
Normal file
BIN
assets/goverment/video_ru.mp4
Normal file
Binary file not shown.
Binary file not shown.
BIN
assets/manual/manual_video_en.mp4
Normal file
BIN
assets/manual/manual_video_en.mp4
Normal file
Binary file not shown.
Binary file not shown.
BIN
assets/manual/manual_video_ru.mp4
Normal file
BIN
assets/manual/manual_video_ru.mp4
Normal file
Binary file not shown.
Binary file not shown.
BIN
assets/manual/manual_video_uz.mp4
Normal file
BIN
assets/manual/manual_video_uz.mp4
Normal file
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,5 @@
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { router } from 'expo-router';
|
||||
import { createContext, useContext, useEffect, useState } from 'react';
|
||||
|
||||
type AuthContextType = {
|
||||
@@ -32,6 +33,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
await AsyncStorage.removeItem('access_token');
|
||||
await AsyncStorage.removeItem('refresh_token');
|
||||
setIsAuthenticated(false);
|
||||
router.replace('/(auth)');
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -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 && (
|
||||
<View style={styles.center}>
|
||||
<ActivityIndicator size="large" color="#3b82f6" />
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Error holati */}
|
||||
{detailError && (
|
||||
<View style={styles.center}>
|
||||
<Text style={{ color: '#ef4444', fontWeight: '600' }}>
|
||||
{t('Xatolik yuz berdi')}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Detail mavjud bo‘lsa */}
|
||||
{detail && (
|
||||
<>
|
||||
<View style={styles.carouselWrapper}>
|
||||
<FlatList
|
||||
nestedScrollEnabled={true}
|
||||
data={selectedProduct.files || []}
|
||||
data={detail.files || []}
|
||||
renderItem={renderCarouselItem}
|
||||
keyExtractor={(item) => item.id.toString()}
|
||||
horizontal
|
||||
@@ -197,9 +226,9 @@ export default function ProductList({ query }: Props) {
|
||||
setCurrentImageIndex(index);
|
||||
}}
|
||||
/>
|
||||
{selectedProduct.files.length > 1 && (
|
||||
{detail.files.length > 1 && (
|
||||
<View style={styles.pagination}>
|
||||
{selectedProduct.files.map((_, i) => (
|
||||
{detail.files.map((_, i) => (
|
||||
<View
|
||||
key={i}
|
||||
style={[
|
||||
@@ -214,10 +243,10 @@ export default function ProductList({ query }: Props) {
|
||||
|
||||
<View style={styles.sheetHeader}>
|
||||
<Text style={[styles.sheetTitle, isDark ? styles.darkText : styles.lightText]}>
|
||||
{selectedProduct.title}
|
||||
{detail.title}
|
||||
</Text>
|
||||
<View style={styles.sheetCompanyBadge}>
|
||||
<Text style={styles.sheetCompanyText}>{selectedProduct.company}</Text>
|
||||
<Text style={styles.sheetCompanyText}>{detail.company}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -230,10 +259,8 @@ export default function ProductList({ query }: Props) {
|
||||
{t("Batafsil ma'lumot")}
|
||||
</Text>
|
||||
</View>
|
||||
<Text
|
||||
style={[styles.sheetDescription, isDark ? styles.darkText : styles.lightText]}
|
||||
>
|
||||
{selectedProduct.description || "Ma'lumot mavjud emas."}
|
||||
<Text style={[styles.sheetDescription, isDark ? styles.darkText : styles.lightText]}>
|
||||
{detail.description || "Ma'lumot mavjud emas."}
|
||||
</Text>
|
||||
</View>
|
||||
</>
|
||||
@@ -245,7 +272,6 @@ export default function ProductList({ query }: Props) {
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
listContainer: { gap: 0, paddingBottom: 20 },
|
||||
card: {
|
||||
borderRadius: 16,
|
||||
overflow: 'hidden',
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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": "Пока нет уведомлений"
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
81
package-lock.json
generated
81
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
"ios": "expo run:ios",
|
||||
"web": "expo start --web",
|
||||
"build": "expo prebuild && cd android && gradlew.bat assembleDebug",
|
||||
"lint": "expo lint"
|
||||
"lint": "expo lint",
|
||||
"ios:init": "expo prebuild -p ios"
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/html-elements": "^0.10.1",
|
||||
@@ -75,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": {
|
||||
|
||||
@@ -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<Record<'uz' | 'ru' | 'en', any>> = {
|
||||
ru: require('@/assets/goverment/video_ru.webm'),
|
||||
ru: require('@/assets/goverment/video_ru.mp4'),
|
||||
};
|
||||
|
||||
// Update selected language
|
||||
|
||||
@@ -16,12 +16,11 @@ 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 +97,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'));
|
||||
},
|
||||
});
|
||||
|
||||
@@ -130,72 +129,71 @@ const ConfirmScreen = () => {
|
||||
<View style={styles.decorCircle1} />
|
||||
<View style={styles.decorCircle2} />
|
||||
<AuthHeader />
|
||||
<SafeAreaView style={{ flex: 1 }}>
|
||||
<KeyboardAwareScrollView
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
showsVerticalScrollIndicator={false}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
>
|
||||
<View style={styles.header}>
|
||||
<View style={styles.iconContainer}>
|
||||
<LinearGradient colors={['#3b82f6', '#2563eb']} style={styles.iconGradient}>
|
||||
<ShieldCheck size={32} color="#ffffff" strokeWidth={2.2} />
|
||||
</LinearGradient>
|
||||
</View>
|
||||
<KeyboardAwareScrollView
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
enableOnAndroid
|
||||
extraScrollHeight={120}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
>
|
||||
<View style={styles.header}>
|
||||
<View style={styles.iconContainer}>
|
||||
<LinearGradient colors={['#3b82f6', '#2563eb']} style={styles.iconGradient}>
|
||||
<ShieldCheck size={32} color="#ffffff" strokeWidth={2.2} />
|
||||
</LinearGradient>
|
||||
</View>
|
||||
|
||||
<Text style={styles.title}>{t('Kodni tasdiqlash')}</Text>
|
||||
<Text style={styles.subtitle}>
|
||||
{t("Tasdiqlash kodi sizning Telegram botingizga yuboriladi. Botni ko'rish")}
|
||||
</Text>
|
||||
<Text style={styles.title}>{t('Kodni tasdiqlash')}</Text>
|
||||
<Text style={styles.subtitle}>
|
||||
{t("Tasdiqlash kodi sizning Telegram botingizga yuboriladi. Botni ko'rish")}
|
||||
</Text>
|
||||
|
||||
<View style={styles.phoneBadge}>
|
||||
<Text style={styles.phoneText}>+{phoneOTP}</Text>
|
||||
</View>
|
||||
<View style={styles.phoneBadge}>
|
||||
<Text style={styles.phoneText}>+{phoneOTP}</Text>
|
||||
</View>
|
||||
|
||||
{/* Telegram Button */}
|
||||
<TouchableOpacity
|
||||
style={styles.telegramBanner}
|
||||
onPress={openBotLink}
|
||||
activeOpacity={0.8}
|
||||
{/* Telegram Button */}
|
||||
<TouchableOpacity
|
||||
style={styles.telegramBanner}
|
||||
onPress={openBotLink}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<LinearGradient
|
||||
colors={['#0088cc', '#00a2ed']}
|
||||
style={styles.telegramGradient}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 0 }}
|
||||
>
|
||||
<LinearGradient
|
||||
colors={['#0088cc', '#00a2ed']}
|
||||
style={styles.telegramGradient}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 0 }}
|
||||
>
|
||||
<View style={styles.botIconCircle}>
|
||||
<MessageCircle size={20} color="#0088cc" fill="#fff" />
|
||||
</View>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={styles.telegramTitle}>{t('Botni ochish')}</Text>
|
||||
<Text style={styles.telegramSub}>
|
||||
{t('Telegram botni ochish uchun tugmani bosing va kodni oling')}
|
||||
</Text>
|
||||
</View>
|
||||
<ArrowLeft size={20} color="#fff" style={{ transform: [{ rotate: '180deg' }] }} />
|
||||
</LinearGradient>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={styles.botIconCircle}>
|
||||
<MessageCircle size={20} color="#0088cc" fill="#fff" />
|
||||
</View>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={styles.telegramTitle}>{t('Botni ochish')}</Text>
|
||||
<Text style={styles.telegramSub}>
|
||||
{t('Telegram botni ochish uchun tugmani bosing va kodni oling')}
|
||||
</Text>
|
||||
</View>
|
||||
<ArrowLeft size={20} color="#fff" style={{ transform: [{ rotate: '180deg' }] }} />
|
||||
</LinearGradient>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<View style={styles.card}>
|
||||
<ConfirmForm
|
||||
onSubmit={(otp) => mutate({ code: otp, phone: phoneOTP || '' })}
|
||||
isLoading={isPending}
|
||||
error={error}
|
||||
onResendPress={() => resendMutation.mutate({ phone: phoneOTP || '' })}
|
||||
resendTimer={resendTimer}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.card}>
|
||||
<ConfirmForm
|
||||
onSubmit={(otp) => mutate({ code: otp, phone: phoneOTP || '' })}
|
||||
isLoading={isPending}
|
||||
error={error}
|
||||
onResendPress={() => resendMutation.mutate({ phone: phoneOTP || '' })}
|
||||
resendTimer={resendTimer}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* <View style={styles.infoBox}>
|
||||
{/* <View style={styles.infoBox}>
|
||||
<Text style={styles.infoText}>
|
||||
<Text style={{ fontWeight: '700' }}>Eslatma:</Text> Kod SMS orqali kelmaydi. Agar
|
||||
botni ishga tushirmagan bo'lsangiz, yuqoridagi tugmani bosing.
|
||||
</Text>
|
||||
</View> */}
|
||||
</KeyboardAwareScrollView>
|
||||
</SafeAreaView>
|
||||
</KeyboardAwareScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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: () => {
|
||||
|
||||
@@ -11,20 +11,28 @@ import { AxiosError } from 'axios';
|
||||
import { Image } from 'expo-image';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { Building2, CheckIcon, Globe, Hash, Search, ShieldCheck, User, UserPlus } from 'lucide-react-native';
|
||||
import {
|
||||
Building2,
|
||||
CheckIcon,
|
||||
Globe,
|
||||
Hash,
|
||||
Search,
|
||||
ShieldCheck,
|
||||
User,
|
||||
UserPlus,
|
||||
} from 'lucide-react-native';
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
ScrollView,
|
||||
Keyboard,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
View
|
||||
} from 'react-native';
|
||||
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { auth_api } from '../login/lib/api';
|
||||
import PhonePrefix from '../login/ui/PhonePrefix';
|
||||
@@ -100,6 +108,7 @@ export default function RegisterFormScreen() {
|
||||
});
|
||||
|
||||
const openCountrySheet = useCallback(() => {
|
||||
Keyboard.dismiss();
|
||||
setTimeout(() => {
|
||||
countrySheetRef.current?.snapToIndex(0);
|
||||
}, 100);
|
||||
@@ -125,15 +134,16 @@ export default function RegisterFormScreen() {
|
||||
|
||||
const selectedCountryName = useMemo(() => {
|
||||
if (!selectedCountry) return t('Tanlang');
|
||||
return countryResponse?.find((c: any) => c.flag?.toUpperCase() === selectedCountry)?.name || t('Tanlang');
|
||||
return (
|
||||
countryResponse?.find((c: any) => c.flag?.toUpperCase() === selectedCountry)?.name ||
|
||||
t('Tanlang')
|
||||
);
|
||||
}, [selectedCountry, countryResponse, t]);
|
||||
|
||||
const filteredCountries = useMemo(() => {
|
||||
if (!countrySearch.trim()) return countryResponse || [];
|
||||
const q = countrySearch.toLowerCase().trim();
|
||||
return (countryResponse || []).filter((c: any) =>
|
||||
c.name?.toLowerCase().includes(q)
|
||||
);
|
||||
return (countryResponse || []).filter((c: any) => c.name?.toLowerCase().includes(q));
|
||||
}, [countryResponse, countrySearch]);
|
||||
|
||||
const headerInfo = getHeaderInfo(personType);
|
||||
@@ -151,7 +161,6 @@ export default function RegisterFormScreen() {
|
||||
onSuccess: (res) => {
|
||||
setInfo(res.data.data);
|
||||
setLoading(false);
|
||||
// INN o'zgarganda director ma'lumotlarini tozalash
|
||||
setDirectorJshshr('');
|
||||
setDirectorInfo(null);
|
||||
setErrorDirectorInfo(null);
|
||||
@@ -169,29 +178,16 @@ export default function RegisterFormScreen() {
|
||||
mutationFn: (body: { value: string }) => auth_api.get_director_info(body),
|
||||
onSuccess: (res) => {
|
||||
const directorData = res.data;
|
||||
|
||||
// -------------------------------------------------------
|
||||
// INN TEKSHIRUVI
|
||||
// Director response strukturasi:
|
||||
// data.entity.name.rows = [{ inn: "123456789", ... }, ...]
|
||||
// Shu rows ichida joriy inn bor-yo'qligini tekshiramiz
|
||||
// -------------------------------------------------------
|
||||
const rows: Array<{ inn: string }> =
|
||||
directorData?.data?.entity?.name?.rows ?? [];
|
||||
|
||||
const rows: Array<{ inn: string }> = directorData?.data?.entity?.name?.rows ?? [];
|
||||
const innMatch = rows.some((row) => row.inn === inn);
|
||||
|
||||
if (!innMatch) {
|
||||
// Director bu tashkilotga tegishli emas
|
||||
setDirectorInfo(null);
|
||||
setErrorDirectorInfo(
|
||||
t("Bu direktor ko'rsatilgan tashkilotga tegishli emas")
|
||||
);
|
||||
setErrorDirectorInfo(t("Bu direktor ko'rsatilgan tashkilotga tegishli emas"));
|
||||
setDirectorLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// INN mos keldi — director ma'lumotini saqlash
|
||||
setDirectorInfo(directorData);
|
||||
setDirectorLoading(false);
|
||||
setErrorDirectorInfo(null);
|
||||
@@ -241,7 +237,10 @@ export default function RegisterFormScreen() {
|
||||
|
||||
const handlePassportSeriesChange = useCallback(
|
||||
(text: string) => {
|
||||
const v = text.toUpperCase().replace(/[^A-Z]/g, '').slice(0, 2);
|
||||
const v = text
|
||||
.toUpperCase()
|
||||
.replace(/[^A-Z]/g, '')
|
||||
.slice(0, 2);
|
||||
setPassportSeries(v);
|
||||
if (personType && passportNumber.length === 7 && jshshr.length === 14) {
|
||||
setLoading(true);
|
||||
@@ -315,7 +314,7 @@ export default function RegisterFormScreen() {
|
||||
inn.length === 9 &&
|
||||
info &&
|
||||
directorJshshr.length === 14 &&
|
||||
directorInfo // faqat INN mos kelganda to'ldiriladi
|
||||
directorInfo
|
||||
);
|
||||
}
|
||||
|
||||
@@ -323,7 +322,6 @@ export default function RegisterFormScreen() {
|
||||
})();
|
||||
|
||||
const handleContinue = () => {
|
||||
// Director to'liq ismi directorInfo dan olinadi
|
||||
const directorFullName = isLegal
|
||||
? (directorInfo?.data?.entrepreneur?.rows[0]?.entrepreneur ?? '')
|
||||
: `${info?.firstname ?? ''} ${info?.lastname ?? ''} ${info?.middlename ?? ''}`.trim();
|
||||
@@ -350,29 +348,36 @@ export default function RegisterFormScreen() {
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<View style={styles.container}>
|
||||
<LinearGradient
|
||||
colors={['#0f172a', '#1e293b', '#334155']}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 1 }}
|
||||
style={StyleSheet.absoluteFill}
|
||||
/>
|
||||
<View style={styles.decorCircle1} />
|
||||
<View style={styles.decorCircle2} />
|
||||
// ✅ Fragment — BottomSheet tashqarida bo'lishi uchun
|
||||
<>
|
||||
<KeyboardAwareScrollView
|
||||
enableOnAndroid
|
||||
enableAutomaticScroll
|
||||
extraScrollHeight={120}
|
||||
style={styles.keyboardScroll}
|
||||
>
|
||||
{/* ✅ TouchableWithoutFeedback yo'q — onStartShouldSetResponder ishlatildi */}
|
||||
<View
|
||||
style={styles.container}
|
||||
onStartShouldSetResponder={() => {
|
||||
Keyboard.dismiss();
|
||||
return false; // false — child elementlar (buttonlar) ham ishlaydi
|
||||
}}
|
||||
>
|
||||
<LinearGradient
|
||||
colors={['#0f172a', '#1e293b', '#334155']}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 1 }}
|
||||
style={StyleSheet.absoluteFill}
|
||||
/>
|
||||
<View style={styles.decorCircle1} />
|
||||
<View style={styles.decorCircle2} />
|
||||
|
||||
<AuthHeader />
|
||||
<AuthHeader />
|
||||
|
||||
<SafeAreaView style={{ flex: 1 }} edges={['bottom']}>
|
||||
<KeyboardAvoidingView
|
||||
style={{ flex: 1 }}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||
>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
showsVerticalScrollIndicator={false}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
>
|
||||
{/* ✅ SafeAreaView va ichki ScrollView yo'q — to'g'ridan View */}
|
||||
<SafeAreaView style={{ flex: 1 }} edges={['bottom']}>
|
||||
<View style={styles.scrollContent}>
|
||||
<View style={styles.header}>
|
||||
<View style={styles.iconContainer}>
|
||||
<LinearGradient
|
||||
@@ -394,43 +399,6 @@ export default function RegisterFormScreen() {
|
||||
|
||||
<View style={styles.card}>
|
||||
<View style={styles.formGap}>
|
||||
{/* ---- Davlat (Country) ---- */}
|
||||
{/* <View>
|
||||
<Text style={styles.label}>{t('Davlat')}</Text>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.input,
|
||||
styles.inputDisabled,
|
||||
]}
|
||||
onPress={openCountrySheet}
|
||||
activeOpacity={0.7}
|
||||
testID="country-select"
|
||||
disabled
|
||||
>
|
||||
{countryLoading ? (
|
||||
<ActivityIndicator size="small" color="#3b82f6" style={{ flex: 1 }} />
|
||||
) : (
|
||||
<>
|
||||
<Image
|
||||
source={{ uri: `https://flagcdn.com/w320/${selectedCountry.toLowerCase()}.png` }}
|
||||
style={{ width: 30, height: 15 }}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
<Text
|
||||
style={[
|
||||
styles.textInput,
|
||||
// { color: selectedCountry === 'all' ? '#94a3b8' : '#1e293b' },
|
||||
{ color: '#94a3b8' },
|
||||
]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{selectedCountryName}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
<ChevronDown size={18} color={'#cbd5e1'} />
|
||||
</TouchableOpacity>
|
||||
</View> */}
|
||||
{/* ---- YATT / BAND ---- */}
|
||||
{isYattOrBand && (
|
||||
<>
|
||||
@@ -510,8 +478,9 @@ export default function RegisterFormScreen() {
|
||||
)}
|
||||
|
||||
{/* ---- LEGAL ENTITY: Kompaniya ma'lumoti ---- */}
|
||||
{isLegal && info && (
|
||||
hasValidName ? (
|
||||
{isLegal &&
|
||||
info &&
|
||||
(hasValidName ? (
|
||||
<View style={styles.infoBox}>
|
||||
<Text style={styles.infoLabel}>{t('Tashkilot')}</Text>
|
||||
<Text style={styles.infoText}>{info.fullName || info.name}</Text>
|
||||
@@ -527,10 +496,9 @@ export default function RegisterFormScreen() {
|
||||
<View style={styles.errorBox}>
|
||||
<Text style={styles.errorText}>{t('Tashkilot topilmadi')}</Text>
|
||||
</View>
|
||||
)
|
||||
)}
|
||||
))}
|
||||
|
||||
{/* ---- LEGAL ENTITY: Direktor JSHSHR — faqat info kelganda ko'rinadi ---- */}
|
||||
{/* ---- LEGAL ENTITY: Direktor JSHSHR ---- */}
|
||||
{isLegal && info && (
|
||||
<View>
|
||||
<Text style={styles.label}>{t('Direktor JSHSHR')}</Text>
|
||||
@@ -551,7 +519,6 @@ export default function RegisterFormScreen() {
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* ---- Direktor ma'lumoti (INN mos kelgan holda) ---- */}
|
||||
{directorInfo && (
|
||||
<View style={[styles.infoBox, { marginTop: 8 }]}>
|
||||
<Text style={styles.infoLabel}>{t('Direktor')}</Text>
|
||||
@@ -561,7 +528,6 @@ export default function RegisterFormScreen() {
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* ---- Xato: INN mos kelmasa yoki server xatosi ---- */}
|
||||
{directorInfoError && (
|
||||
<View style={[styles.errorBox, { marginTop: 8 }]}>
|
||||
<Text style={styles.errorText}>{directorInfoError}</Text>
|
||||
@@ -577,11 +543,11 @@ export default function RegisterFormScreen() {
|
||||
<Hash size={18} color="#94a3b8" />
|
||||
<TextInput
|
||||
value={referal}
|
||||
keyboardType="numeric"
|
||||
placeholder={t('Referal kodi')}
|
||||
placeholderTextColor="#94a3b8"
|
||||
style={styles.textInput}
|
||||
onChangeText={setReferal}
|
||||
maxLength={9}
|
||||
testID="referal-input"
|
||||
/>
|
||||
</View>
|
||||
@@ -605,8 +571,9 @@ export default function RegisterFormScreen() {
|
||||
</View>
|
||||
|
||||
{/* ---- YATT/BAND info ---- */}
|
||||
{!isLegal && info && (
|
||||
hasValidName ? (
|
||||
{!isLegal &&
|
||||
info &&
|
||||
(hasValidName ? (
|
||||
<View style={styles.infoBox}>
|
||||
<Text style={styles.infoText}>{info.fullName || info.name}</Text>
|
||||
</View>
|
||||
@@ -620,8 +587,7 @@ export default function RegisterFormScreen() {
|
||||
<View style={styles.errorBox}>
|
||||
<Text style={styles.errorText}>{t('Foydalanuvchi topilmadi')}</Text>
|
||||
</View>
|
||||
)
|
||||
)}
|
||||
))}
|
||||
|
||||
{/* ---- Davom etish tugmasi ---- */}
|
||||
<TouchableOpacity
|
||||
@@ -634,101 +600,103 @@ export default function RegisterFormScreen() {
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
</KeyboardAwareScrollView>
|
||||
|
||||
{/* ---- Country BottomSheet ---- */}
|
||||
<BottomSheet
|
||||
ref={countrySheetRef}
|
||||
index={-1}
|
||||
snapPoints={snapPoints}
|
||||
enablePanDownToClose={true}
|
||||
enableDynamicSizing={false}
|
||||
enableOverDrag={false}
|
||||
backdropComponent={renderBackdrop}
|
||||
backgroundStyle={styles.bottomSheetBg}
|
||||
handleIndicatorStyle={styles.handleIndicator}
|
||||
android_keyboardInputMode="adjustResize"
|
||||
keyboardBehavior="interactive"
|
||||
keyboardBlurBehavior="restore"
|
||||
>
|
||||
<View style={styles.sheetHeader}>
|
||||
<Text style={styles.sheetTitle}>{t('Davlat')}</Text>
|
||||
</View>
|
||||
{/* ✅ BottomSheet KeyboardAwareScrollView TASHQARISIDA */}
|
||||
<BottomSheet
|
||||
ref={countrySheetRef}
|
||||
index={-1}
|
||||
snapPoints={snapPoints}
|
||||
enablePanDownToClose={true}
|
||||
enableDynamicSizing={false}
|
||||
enableOverDrag={false}
|
||||
backdropComponent={renderBackdrop}
|
||||
backgroundStyle={styles.bottomSheetBg}
|
||||
handleIndicatorStyle={styles.handleIndicator}
|
||||
android_keyboardInputMode="adjustResize"
|
||||
keyboardBehavior="interactive"
|
||||
keyboardBlurBehavior="restore"
|
||||
>
|
||||
<View style={styles.sheetHeader}>
|
||||
<Text style={styles.sheetTitle}>{t('Davlat')}</Text>
|
||||
</View>
|
||||
|
||||
{/* Search input */}
|
||||
<View style={styles.searchContainer}>
|
||||
<Search size={16} color="#94a3b8" />
|
||||
<BottomSheetTextInput
|
||||
value={countrySearch}
|
||||
onChangeText={setCountrySearch}
|
||||
placeholder={t('Qidirish...')}
|
||||
placeholderTextColor="#94a3b8"
|
||||
style={styles.searchInput}
|
||||
clearButtonMode="while-editing"
|
||||
autoCorrect={false}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<BottomSheetFlatList
|
||||
data={filteredCountries}
|
||||
keyExtractor={(item: any) => item.id?.toString()}
|
||||
contentContainerStyle={styles.listContainer}
|
||||
showsVerticalScrollIndicator={false}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
ListEmptyComponent={
|
||||
<View style={styles.emptyList}>
|
||||
<Text style={styles.emptyListText}>{t('Natija topilmadi')}</Text>
|
||||
</View>
|
||||
}
|
||||
renderItem={({ item }: { item: any }) => {
|
||||
const isSelected = item.flag?.toUpperCase() === selectedCountry;
|
||||
const flagCode = item.flag ? item.flag.toLowerCase() : '';
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.listItem,
|
||||
isSelected && styles.selectedListItem,
|
||||
]}
|
||||
onPress={() => {
|
||||
setSelectedCountry(item.flag?.toUpperCase());
|
||||
closeCountrySheet();
|
||||
}}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 10, flex: 1 }}>
|
||||
{flagCode ? (
|
||||
<Image
|
||||
source={{ uri: `https://flagcdn.com/w320/${flagCode}.png` }}
|
||||
style={{ width: 34, height: 22, borderRadius: 2 }}
|
||||
/>
|
||||
) : (
|
||||
<Globe size={20} color={isSelected ? '#2563eb' : '#94a3b8'} />
|
||||
)}
|
||||
<Text style={[styles.listItemText, isSelected && styles.selectedListItemText]}>
|
||||
{item.name}
|
||||
</Text>
|
||||
</View>
|
||||
{isSelected && (
|
||||
<View style={styles.checkmark}>
|
||||
<CheckIcon color="#3b82f6" strokeWidth={2.5} size={16} />
|
||||
</View>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}}
|
||||
<View style={styles.searchContainer}>
|
||||
<Search size={16} color="#94a3b8" />
|
||||
<BottomSheetTextInput
|
||||
value={countrySearch}
|
||||
onChangeText={setCountrySearch}
|
||||
placeholder={t('Qidirish...')}
|
||||
placeholderTextColor="#94a3b8"
|
||||
style={styles.searchInput}
|
||||
clearButtonMode="while-editing"
|
||||
autoCorrect={false}
|
||||
/>
|
||||
</BottomSheet>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<BottomSheetFlatList
|
||||
data={filteredCountries}
|
||||
keyExtractor={(item: any) => item.id?.toString()}
|
||||
contentContainerStyle={styles.listContainer}
|
||||
showsVerticalScrollIndicator={false}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
ListEmptyComponent={
|
||||
<View style={styles.emptyList}>
|
||||
<Text style={styles.emptyListText}>{t('Natija topilmadi')}</Text>
|
||||
</View>
|
||||
}
|
||||
renderItem={({ item }: { item: any }) => {
|
||||
const isSelected = item.flag?.toUpperCase() === selectedCountry;
|
||||
const flagCode = item.flag ? item.flag.toLowerCase() : '';
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[styles.listItem, isSelected && styles.selectedListItem]}
|
||||
onPress={() => {
|
||||
setSelectedCountry(item.flag?.toUpperCase());
|
||||
closeCountrySheet();
|
||||
}}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 10, flex: 1 }}>
|
||||
{flagCode ? (
|
||||
<Image
|
||||
source={{ uri: `https://flagcdn.com/w320/${flagCode}.png` }}
|
||||
style={{ width: 34, height: 22, borderRadius: 2 }}
|
||||
/>
|
||||
) : (
|
||||
<Globe size={20} color={isSelected ? '#2563eb' : '#94a3b8'} />
|
||||
)}
|
||||
<Text style={[styles.listItemText, isSelected && styles.selectedListItemText]}>
|
||||
{item.name}
|
||||
</Text>
|
||||
</View>
|
||||
{isSelected && (
|
||||
<View style={styles.checkmark}>
|
||||
<CheckIcon color="#3b82f6" strokeWidth={2.5} size={16} />
|
||||
</View>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</BottomSheet>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
// ✅ KeyboardAwareScrollView uchun style
|
||||
keyboardScroll: {
|
||||
flex: 1,
|
||||
backgroundColor: '#0f172a',
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#0f172a',
|
||||
minHeight: '100%',
|
||||
},
|
||||
scrollContent: {
|
||||
flexGrow: 1,
|
||||
@@ -958,15 +926,6 @@ const styles = StyleSheet.create({
|
||||
fontWeight: '800' as const,
|
||||
fontSize: 16,
|
||||
},
|
||||
footer: {
|
||||
marginTop: 20,
|
||||
alignItems: 'center',
|
||||
},
|
||||
footerText: {
|
||||
color: '#94a3b8',
|
||||
fontSize: 14,
|
||||
fontWeight: '600' as const,
|
||||
},
|
||||
decorCircle1: {
|
||||
position: 'absolute',
|
||||
top: -150,
|
||||
@@ -986,7 +945,7 @@ const styles = StyleSheet.create({
|
||||
backgroundColor: 'rgba(16, 185, 129, 0.08)',
|
||||
},
|
||||
inputDisabled: {
|
||||
backgroundColor: '#f1f5f9', // disabled bo'lganda fon rangi
|
||||
borderColor: '#e2e8f0', // disabled bo'lganda border rangi
|
||||
backgroundColor: '#f1f5f9',
|
||||
borderColor: '#e2e8f0',
|
||||
},
|
||||
});
|
||||
@@ -7,18 +7,19 @@ import React, { useCallback, useRef, useState } from 'react';
|
||||
import {
|
||||
Alert,
|
||||
Image,
|
||||
KeyboardAvoidingView,
|
||||
Keyboard,
|
||||
Linking,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
TouchableWithoutFeedback,
|
||||
View
|
||||
} from 'react-native';
|
||||
|
||||
import OneClick from '@/assets/images/one_click.png';
|
||||
import PAYME from '@/assets/images/Payme_NEW.png';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { KeyboardAwareScrollView, } from 'react-native-keyboard-aware-scroll-view';
|
||||
import { price_calculation } from '../lib/api';
|
||||
import { CreateAdsResponse } from '../lib/types';
|
||||
import StepFour from './StepFour';
|
||||
@@ -185,13 +186,16 @@ export default function CreateAdsScreens() {
|
||||
onSuccess: async (res, variables) => {
|
||||
if (variables.paymentType === 'payme') {
|
||||
await Linking.openURL(res.data.url);
|
||||
router.push('/(dashboard)/announcements');
|
||||
bottomSheetModalRef.current?.dismiss();
|
||||
router.push('/profile/my-ads');
|
||||
} else {
|
||||
router.push('/(dashboard)/announcements');
|
||||
bottomSheetModalRef.current?.dismiss();
|
||||
router.push('/profile/my-ads')
|
||||
}
|
||||
},
|
||||
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);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -206,78 +210,83 @@ export default function CreateAdsScreens() {
|
||||
};
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
behavior="padding"
|
||||
<KeyboardAwareScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
style={[styles.safeArea, isDark ? styles.darkBg : styles.lightBg]}
|
||||
>
|
||||
<ScrollView contentContainerStyle={[styles.container, { paddingBottom: 90 }]}>
|
||||
<Image
|
||||
source={OneClick}
|
||||
style={{ width: 180, height: 56, marginBottom: 10 }}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
<Text style={[styles.title, isDark ? styles.darkText : styles.lightText]}>
|
||||
{t("Bir Zumda Jonatish")}
|
||||
</Text>
|
||||
|
||||
{currentStep === 1 && (
|
||||
<StepOne ref={stepOneRef} formData={formData} updateForm={updateForm} />
|
||||
)}
|
||||
{currentStep === 2 && (
|
||||
<StepTwo ref={stepTwoRef} formData={formData} updateForm={updateForm} />
|
||||
)}
|
||||
{currentStep === 3 && (
|
||||
<StepThree
|
||||
ref={stepThreeRef}
|
||||
formData={formData}
|
||||
updateForm={updateForm}
|
||||
data={data?.data}
|
||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||
<View
|
||||
style={[styles.container, { marginBottom: Keyboard.isVisible() ? 10 : 90 }]}
|
||||
onStartShouldSetResponder={() => {
|
||||
Keyboard.dismiss();
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
source={OneClick}
|
||||
style={{ width: 180, height: 56, marginBottom: 10 }}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
)}
|
||||
{currentStep === 4 && <StepFour data={ads} setPayment={setPaymentType} />}
|
||||
<View style={styles.footer}>
|
||||
{currentStep > 1 && currentStep !== 4 && (
|
||||
<Text style={[styles.title, isDark ? styles.darkText : styles.lightText]}>
|
||||
{t("Bir Zumda Jonatish")}
|
||||
</Text>
|
||||
|
||||
{currentStep === 1 && (
|
||||
<StepOne ref={stepOneRef} formData={formData} updateForm={updateForm} />
|
||||
)}
|
||||
{currentStep === 2 && (
|
||||
<StepTwo ref={stepTwoRef} formData={formData} updateForm={updateForm} />
|
||||
)}
|
||||
{currentStep === 3 && (
|
||||
<StepThree
|
||||
ref={stepThreeRef}
|
||||
formData={formData}
|
||||
updateForm={updateForm}
|
||||
data={data?.data}
|
||||
/>
|
||||
)}
|
||||
{currentStep === 4 && <StepFour data={ads} setPayment={setPaymentType} />}
|
||||
<View style={styles.footer}>
|
||||
{currentStep > 1 && currentStep !== 4 && (
|
||||
<TouchableOpacity
|
||||
style={[styles.back, isDark ? styles.darkBack : styles.lightBack]}
|
||||
onPress={() => setCurrentStep((s) => s - 1)}
|
||||
>
|
||||
<Text style={[styles.btnText, isDark ? styles.darkBtnText : styles.lightBtnText]}>
|
||||
{t('Orqaga')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
<TouchableOpacity
|
||||
style={[styles.back, isDark ? styles.darkBack : styles.lightBack]}
|
||||
onPress={() => setCurrentStep((s) => s - 1)}
|
||||
style={styles.next}
|
||||
disabled={isPending}
|
||||
onPress={() => {
|
||||
let isValid = true;
|
||||
|
||||
if (currentStep === 1) isValid = stepOneRef.current?.validate() ?? false;
|
||||
if (currentStep === 2) isValid = stepTwoRef.current?.validate() ?? false;
|
||||
if (currentStep === 3) isValid = stepThreeRef.current?.validate() ?? false;
|
||||
|
||||
if (!isValid) return;
|
||||
|
||||
if (currentStep < 3) setCurrentStep((s) => s + 1);
|
||||
if (currentStep === 3) handleSubmit();
|
||||
if (currentStep === 4) handlePresentModalPress();
|
||||
}}
|
||||
>
|
||||
<Text style={[styles.btnText, isDark ? styles.darkBtnText : styles.lightBtnText]}>
|
||||
{t('Orqaga')}
|
||||
<Text style={styles.btnText}>
|
||||
{currentStep === 3
|
||||
? t('Yaratish')
|
||||
: currentStep === 4
|
||||
? t("To'lash")
|
||||
: t('Keyingisi')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.next}
|
||||
disabled={isPending}
|
||||
onPress={() => {
|
||||
let isValid = true;
|
||||
|
||||
if (currentStep === 1) isValid = stepOneRef.current?.validate() ?? false;
|
||||
if (currentStep === 2) isValid = stepTwoRef.current?.validate() ?? false;
|
||||
if (currentStep === 3) isValid = stepThreeRef.current?.validate() ?? false;
|
||||
|
||||
if (!isValid) return;
|
||||
|
||||
if (currentStep < 3) setCurrentStep((s) => s + 1);
|
||||
if (currentStep === 3) handleSubmit();
|
||||
if (currentStep === 4) handlePresentModalPress();
|
||||
}}
|
||||
>
|
||||
<Text style={styles.btnText}>
|
||||
{currentStep === 3
|
||||
? t('Yaratish')
|
||||
: currentStep === 4
|
||||
? t("To'lash")
|
||||
: t('Keyingisi')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
{/* FOOTER */}
|
||||
|
||||
{/* PAYMENT BOTTOM SHEET */}
|
||||
</TouchableWithoutFeedback>
|
||||
<BottomSheetModal
|
||||
ref={bottomSheetModalRef}
|
||||
index={0}
|
||||
@@ -321,7 +330,7 @@ export default function CreateAdsScreens() {
|
||||
</View>
|
||||
</BottomSheetScrollView>
|
||||
</BottomSheetModal>
|
||||
</KeyboardAvoidingView>
|
||||
</KeyboardAwareScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -234,10 +234,10 @@ const StepThree = forwardRef(({ formData, updateForm, data }: StepProps, ref) =>
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Text style={[styles.sectionTitle, { color: theme.text }]}>
|
||||
{t('Reklama joylashtirish kompaniyasi')}
|
||||
</Text>
|
||||
<Text style={[styles.sectionTitle, { color: theme.text }]}>
|
||||
{t('Reklama joylashtirish kompaniyasi')}
|
||||
</Text>
|
||||
<View style={{ flexDirection: 'column', justifyContent: 'space-between', alignItems: 'flex-end', marginBottom: 10 }}>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.selectAllButton,
|
||||
@@ -330,7 +330,7 @@ const styles = StyleSheet.create({
|
||||
marginBottom: 12,
|
||||
},
|
||||
pickerText: { fontSize: 16 },
|
||||
sectionTitle: { fontSize: 16, fontWeight: '700', marginVertical: 12 },
|
||||
sectionTitle: { fontSize: 16, fontWeight: '700', marginVertical: 12, },
|
||||
companyItem: {
|
||||
width: 55,
|
||||
height: 55,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,42 +1,57 @@
|
||||
import Express_diagnistika from '@/assets/images/Express_diagnistika.png';
|
||||
import { useTheme } from '@/components/ThemeContext';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { Image } from 'expo-image';
|
||||
import { router } from 'expo-router';
|
||||
import { ChevronLeft, XIcon } from 'lucide-react-native';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { ActivityIndicator, FlatList, Modal, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { RefreshControl } from 'react-native-gesture-handler';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { WebView } from 'react-native-webview';
|
||||
import { eservices_api } from '../lib/api';
|
||||
import Express_diagnistika from "@/assets/images/Express_diagnistika.png";
|
||||
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,
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from "react-native";
|
||||
import { RefreshControl } from "react-native-gesture-handler";
|
||||
import { Toast } from "toastify-react-native";
|
||||
import { eservices_api } from "../lib/api";
|
||||
|
||||
const dark = {
|
||||
bg: '#0f172a',
|
||||
card: '#334155',
|
||||
border: '#1e293b',
|
||||
muted: '#334155',
|
||||
text: '#E5B037',
|
||||
subText: '#0B0F2C',
|
||||
bg: "#0f172a",
|
||||
card: "#334155",
|
||||
border: "#1e293b",
|
||||
muted: "#334155",
|
||||
text: "#E5B037",
|
||||
subText: "#0B0F2C",
|
||||
};
|
||||
|
||||
export default function EServicesCategoryScreen() {
|
||||
const { isDark } = useTheme();
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const webviewRef = useRef<WebView>(null);
|
||||
const [webUrl, setWebUrl] = React.useState<string | null>(null);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ['goverment_category'],
|
||||
queryKey: ["goverment_category"],
|
||||
queryFn: () => eservices_api.category(),
|
||||
});
|
||||
|
||||
const handleOpenBrowser = async (fileUrl: string) => {
|
||||
try {
|
||||
await WebBrowser.openBrowserAsync(fileUrl, {
|
||||
dismissButtonStyle: 'close',
|
||||
presentationStyle: WebBrowser.WebBrowserPresentationStyle.FULL_SCREEN,
|
||||
});
|
||||
} catch (error) {
|
||||
Toast.error(t("Xatolik yuz berdi"));
|
||||
}
|
||||
}
|
||||
|
||||
const onRefresh = async () => {
|
||||
setRefreshing(true);
|
||||
try {
|
||||
await queryClient.refetchQueries({ queryKey: ['goverment_category'] });
|
||||
await queryClient.refetchQueries({ queryKey: ["goverment_category"] });
|
||||
} finally {
|
||||
setRefreshing(false);
|
||||
}
|
||||
@@ -52,22 +67,21 @@ export default function EServicesCategoryScreen() {
|
||||
|
||||
const staticCategory = {
|
||||
id: 0,
|
||||
name: 'Express Diagnostika',
|
||||
name: "Express Diagnostika",
|
||||
image: Express_diagnistika,
|
||||
url: 'https://myorg.uz/ru',
|
||||
url: "https://myorg.uz/ru",
|
||||
};
|
||||
|
||||
const categories = [staticCategory, ...(data?.data?.data?.results ?? [])];
|
||||
|
||||
const handlePress = (item: any) => {
|
||||
if (item.id === 0 && item.url) {
|
||||
setWebUrl(item.url);
|
||||
setModalVisible(true);
|
||||
handleOpenBrowser(item.url);
|
||||
return;
|
||||
}
|
||||
|
||||
router.push({
|
||||
pathname: '/(dashboard)/e-service/e-services-category',
|
||||
pathname: "/(dashboard)/e-service/e-services-category",
|
||||
params: {
|
||||
categoryId: item.id,
|
||||
categoryName: item.name,
|
||||
@@ -79,7 +93,7 @@ export default function EServicesCategoryScreen() {
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: isDark ? dark.bg : '#f8fafc',
|
||||
backgroundColor: isDark ? dark.bg : "#f8fafc",
|
||||
padding: 16,
|
||||
}}
|
||||
>
|
||||
@@ -91,8 +105,8 @@ export default function EServicesCategoryScreen() {
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
tintColor={isDark ? '#f8fafc' : '#020617'}
|
||||
colors={['#3b82f6']}
|
||||
tintColor={isDark ? "#f8fafc" : "#020617"}
|
||||
colors={["#3b82f6"]}
|
||||
/>
|
||||
}
|
||||
renderItem={({ item }) => (
|
||||
@@ -101,13 +115,13 @@ export default function EServicesCategoryScreen() {
|
||||
onPress={() => handlePress(item)}
|
||||
style={{
|
||||
marginHorizontal: 1,
|
||||
backgroundColor: isDark ? '#FDFDFD' : '#ffffff',
|
||||
backgroundColor: isDark ? "#FDFDFD" : "#ffffff",
|
||||
borderRadius: 16,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
borderWidth: isDark ? 1 : 0,
|
||||
borderColor: isDark ? dark.border : 'transparent',
|
||||
shadowColor: '#000',
|
||||
borderColor: isDark ? dark.border : "transparent",
|
||||
shadowColor: "#000",
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.15,
|
||||
shadowRadius: 4,
|
||||
@@ -116,98 +130,31 @@ export default function EServicesCategoryScreen() {
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: '100%',
|
||||
width: "100%",
|
||||
height: 100,
|
||||
borderRadius: 12,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
marginRight: 14,
|
||||
padding: 2,
|
||||
overflow: 'hidden',
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{item.image ? (
|
||||
<Image
|
||||
source={item.image}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
contentFit="contain"
|
||||
/>
|
||||
) : (
|
||||
<Text style={{ fontWeight: '700', color: dark.text }}>{item.name[0]}</Text>
|
||||
<Text style={{ fontWeight: "700", color: dark.text }}>
|
||||
{item.name[0]}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
/>
|
||||
<Modal visible={modalVisible} animationType="slide">
|
||||
<SafeAreaView
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: isDark ? '#0f172a' : '#f8fafc',
|
||||
}}
|
||||
>
|
||||
{/* WebView Header */}
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 12,
|
||||
backgroundColor: isDark ? '#1e293b' : '#f8fafc',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: isDark ? '#334155' : '#e2e8f0',
|
||||
}}
|
||||
>
|
||||
<TouchableOpacity
|
||||
onPress={() => webviewRef.current?.goBack()}
|
||||
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
||||
>
|
||||
<ChevronLeft size={28} color={isDark ? '#f1f5f9' : '#0f172a'} />
|
||||
</TouchableOpacity>
|
||||
|
||||
<Text
|
||||
style={{
|
||||
flex: 1,
|
||||
textAlign: 'center',
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
color: isDark ? '#f1f5f9' : '#0f172a',
|
||||
}}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{webUrl}
|
||||
</Text>
|
||||
|
||||
<TouchableOpacity
|
||||
onPress={() => setModalVisible(false)}
|
||||
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
||||
>
|
||||
<XIcon size={28} color={isDark ? '#f1f5f9' : '#0f172a'} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* WebView */}
|
||||
{webUrl && (
|
||||
<WebView
|
||||
ref={webviewRef}
|
||||
originWhitelist={['*']}
|
||||
javaScriptEnabled
|
||||
domStorageEnabled
|
||||
onShouldStartLoadWithRequest={(request) => {
|
||||
// iOS va Android uchun barcha URLlarni WebView ichida ochish
|
||||
return true;
|
||||
}}
|
||||
source={{ uri: webUrl }}
|
||||
style={{ flex: 1, backgroundColor: isDark ? '#0f172a' : '#f8fafc' }}
|
||||
startInLoadingState
|
||||
renderLoading={() => (
|
||||
<ActivityIndicator color="#3b82f6" size="large" style={{ flex: 1 }} />
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</SafeAreaView>
|
||||
</Modal>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,28 +1,26 @@
|
||||
// EServicesScreen.tsx
|
||||
import { useTheme } from '@/components/ThemeContext';
|
||||
import { useInfiniteQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { Image } from 'expo-image';
|
||||
import { router, useLocalSearchParams } from 'expo-router';
|
||||
import { ChevronLeft, XIcon } from 'lucide-react-native';
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
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 {
|
||||
ActivityIndicator,
|
||||
Dimensions,
|
||||
FlatList,
|
||||
Modal,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { WebView } from 'react-native-webview';
|
||||
View
|
||||
} from "react-native";
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { RefreshControl } from 'react-native-gesture-handler';
|
||||
import { eservices_api } from '../lib/api';
|
||||
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');
|
||||
const { width: SCREEN_WIDTH } = Dimensions.get("window");
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
export interface GovermentServiceDataRes {
|
||||
@@ -34,78 +32,99 @@ export interface GovermentServiceDataRes {
|
||||
|
||||
export default function EServicesScreen() {
|
||||
const { isDark } = useTheme();
|
||||
const [webUrl, setWebUrl] = useState<string | null>(null);
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const webviewRef = useRef<WebView>(null);
|
||||
const params = useLocalSearchParams();
|
||||
const { t } = useTranslation();
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data, isLoading, isError, fetchNextPage, hasNextPage, isFetchingNextPage } =
|
||||
useInfiniteQuery({
|
||||
queryKey: ['goverment_service', params.categoryId],
|
||||
queryFn: async ({ pageParam = 1 }) => {
|
||||
const response = await eservices_api.list({
|
||||
page: pageParam,
|
||||
page_size: PAGE_SIZE,
|
||||
category: Number(params.categoryId),
|
||||
});
|
||||
return response.data.data;
|
||||
},
|
||||
getNextPageParam: (lastPage) =>
|
||||
lastPage.current_page < lastPage.total_pages ? lastPage.current_page + 1 : undefined,
|
||||
initialPageParam: 1,
|
||||
});
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
isError,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
} = useInfiniteQuery({
|
||||
queryKey: ["goverment_service", params.categoryId],
|
||||
queryFn: async ({ pageParam = 1 }) => {
|
||||
const response = await eservices_api.list({
|
||||
page: pageParam,
|
||||
page_size: PAGE_SIZE,
|
||||
category: Number(params.categoryId),
|
||||
});
|
||||
return response.data.data;
|
||||
},
|
||||
getNextPageParam: (lastPage) =>
|
||||
lastPage.current_page < lastPage.total_pages
|
||||
? lastPage.current_page + 1
|
||||
: undefined,
|
||||
initialPageParam: 1,
|
||||
});
|
||||
|
||||
const services: GovermentServiceDataRes[] = data?.pages.flatMap((p) => p.results) ?? [];
|
||||
const services: GovermentServiceDataRes[] =
|
||||
data?.pages.flatMap((p) => p.results) ?? [];
|
||||
|
||||
const onRefresh = async () => {
|
||||
setRefreshing(true);
|
||||
try {
|
||||
await queryClient.refetchQueries({ queryKey: ['goverment_service'] });
|
||||
await queryClient.refetchQueries({ queryKey: ["goverment_service"] });
|
||||
} finally {
|
||||
setRefreshing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const openWebView = (url: string) => {
|
||||
setWebUrl(url);
|
||||
setModalVisible(true);
|
||||
const handleOpenBrowser = async (fileUrl: string) => {
|
||||
try {
|
||||
await WebBrowser.openBrowserAsync(fileUrl);
|
||||
} catch (error) {
|
||||
Toast.error(t("Xatolik yuz berdi"));
|
||||
}
|
||||
};
|
||||
|
||||
const renderItem = useCallback(
|
||||
({ item }: { item: GovermentServiceDataRes }) => (
|
||||
<View style={{ alignItems: 'center', marginTop: 12, width: CARD_WIDTH }}>
|
||||
{/* Logo (bosilganda WebView ochiladi) */}
|
||||
<View style={{ alignItems: "center", marginTop: 12, width: CARD_WIDTH }}>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.card,
|
||||
{ backgroundColor: isDark ? '#1e293b' : '#f8fafc' },
|
||||
{ backgroundColor: isDark ? "#1e293b" : "#f8fafc" },
|
||||
isDark ? styles.darkShadow : styles.lightShadow,
|
||||
]}
|
||||
onPress={() => openWebView(item.url)}
|
||||
onPress={() => handleOpenBrowser(item.url)}
|
||||
>
|
||||
<Image source={{ uri: item.logo }} style={styles.logo} resizeMode="contain" />
|
||||
<Image
|
||||
source={{ uri: item.logo }}
|
||||
style={styles.logo}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Name (alog‘ifa, faqat ko‘rsatish) */}
|
||||
<Text
|
||||
style={[
|
||||
styles.name,
|
||||
{ color: isDark ? '#f1f5f9' : '#0f172a', marginTop: 4, paddingHorizontal: 5 },
|
||||
{
|
||||
color: isDark ? "#f1f5f9" : "#0f172a",
|
||||
marginTop: 4,
|
||||
paddingHorizontal: 5,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{item.name}
|
||||
</Text>
|
||||
</View>
|
||||
),
|
||||
[isDark]
|
||||
[isDark],
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<View style={[styles.center, { backgroundColor: isDark ? '#0f172a' : '#f8fafc' }]}>
|
||||
<View
|
||||
style={[
|
||||
styles.center,
|
||||
{ backgroundColor: isDark ? "#0f172a" : "#f8fafc" },
|
||||
]}
|
||||
>
|
||||
<ActivityIndicator size="large" color="#3b82f6" />
|
||||
</View>
|
||||
);
|
||||
@@ -113,40 +132,45 @@ export default function EServicesScreen() {
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<View style={[styles.center, { backgroundColor: isDark ? '#0f172a' : '#f8fafc' }]}>
|
||||
<Text style={{ color: 'red' }}>Xatolik yuz berdi</Text>
|
||||
<View
|
||||
style={[
|
||||
styles.center,
|
||||
{ backgroundColor: isDark ? "#0f172a" : "#f8fafc" },
|
||||
]}
|
||||
>
|
||||
<Text style={{ color: "red" }}>Xatolik yuz berdi</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, backgroundColor: isDark ? '#0f172a' : '#f8fafc' }}>
|
||||
<View style={{ flex: 1, backgroundColor: isDark ? "#0f172a" : "#f8fafc" }}>
|
||||
{/* Header */}
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 16,
|
||||
gap: 10,
|
||||
paddingVertical: 12,
|
||||
backgroundColor: isDark ? '#1e293b' : '#f8fafc',
|
||||
backgroundColor: isDark ? "#1e293b" : "#f8fafc",
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: isDark ? '#334155' : '#e2e8f0',
|
||||
borderBottomColor: isDark ? "#334155" : "#e2e8f0",
|
||||
}}
|
||||
>
|
||||
<TouchableOpacity
|
||||
onPress={() => router.back()}
|
||||
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
||||
>
|
||||
<ChevronLeft size={28} color={isDark ? '#f1f5f9' : '#0f172a'} />
|
||||
<ChevronLeft size={28} color={isDark ? "#f1f5f9" : "#0f172a"} />
|
||||
</TouchableOpacity>
|
||||
|
||||
<Text
|
||||
style={{
|
||||
flex: 1,
|
||||
fontSize: 18,
|
||||
fontWeight: '600',
|
||||
color: isDark ? '#f1f5f9' : '#0f172a',
|
||||
fontWeight: "600",
|
||||
color: isDark ? "#f1f5f9" : "#0f172a",
|
||||
}}
|
||||
>
|
||||
{params.categoryName}
|
||||
@@ -158,18 +182,18 @@ export default function EServicesScreen() {
|
||||
<View style={[styles.center, { flex: 1, padding: 16, gap: 5 }]}>
|
||||
<Text
|
||||
style={{
|
||||
color: isDark ? '#f1f5f9' : '#0f172a',
|
||||
color: isDark ? "#f1f5f9" : "#0f172a",
|
||||
fontSize: 18,
|
||||
textAlign: 'center',
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
{t("Bu kategoriya bo'yicha xizmat topilmadi")}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: isDark ? '#f1f5f9' : '#0f172a',
|
||||
color: isDark ? "#f1f5f9" : "#0f172a",
|
||||
fontSize: 16,
|
||||
textAlign: 'center',
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
{t("Tez orada xizmat qo'shiladi")}
|
||||
@@ -182,96 +206,34 @@ export default function EServicesScreen() {
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
tintColor={isDark ? '#f8fafc' : '#020617'}
|
||||
colors={['#3b82f6']}
|
||||
tintColor={isDark ? "#f8fafc" : "#020617"}
|
||||
colors={["#3b82f6"]}
|
||||
/>
|
||||
}
|
||||
keyExtractor={(item) => item.id.toString()}
|
||||
renderItem={renderItem}
|
||||
numColumns={3}
|
||||
columnWrapperStyle={{ justifyContent: 'space-between', marginBottom: 12 }}
|
||||
columnWrapperStyle={{
|
||||
justifyContent: "space-between",
|
||||
marginBottom: 12,
|
||||
}}
|
||||
contentContainerStyle={{ padding: 16, paddingBottom: 80 }}
|
||||
onEndReached={() => hasNextPage && !isFetchingNextPage && fetchNextPage()}
|
||||
onEndReached={() =>
|
||||
hasNextPage && !isFetchingNextPage && fetchNextPage()
|
||||
}
|
||||
onEndReachedThreshold={0.4}
|
||||
ListFooterComponent={
|
||||
isFetchingNextPage ? (
|
||||
<ActivityIndicator color="#3b82f6" size="large" style={{ marginVertical: 20 }} />
|
||||
<ActivityIndicator
|
||||
color="#3b82f6"
|
||||
size="large"
|
||||
style={{ marginVertical: 20 }}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
showsVerticalScrollIndicator={false}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* WebView Modal */}
|
||||
<Modal visible={modalVisible} animationType="slide">
|
||||
<SafeAreaView
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: isDark ? '#0f172a' : '#f8fafc',
|
||||
}}
|
||||
>
|
||||
{/* WebView Header */}
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 12,
|
||||
backgroundColor: isDark ? '#1e293b' : '#f8fafc',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: isDark ? '#334155' : '#e2e8f0',
|
||||
}}
|
||||
>
|
||||
<TouchableOpacity
|
||||
onPress={() => webviewRef.current?.goBack()}
|
||||
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
||||
>
|
||||
<ChevronLeft size={28} color={isDark ? '#f1f5f9' : '#0f172a'} />
|
||||
</TouchableOpacity>
|
||||
|
||||
<Text
|
||||
style={{
|
||||
flex: 1,
|
||||
textAlign: 'center',
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
color: isDark ? '#f1f5f9' : '#0f172a',
|
||||
}}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{webUrl}
|
||||
</Text>
|
||||
|
||||
<TouchableOpacity
|
||||
onPress={() => setModalVisible(false)}
|
||||
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
||||
>
|
||||
<XIcon size={28} color={isDark ? '#f1f5f9' : '#0f172a'} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* WebView */}
|
||||
{webUrl && (
|
||||
<WebView
|
||||
ref={webviewRef}
|
||||
originWhitelist={['*']}
|
||||
javaScriptEnabled
|
||||
domStorageEnabled
|
||||
onShouldStartLoadWithRequest={(request) => {
|
||||
// iOS va Android uchun barcha URLlarni WebView ichida ochish
|
||||
return true;
|
||||
}}
|
||||
source={{ uri: webUrl }}
|
||||
style={{ flex: 1, backgroundColor: isDark ? '#0f172a' : '#f8fafc' }}
|
||||
startInLoadingState
|
||||
renderLoading={() => (
|
||||
<ActivityIndicator color="#3b82f6" size="large" style={{ flex: 1 }} />
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</SafeAreaView>
|
||||
</Modal>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -279,12 +241,12 @@ export default function EServicesScreen() {
|
||||
const CARD_WIDTH = (SCREEN_WIDTH - 50) / 3;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
center: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
||||
center: { flex: 1, justifyContent: "center", alignItems: "center" },
|
||||
card: {
|
||||
width: CARD_WIDTH,
|
||||
borderRadius: 12,
|
||||
padding: 12,
|
||||
alignItems: 'center',
|
||||
alignItems: "center",
|
||||
},
|
||||
logo: {
|
||||
width: 80,
|
||||
@@ -293,18 +255,18 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
name: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
textAlign: 'center',
|
||||
fontWeight: "600",
|
||||
textAlign: "center",
|
||||
},
|
||||
darkShadow: {
|
||||
shadowColor: '#000',
|
||||
shadowColor: "#000",
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.4,
|
||||
shadowRadius: 6,
|
||||
elevation: 3,
|
||||
},
|
||||
lightShadow: {
|
||||
shadowColor: '#000',
|
||||
shadowColor: "#000",
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 4,
|
||||
|
||||
@@ -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<AxiosResponse<{ status: boolean; data: MyAdsDataRes }>> {
|
||||
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<AxiosResponse<{ status: boolean; data: MyAdsDataRes }>> {
|
||||
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;
|
||||
|
||||
@@ -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() {
|
||||
<Pressable onPress={() => router.push('/profile/employees')}>
|
||||
<ArrowLeft color={theme.text} />
|
||||
</Pressable>
|
||||
<Text style={[styles.headerTitle, { color: theme.text }]}>{t("Yangi xodim qo'shish")}</Text>
|
||||
<Text style={[styles.headerTitle, { color: theme.text }]}>{t("Xodim qo'shish")}</Text>
|
||||
<Pressable onPress={handleSave} disabled={isPending}>
|
||||
{isPending ? (
|
||||
<ActivityIndicator size={'small'} />
|
||||
) : (
|
||||
<Text style={[styles.saveButton, { color: theme.primary }]}>{t('Saqlash')}</Text>
|
||||
<Check size={26} color={theme.primary} />
|
||||
)}
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
@@ -52,7 +52,7 @@ export function AnnouncementsTab() {
|
||||
};
|
||||
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [selectedAnnouncement, setSelectedAnnouncement] = useState<MyAdsDataRes | null>(null);
|
||||
const [selectedAnnouncement, setSelectedAnnouncement] = useState<number | null>(null);
|
||||
const [sheetOpen, setSheetOpen] = useState(false); const bottomSheetModalRef = useRef<BottomSheetModal>(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 (
|
||||
<View style={[styles.center, { backgroundColor: theme.background }]}>
|
||||
<Text style={[styles.error, { color: theme.error }]}>{t('Xatolik yuz berdi')}</Text>
|
||||
<Text style={[{ color: theme.error }]}>{t('Xatolik yuz berdi')}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -196,7 +196,7 @@ export function AnnouncementsTab() {
|
||||
<FlatList
|
||||
data={allAds}
|
||||
keyExtractor={(item) => item.id.toString()}
|
||||
contentContainerStyle={styles.list}
|
||||
contentContainerStyle={[styles.list, { flexGrow: 1 }]}
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor={theme.primary} />
|
||||
}
|
||||
@@ -204,7 +204,7 @@ export function AnnouncementsTab() {
|
||||
renderItem={({ item }) => (
|
||||
<Pressable
|
||||
style={[styles.card, { backgroundColor: theme.cardBg }]}
|
||||
onPress={() => openSheet(item)}
|
||||
onPress={() => { openSheet(item); setSelectedAnnouncement(item.id) }}
|
||||
>
|
||||
{item.files?.[0]?.file && (
|
||||
<Image source={{ uri: item.files[0].file }} style={styles.cardImage} />
|
||||
@@ -243,6 +243,14 @@ export function AnnouncementsTab() {
|
||||
</View>
|
||||
</Pressable>
|
||||
)}
|
||||
ListEmptyComponent={() => (
|
||||
<View style={styles.emptyContainer}>
|
||||
<Megaphone size={48} color={theme.textSecondary} />
|
||||
<Text style={[styles.emptyTitle, { color: theme.text }]}>
|
||||
{t('Hozircha hech qanday eʼlon mavjud emas')}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
|
||||
<BottomSheetModal
|
||||
@@ -253,14 +261,13 @@ export function AnnouncementsTab() {
|
||||
backgroundStyle={{ backgroundColor: theme.sheetBg }}
|
||||
handleIndicatorStyle={{ backgroundColor: theme.indicator }}
|
||||
onDismiss={() => {
|
||||
setSheetOpen(false);
|
||||
setSelectedAnnouncement(null);
|
||||
setSelectedAnnouncement(null); // shu yetarli
|
||||
}}
|
||||
>
|
||||
<BottomSheetScrollView contentContainerStyle={styles.sheet}>
|
||||
{loadingDetail && <ActivityIndicator size={'large'} />}
|
||||
{detailError && (
|
||||
<Text style={[styles.error, { color: theme.error }]}>{t('Xatolik yuz berdi')}</Text>
|
||||
<Text style={[{ color: theme.error }]}>{t('Xatolik yuz berdi')}</Text>
|
||||
)}
|
||||
|
||||
{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' })}
|
||||
>
|
||||
<Image source={PAYME} style={{ width: 80, height: 80 }} />
|
||||
</TouchableOpacity>
|
||||
@@ -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' })}
|
||||
>
|
||||
<Text style={[styles.paymentText, isDark ? styles.darkText : styles.lightText]}>
|
||||
{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',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'));
|
||||
},
|
||||
});
|
||||
|
||||
@@ -60,8 +60,6 @@ export default function CreateReferrals() {
|
||||
const validate = () => {
|
||||
const e: any = {};
|
||||
|
||||
if (!form.code || form.code.length !== 9)
|
||||
e.code = 'Kod aynan 9 ta belgidan iborat bo‘lishi kerak';
|
||||
if (!form.description || form.description.length < 5)
|
||||
e.description = 'Tavsif kamida 5 ta belgidan iborat bo‘lishi kerak';
|
||||
|
||||
|
||||
@@ -151,7 +151,7 @@ export default function EditService() {
|
||||
<ArrowLeft color={isDark ? '#fff' : '#0f172a'} />
|
||||
</Pressable>
|
||||
<Text style={[styles.headerTitle, { color: isDark ? '#fff' : '#0f172a' }]}>
|
||||
{step === 1 ? t('Xizmatni tahrirlash (1/2)') : t('Xizmatni tahrirlash (2/2)')}
|
||||
{t('Xizmatni tahrirlash')}
|
||||
</Text>
|
||||
</View>
|
||||
<Pressable
|
||||
|
||||
@@ -124,7 +124,7 @@ export function EmployeesTab() {
|
||||
data={allEmployees}
|
||||
keyExtractor={(item) => 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',
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -116,7 +116,7 @@ export default function MyServicesScreen() {
|
||||
<FlatList
|
||||
data={allServices}
|
||||
keyExtractor={(item) => item.id.toString()}
|
||||
contentContainerStyle={styles.list}
|
||||
contentContainerStyle={[styles.list, { flexGrow: 1 }]}
|
||||
onEndReached={() => hasNextPage && fetchNextPage()}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
@@ -269,7 +269,7 @@ const styles = StyleSheet.create({
|
||||
borderRadius: 12,
|
||||
},
|
||||
categoryText: { fontSize: 13, fontWeight: '500' as const },
|
||||
emptyContainer: { alignItems: 'center', justifyContent: 'center', paddingVertical: 80, gap: 16 },
|
||||
emptyContainer: { alignItems: 'center', justifyContent: 'center', paddingVertical: 80, gap: 16, flex: 1 },
|
||||
emptyText: { fontSize: 17, fontWeight: '600' as const },
|
||||
emptyButton: {
|
||||
flexDirection: 'row',
|
||||
|
||||
@@ -91,32 +91,77 @@ export function NotificationTab() {
|
||||
<Text style={[styles.headerTitle, { color: isDark ? '#f1f5f9' : '#0f172a' }]}>
|
||||
{t('Bildirishnomalar')}
|
||||
</Text>
|
||||
|
||||
{notifications.some((n) => !n.is_read) && (
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.markAllButton,
|
||||
{ backgroundColor: isDark ? '#1e293b' : '#e0f2fe', borderColor: '#3b82f6' },
|
||||
]}
|
||||
onPress={() => markAllAsRead()}
|
||||
disabled={isMarkingAllRead}
|
||||
>
|
||||
{isMarkingAllRead ? (
|
||||
<ActivityIndicator size="small" color="#3b82f6" />
|
||||
) : (
|
||||
<Text style={[styles.markAllText, { color: '#3b82f6' }]}>
|
||||
{t("Barchasi o'qildi")}
|
||||
</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
<FlatList
|
||||
data={notifications}
|
||||
keyExtractor={(item) => item.id.toString()}
|
||||
contentContainerStyle={styles.listContent}
|
||||
showsVerticalScrollIndicator={false}
|
||||
renderItem={({ item, index }) => <NotificationCard item={item} />}
|
||||
ListHeaderComponent={() => {
|
||||
if (notifications.length === 0) {
|
||||
return (
|
||||
<View style={styles.emptyHeader}>
|
||||
<Text
|
||||
style={[
|
||||
styles.emptyTitle,
|
||||
{ color: isDark ? '#f1f5f9' : '#0f172a' },
|
||||
]}
|
||||
>
|
||||
{t("Hozircha bildirishnomalar yo'q")}
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
style={[
|
||||
styles.emptyDesc,
|
||||
{ color: isDark ? '#94a3b8' : '#64748b' },
|
||||
]}
|
||||
>
|
||||
{t("Yangi xabarlar shu yerda paydo bo‘ladi")}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (notifications.some((n) => !n.is_read)) {
|
||||
return (
|
||||
<View style={styles.headerActions}>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.markAllButton,
|
||||
{
|
||||
backgroundColor: isDark ? '#1e293b' : '#e0f2fe',
|
||||
borderColor: '#3b82f6',
|
||||
},
|
||||
]}
|
||||
onPress={() => markAllAsRead()}
|
||||
disabled={isMarkingAllRead}
|
||||
>
|
||||
{isMarkingAllRead ? (
|
||||
<ActivityIndicator size="small" color="#3b82f6" />
|
||||
) : (
|
||||
<Text style={[styles.markAllText, { color: '#3b82f6' }]}>
|
||||
{t("Barchasi o'qildi")}
|
||||
</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.allReadContainer}>
|
||||
<Text
|
||||
style={[
|
||||
styles.allReadText,
|
||||
{ color: isDark ? '#94a3b8' : '#64748b' },
|
||||
]}
|
||||
>
|
||||
{t("Barcha bildirishnomalar o‘qilgan")}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}}
|
||||
renderItem={({ item }) => <NotificationCard item={item} />}
|
||||
onEndReached={() => {
|
||||
if (hasNextPage && !isFetchingNextPage) {
|
||||
fetchNextPage();
|
||||
@@ -130,6 +175,15 @@ export function NotificationTab() {
|
||||
}
|
||||
refreshing={isLoading}
|
||||
onRefresh={refetch}
|
||||
ListEmptyComponent={() =>
|
||||
!isLoading && (
|
||||
<View style={styles.emptyContainer}>
|
||||
<Text style={[styles.emptyTitle, { color: isDark ? '#f1f5f9' : '#0f172a' }]}>
|
||||
{t("Hozircha bildirishnomalar yo'q")}
|
||||
</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
@@ -68,7 +68,7 @@ export function ReferralsTab() {
|
||||
|
||||
// Clipboard + Share funksiyasi
|
||||
const handleCopyAndShare = async (code: string) => {
|
||||
const referralLink = `https://t.me/infotargetbot/join?startapp=${code}`;
|
||||
const referralLink = `${code}`;
|
||||
|
||||
// Clipboard-ga nusxa olish
|
||||
await Clipboard.setStringAsync(referralLink);
|
||||
@@ -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() {
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
ListEmptyComponent={
|
||||
<Text style={{ textAlign: 'center', color: theme.subText }}>
|
||||
{t('Refferallar topilmadi')}
|
||||
</Text>
|
||||
}
|
||||
ListEmptyComponent={() => (
|
||||
<View style={styles.emptyContainer}>
|
||||
<View
|
||||
style={[
|
||||
styles.emptyIconWrapper,
|
||||
{ backgroundColor: theme.cardBg }
|
||||
]}
|
||||
>
|
||||
<Gift size={40} color={theme.primary} />
|
||||
</View>
|
||||
|
||||
<Text style={[styles.emptyTitle, { color: theme.text }]}>
|
||||
{t("Refferallar mavjud emas")}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user