Compare commits

..

5 Commits

Author SHA1 Message Date
Samandar Turgunboyev
a34cf75c57 bug fixed 2026-03-05 11:49:56 +05:00
Samandar Turgunboyev
382b214e3e register form refferal update 2026-03-02 16:28:00 +05:00
Samandar Turgunboyev
c71651ec4b keyboard scroll 2026-03-02 15:46:12 +05:00
Samandar Turgunboyev
4d5cc84850 bug fixed complated 2026-03-02 15:14:52 +05:00
Samandar Turgunboyev
ab363ca3b9 bug fixed 2026-03-02 13:22:55 +05:00
49 changed files with 718 additions and 463 deletions

View File

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

View File

@@ -11,12 +11,16 @@
"ios": {
"supportsTablet": true,
"infoPlist": {
"UIBackgroundModes": ["remote-notification"]
"UIBackgroundModes": [
"remote-notification"
],
"UIViewControllerBasedStatusBarAppearance": true
},
"bundleIdentifier": "com.felix.infotarget"
},
"android": {
"useNextNotificationsApi": true,
"softwareKeyboardLayoutMode": "resize",
"adaptiveIcon": {
"backgroundColor": "#E6F4FE",
"foregroundImage": "./assets/images/logo.png"

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

View File

@@ -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 bolsa */}
{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',

View File

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

View File

@@ -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": "Пока нет уведомлений"
}

View File

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

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

View File

@@ -76,6 +76,7 @@
"react-native-worklets": "^0.5.1",
"react-stately": "^3.39.0",
"tailwind-variants": "^0.1.20",
"toastify-react-native": "^7.2.3",
"zustand": "^5.0.10"
},
"devDependencies": {

View File

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

View File

@@ -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,10 +129,10 @@ const ConfirmScreen = () => {
<View style={styles.decorCircle1} />
<View style={styles.decorCircle2} />
<AuthHeader />
<SafeAreaView style={{ flex: 1 }}>
<KeyboardAwareScrollView
contentContainerStyle={styles.scrollContent}
showsVerticalScrollIndicator={false}
enableOnAndroid
extraScrollHeight={120}
keyboardShouldPersistTaps="handled"
>
<View style={styles.header}>
@@ -195,7 +194,6 @@ const ConfirmScreen = () => {
</Text>
</View> */}
</KeyboardAwareScrollView>
</SafeAreaView>
</View>
);
};

View File

@@ -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: () => {

View File

@@ -25,15 +25,14 @@ 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';
@@ -109,6 +108,7 @@ export default function RegisterFormScreen() {
});
const openCountrySheet = useCallback(() => {
Keyboard.dismiss();
setTimeout(() => {
countrySheetRef.current?.snapToIndex(0);
}, 100);
@@ -161,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);
@@ -179,26 +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 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"));
setDirectorLoading(false);
return;
}
// INN mos keldi — director ma'lumotini saqlash
setDirectorInfo(directorData);
setDirectorLoading(false);
setErrorDirectorInfo(null);
@@ -325,7 +314,7 @@ export default function RegisterFormScreen() {
inn.length === 9 &&
info &&
directorJshshr.length === 14 &&
directorInfo // faqat INN mos kelganda to'ldiriladi
directorInfo
);
}
@@ -333,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();
@@ -360,8 +348,22 @@ export default function RegisterFormScreen() {
};
return (
<View style={{ flex: 1 }}>
<View style={styles.container}>
// ✅ 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 }}
@@ -373,16 +375,9 @@ export default function RegisterFormScreen() {
<AuthHeader />
{/* ✅ SafeAreaView va ichki ScrollView yo'q — to'g'ridan View */}
<SafeAreaView style={{ flex: 1 }} edges={['bottom']}>
<KeyboardAvoidingView
style={{ flex: 1 }}
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
>
<ScrollView
contentContainerStyle={styles.scrollContent}
showsVerticalScrollIndicator={false}
keyboardShouldPersistTaps="handled"
>
<View style={styles.scrollContent}>
<View style={styles.header}>
<View style={styles.iconContainer}>
<LinearGradient
@@ -404,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 && (
<>
@@ -540,7 +498,7 @@ export default function RegisterFormScreen() {
</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>
@@ -561,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>
@@ -571,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>
@@ -591,6 +547,7 @@ export default function RegisterFormScreen() {
placeholderTextColor="#94a3b8"
style={styles.textInput}
onChangeText={setReferal}
maxLength={9}
testID="referal-input"
/>
</View>
@@ -643,11 +600,12 @@ export default function RegisterFormScreen() {
</TouchableOpacity>
</View>
</View>
</ScrollView>
</KeyboardAvoidingView>
</View>
</SafeAreaView>
</View>
</KeyboardAwareScrollView>
{/* ---- Country BottomSheet ---- */}
{/* BottomSheet KeyboardAwareScrollView TASHQARISIDA */}
<BottomSheet
ref={countrySheetRef}
index={-1}
@@ -666,7 +624,6 @@ export default function RegisterFormScreen() {
<Text style={styles.sheetTitle}>{t('Davlat')}</Text>
</View>
{/* Search input */}
<View style={styles.searchContainer}>
<Search size={16} color="#94a3b8" />
<BottomSheetTextInput
@@ -726,15 +683,20 @@ export default function RegisterFormScreen() {
}}
/>
</BottomSheet>
</View>
</View>
</>
);
}
const styles = StyleSheet.create({
// ✅ KeyboardAwareScrollView uchun style
keyboardScroll: {
flex: 1,
backgroundColor: '#0f172a',
},
container: {
flex: 1,
backgroundColor: '#0f172a',
minHeight: '100%',
},
scrollContent: {
flexGrow: 1,
@@ -964,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,
@@ -992,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',
},
});

View File

@@ -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,11 +210,19 @@ 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 }]}>
<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 }}
@@ -273,11 +285,8 @@ export default function CreateAdsScreens() {
</Text>
</TouchableOpacity>
</View>
</ScrollView>
{/* FOOTER */}
{/* PAYMENT BOTTOM SHEET */}
</View>
</TouchableWithoutFeedback>
<BottomSheetModal
ref={bottomSheetModalRef}
index={0}
@@ -321,7 +330,7 @@ export default function CreateAdsScreens() {
</View>
</BottomSheetScrollView>
</BottomSheetModal>
</KeyboardAvoidingView>
</KeyboardAwareScrollView>
);
}

View File

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

View File

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

View File

@@ -3,19 +3,19 @@ import { useTheme } from "@/components/ThemeContext";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { Image } from "expo-image";
import { router } from "expo-router";
import * as WebBrowser from "expo-web-browser";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import {
ActivityIndicator,
FlatList,
Text,
ToastAndroid,
TouchableOpacity,
View,
View
} from "react-native";
import { RefreshControl } from "react-native-gesture-handler";
import * as WebBrowser from "expo-web-browser";
import { Toast } from "toastify-react-native";
import { eservices_api } from "../lib/api";
import { useTranslation } from "react-i18next";
const dark = {
bg: "#0f172a",
@@ -39,11 +39,14 @@ export default function EServicesCategoryScreen() {
const handleOpenBrowser = async (fileUrl: string) => {
try {
await WebBrowser.openBrowserAsync(fileUrl);
await WebBrowser.openBrowserAsync(fileUrl, {
dismissButtonStyle: 'close',
presentationStyle: WebBrowser.WebBrowserPresentationStyle.FULL_SCREEN,
});
} catch (error) {
ToastAndroid.show(t("Xatolik yuz berdi"), ToastAndroid.TOP);
Toast.error(t("Xatolik yuz berdi"));
}
}
};
const onRefresh = async () => {
setRefreshing(true);

View File

@@ -2,6 +2,7 @@ import { useTheme } from "@/components/ThemeContext";
import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query";
import { Image } from "expo-image";
import { router, useLocalSearchParams } from "expo-router";
import * as WebBrowser from "expo-web-browser";
import { ChevronLeft } from "lucide-react-native";
import { useCallback, useState } from "react";
import {
@@ -10,14 +11,13 @@ import {
FlatList,
StyleSheet,
Text,
ToastAndroid,
TouchableOpacity,
View,
View
} from "react-native";
import * as WebBrowser from "expo-web-browser";
import { useTranslation } from "react-i18next";
import { RefreshControl } from "react-native-gesture-handler";
import { Toast } from "toastify-react-native";
import { eservices_api } from "../lib/api";
const { width: SCREEN_WIDTH } = Dimensions.get("window");
@@ -74,11 +74,10 @@ export default function EServicesScreen() {
};
const handleOpenBrowser = async (fileUrl: string) => {
ToastAndroid.show(t("Xatolik yuz berdi"), ToastAndroid.TOP);
try {
await WebBrowser.openBrowserAsync(fileUrl);
} catch (error) {
ToastAndroid.show(t("Xatolik yuz berdi"), ToastAndroid.TOP);
Toast.error(t("Xatolik yuz berdi"));
}
};

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 bolishi kerak';
if (!form.description || form.description.length < 5)
e.description = 'Tavsif kamida 5 ta belgidan iborat bolishi kerak';

View File

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

View File

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

View File

@@ -68,9 +68,9 @@ export function ManualTab() {
/** 🔹 Video manbalari (SELECT ga bogliq) */
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 },

View File

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

View File

@@ -91,12 +91,47 @@ export function NotificationTab() {
<Text style={[styles.headerTitle, { color: isDark ? '#f1f5f9' : '#0f172a' }]}>
{t('Bildirishnomalar')}
</Text>
</View>
<FlatList
data={notifications}
keyExtractor={(item) => item.id.toString()}
contentContainerStyle={styles.listContent}
showsVerticalScrollIndicator={false}
ListHeaderComponent={() => {
if (notifications.length === 0) {
return (
<View style={styles.emptyHeader}>
<Text
style={[
styles.emptyTitle,
{ color: isDark ? '#f1f5f9' : '#0f172a' },
]}
>
{t("Hozircha bildirishnomalar yo'q")}
</Text>
{notifications.some((n) => !n.is_read) && (
<Text
style={[
styles.emptyDesc,
{ color: isDark ? '#94a3b8' : '#64748b' },
]}
>
{t("Yangi xabarlar shu yerda paydo boladi")}
</Text>
</View>
);
}
if (notifications.some((n) => !n.is_read)) {
return (
<View style={styles.headerActions}>
<TouchableOpacity
style={[
styles.markAllButton,
{ backgroundColor: isDark ? '#1e293b' : '#e0f2fe', borderColor: '#3b82f6' },
{
backgroundColor: isDark ? '#1e293b' : '#e0f2fe',
borderColor: '#3b82f6',
},
]}
onPress={() => markAllAsRead()}
disabled={isMarkingAllRead}
@@ -109,14 +144,24 @@ export function NotificationTab() {
</Text>
)}
</TouchableOpacity>
)}
</View>
<FlatList
data={notifications}
keyExtractor={(item) => item.id.toString()}
contentContainerStyle={styles.listContent}
showsVerticalScrollIndicator={false}
renderItem={({ item, index }) => <NotificationCard item={item} />}
);
}
return (
<View style={styles.allReadContainer}>
<Text
style={[
styles.allReadText,
{ color: isDark ? '#94a3b8' : '#64748b' },
]}
>
{t("Barcha bildirishnomalar oqilgan")}
</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,
},
});

View File

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

View File

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