diff --git a/api/URLs.ts b/api/URLs.ts
index b351da1..ba4716b 100644
--- a/api/URLs.ts
+++ b/api/URLs.ts
@@ -30,4 +30,8 @@ export const API_URLS = {
My_Ads: 'api/my-ads/',
My_Ads_Detail: (id: number) => `api/my-ads/${id}`,
My_Bonuses: 'api/cashback/',
+ My_Refferals: 'api/referral/',
+ Goverment_Service: '/api/goverment-service/',
+ Notification_List: '/api/notifications/',
+ Notification_Ready: (id: number) => `/api/notifications/${id}/read/`,
};
diff --git a/app.json b/app.json
index 4bba789..c01b6cf 100644
--- a/app.json
+++ b/app.json
@@ -9,19 +9,23 @@
"userInterfaceStyle": "automatic",
"newArchEnabled": true,
"ios": {
- "supportsTablet": true
+ "supportsTablet": true,
+ "infoPlist": {
+ "UIBackgroundModes": ["remote-notification"]
+ },
+ "bundleIdentifier": "com.felix.infotarget"
},
"android": {
+ "useNextNotificationsApi": true,
"adaptiveIcon": {
"backgroundColor": "#E6F4FE",
- "foregroundImage": "./assets/images/logo.png",
- "backgroundImage": "./assets/images/logo.png",
- "monochromeImage": "./assets/images/logo.png"
+ "foregroundImage": "./assets/images/logo.png"
},
"edgeToEdgeEnabled": true,
"predictiveBackGestureEnabled": false,
"package": "com.felix.infotarget",
- "versionCode": 1
+ "versionCode": 1,
+ "googleServicesFile": "./google-services.json"
},
"web": {
"output": "static",
@@ -29,6 +33,14 @@
},
"plugins": [
"expo-router",
+ [
+ "expo-notifications",
+ {
+ "icon": "./assets/images/notification-icon.png",
+ "color": "#ffffff",
+ "sounds": ["./assets/sounds/notification.wav"]
+ }
+ ],
[
"expo-navigation-bar",
{
@@ -59,14 +71,6 @@
},
"extra": {
"router": {},
- "expo-navigation-bar": {
- "backgroundColor": "#0f172a",
- "barStyle": "light",
- "borderColor": "#1f2937",
- "visibility": "visible",
- "behavior": "inset-swipe",
- "position": "relative"
- },
"eas": {
"projectId": "9a281404-9d04-4493-b630-66c35af03ace"
}
diff --git a/app/(dashboard)/_layout.tsx b/app/(dashboard)/_layout.tsx
index f1f6ec0..cfb3f81 100644
--- a/app/(dashboard)/_layout.tsx
+++ b/app/(dashboard)/_layout.tsx
@@ -1,10 +1,13 @@
+import Logo from '@/assets/images/logo.png';
import { useTheme } from '@/components/ThemeContext';
import { RefreshProvider } from '@/components/ui/RefreshContext';
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
+import { Image } from 'expo-image';
+import { LinearGradient } from 'expo-linear-gradient';
import { Tabs } from 'expo-router';
import { Home, Megaphone, PlusCircle, User } from 'lucide-react-native';
import { useTranslation } from 'react-i18next';
-import { Text } from 'react-native';
+import { Platform, StyleSheet, Text, View } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
export default function TabsLayout() {
@@ -13,37 +16,53 @@ export default function TabsLayout() {
return (
-
-
+
+
(
+ title: 'Bosh sahifa',
+ tabBarLabel: ({ color, focused }) => (
{t('Bosh sahifa')}
),
- tabBarIcon: ({ color, size }) => ,
+ tabBarIcon: ({ color, focused }) => (
+
+
+
+ ),
}}
/>
(
+ title: "Qo'shish",
+ tabBarLabel: ({ color, focused }) => (
- {t("E'lon joylashtirish")}
+ {t("Qo'shish")}
),
- tabBarIcon: ({ color, size }) => ,
+ tabBarIcon: ({ color, focused }) => (
+
+
+
+ ),
+ }}
+ />
+
+ null,
+ tabBarItemStyle: {
+ flex: 1.2,
+ top: -10,
+ },
+ tabBarIcon: ({ focused }) => (
+
+
+
+
+
+
+
+ {t('Davlat xizmatlari')}
+
+
+ ),
}}
/>
(
+ title: "E'lonlar",
+ tabBarLabel: ({ color, focused }) => (
{t("E'lonlar")}
),
- tabBarIcon: ({ color, size }) => ,
+ tabBarIcon: ({ color, focused }) => (
+
+
+
+ ),
}}
/>
(
+ title: 'Profil',
+ tabBarLabel: ({ color, focused }) => (
{t('Profil')}
),
- tabBarIcon: ({ color, size }) => ,
+ tabBarIcon: ({ color, focused }) => (
+
+
+
+ ),
}}
/>
-
-
+
+
);
}
+
+const styles = StyleSheet.create({
+ tabLabel: {
+ fontSize: 11,
+ textAlign: 'center',
+ marginTop: 4,
+ },
+ iconContainer: {
+ paddingVertical: 4,
+ },
+ iconContainerActive: {
+ transform: [{ scale: 1.05 }],
+ },
+ centerTabContainer: {
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ centerTabGradient: {
+ width: 72,
+ height: 72,
+ borderRadius: 36,
+ justifyContent: 'center',
+ alignItems: 'center',
+ ...Platform.select({
+ ios: {
+ shadowColor: '#3b82f6',
+ shadowOffset: { width: 0, height: 4 },
+ shadowOpacity: 0.4,
+ shadowRadius: 12,
+ },
+ android: {
+ elevation: 12,
+ },
+ }),
+ },
+ centerTabInner: {
+ width: 68,
+ height: 68,
+ borderRadius: 34,
+ backgroundColor: 'rgba(255, 255, 255, 0.1)',
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ centerTabLabel: {
+ marginTop: 8,
+ fontSize: 11,
+ textAlign: 'center',
+ maxWidth: 110,
+ },
+});
diff --git a/app/(dashboard)/announcements.tsx b/app/(dashboard)/announcements.tsx
index fa1cdab..52e8995 100644
--- a/app/(dashboard)/announcements.tsx
+++ b/app/(dashboard)/announcements.tsx
@@ -2,7 +2,6 @@ import { useTheme } from '@/components/ThemeContext';
import { FilterProvider } from '@/components/ui/FilterContext';
import { CustomHeader } from '@/components/ui/Header';
import DashboardScreen from '@/screens/announcements/ui/AnnouncementsList';
-import { Stack } from 'expo-router';
import { SafeAreaView } from 'react-native-safe-area-context';
export default function Announcements() {
@@ -10,10 +9,9 @@ export default function Announcements() {
return (
-
diff --git a/app/(dashboard)/create-announcements.tsx b/app/(dashboard)/create-announcements.tsx
index 15f95d5..4899820 100644
--- a/app/(dashboard)/create-announcements.tsx
+++ b/app/(dashboard)/create-announcements.tsx
@@ -8,7 +8,9 @@ export default function CreateAnnouncements() {
const { isDark } = useTheme();
return (
-
+
diff --git a/app/(dashboard)/e-services.tsx b/app/(dashboard)/e-services.tsx
new file mode 100644
index 0000000..5890488
--- /dev/null
+++ b/app/(dashboard)/e-services.tsx
@@ -0,0 +1,17 @@
+import { useTheme } from '@/components/ThemeContext';
+import { CustomHeader } from '@/components/ui/Header';
+import EServicesScreen from '@/screens/e-services/ui/EServices';
+import { SafeAreaView } from 'react-native-safe-area-context';
+
+export default function EServices() {
+ const { isDark } = useTheme();
+ return (
+
+
+
+
+ );
+}
diff --git a/app/(dashboard)/index.tsx b/app/(dashboard)/index.tsx
index ad77adc..1070a0f 100644
--- a/app/(dashboard)/index.tsx
+++ b/app/(dashboard)/index.tsx
@@ -24,7 +24,9 @@ export default function Index() {
return (
-
+
diff --git a/app/(dashboard)/profile.tsx b/app/(dashboard)/profile.tsx
index fd33adb..c9be477 100644
--- a/app/(dashboard)/profile.tsx
+++ b/app/(dashboard)/profile.tsx
@@ -7,10 +7,10 @@ export default function ProfileScreen() {
const { isDark } = useTheme();
return (
-
+
);
diff --git a/app/_layout.tsx b/app/_layout.tsx
index d1bb677..454b48b 100644
--- a/app/_layout.tsx
+++ b/app/_layout.tsx
@@ -1,18 +1,25 @@
import { AuthProvider } from '@/components/AuthProvider';
import QueryProvider from '@/components/QueryProvider';
-import { ThemeProvider } from '@/components/ThemeContext';
+import { ThemeProvider, useTheme } from '@/components/ThemeContext';
+import { useNotifications } from '@/hooks/useNotifications';
import i18n from '@/i18n/i18n';
import { ProfileDataProvider } from '@/screens/profile/lib/ProfileDataContext';
import { Stack } from 'expo-router';
-import { StatusBar } from 'expo-status-bar';
import { I18nextProvider } from 'react-i18next';
+import { StatusBar } from 'react-native';
import 'react-native-reanimated';
function AppContent() {
+ const { isDark } = useTheme();
+ useNotifications();
return (
<>
-
+
>
);
}
diff --git a/app/profile/added-referalls.tsx b/app/profile/added-referalls.tsx
new file mode 100644
index 0000000..eabb724
--- /dev/null
+++ b/app/profile/added-referalls.tsx
@@ -0,0 +1,9 @@
+import CreateReferrals from '@/screens/profile/ui/CreateReferrals';
+
+export default function AddEmployeeScreen() {
+ return (
+ <>
+
+ >
+ );
+}
diff --git a/app/profile/manual.tsx b/app/profile/manual.tsx
new file mode 100644
index 0000000..665928f
--- /dev/null
+++ b/app/profile/manual.tsx
@@ -0,0 +1,12 @@
+import { useTheme } from '@/components/ThemeContext';
+import { ManualTab } from '@/screens/profile/ui/ManualTab';
+import { SafeAreaView } from 'react-native-safe-area-context';
+
+export default function MyAds() {
+ const { isDark } = useTheme();
+ return (
+
+
+
+ );
+}
diff --git a/app/profile/my-referrals.tsx b/app/profile/my-referrals.tsx
new file mode 100644
index 0000000..40a0a15
--- /dev/null
+++ b/app/profile/my-referrals.tsx
@@ -0,0 +1,12 @@
+import { useTheme } from '@/components/ThemeContext';
+import { ReferralsTab } from '@/screens/profile/ui/RefferallsTab';
+import { SafeAreaView } from 'react-native-safe-area-context';
+
+export default function MyReffrals() {
+ const { isDark } = useTheme();
+ return (
+
+
+
+ );
+}
diff --git a/app/profile/notification.tsx b/app/profile/notification.tsx
new file mode 100644
index 0000000..dd5bcad
--- /dev/null
+++ b/app/profile/notification.tsx
@@ -0,0 +1,12 @@
+import { useTheme } from '@/components/ThemeContext';
+import { NotificationTab } from '@/screens/profile/ui/NotificationTab';
+import { SafeAreaView } from 'react-native-safe-area-context';
+
+export default function MyAds() {
+ const { isDark } = useTheme();
+ return (
+
+
+
+ );
+}
diff --git a/app/profile/personal-info.tsx b/app/profile/personal-info.tsx
index a0f018a..08ca659 100644
--- a/app/profile/personal-info.tsx
+++ b/app/profile/personal-info.tsx
@@ -17,6 +17,7 @@ import {
Text,
TextInput,
ToastAndroid,
+ TouchableOpacity,
View,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
@@ -92,6 +93,8 @@ export default function PersonalInfoScreen() {
stir: editData.data.stir,
director_full_name: editData.data.director_full_name,
address: editData.data.address,
+ gender: editData.data.gender,
+ age: editData.data.age,
});
};
@@ -117,7 +120,13 @@ export default function PersonalInfoScreen() {
}
/* ===================== EDIT MODE ===================== */
+ const [showGenderOptions, setShowGenderOptions] = useState(false);
if (isEditing && editData) {
+ const genderOptions: { label: string; value: 'male' | 'female' }[] = [
+ { label: t('Erkak'), value: 'male' },
+ { label: t('Ayol'), value: 'female' },
+ ];
+
return (
@@ -136,13 +145,18 @@ export default function PersonalInfoScreen() {
+ {/* First Name */}
{t('Ism')}
setEditData((prev) => prev && { ...prev, first_name: text })}
+ onChangeText={(text) =>
+ setEditData((prev) => prev && { ...prev, data: { ...prev.data, first_name: text } })
+ }
placeholderTextColor={theme.placeholder}
/>
+
+ {/* Phone */}
{t('Telefon raqami')}
@@ -161,18 +175,59 @@ export default function PersonalInfoScreen() {
onChangeText={(text) => setPhone(normalizeDigits(text))}
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
- keyboardType="phone-pad"
+ keyboardType="numeric"
placeholder="90 123 45 67"
maxLength={12}
placeholderTextColor={theme.placeholder}
/>
+
+ {/* Age */}
+ {t('Yoshi')}
+
+ setEditData(
+ (prev) => prev && { ...prev, data: { ...prev.data, age: Number(text) } }
+ )
+ }
+ placeholderTextColor={theme.placeholder}
+ />
+
+ {/* Gender as buttons */}
+ {t('Jinsi')}
+
+ {genderOptions.map((option) => {
+ const isSelected = editData.data.gender === option.value;
+ return (
+
+ setEditData((prev) =>
+ prev ? { ...prev, data: { ...prev.data, gender: option.value } } : prev
+ )
+ }
+ style={{
+ flex: 1,
+ paddingVertical: 12,
+ borderRadius: 12,
+ alignItems: 'center',
+ backgroundColor: isSelected ? theme.primary : theme.inputBg,
+ }}
+ >
+ {option.label}
+
+ );
+ })}
+
);
}
-
/* ===================== VIEW MODE ===================== */
return (
@@ -191,6 +246,16 @@ export default function PersonalInfoScreen() {
{t('Ism')}
{me?.data.data.first_name}
+ {t('Yoshi')}
+
+ {me?.data.data.age ? me?.data.data.age : t("Noma'lum")}
+
+
+ {t('Jinsi')}
+
+ {me?.data.data.gender ? t(me?.data.data.gender) : t("Noma'lum")}
+
+
{t('Telefon raqami')}
@@ -267,7 +332,7 @@ const styles = StyleSheet.create({
container: {
flex: 1,
},
- fieldsContainer: { flexDirection: 'row', flexWrap: 'wrap', gap: 8 },
+ fieldsContainer: { flexDirection: 'row', flexWrap: 'wrap', gap: 8, marginBottom: 20 },
fieldChip: {
paddingHorizontal: 16,
paddingVertical: 10,
diff --git a/assets/images/navbar.png b/assets/images/navbar.png
new file mode 100644
index 0000000..2228c87
Binary files /dev/null and b/assets/images/navbar.png differ
diff --git a/assets/manual/manual_video_en.mp4 b/assets/manual/manual_video_en.mp4
new file mode 100644
index 0000000..e68e8c9
Binary files /dev/null and b/assets/manual/manual_video_en.mp4 differ
diff --git a/assets/manual/manual_video_ru.mp4 b/assets/manual/manual_video_ru.mp4
new file mode 100644
index 0000000..4a9b8b6
Binary files /dev/null and b/assets/manual/manual_video_ru.mp4 differ
diff --git a/assets/manual/manual_video_uz.mp4 b/assets/manual/manual_video_uz.mp4
new file mode 100644
index 0000000..fe50c5c
Binary files /dev/null and b/assets/manual/manual_video_uz.mp4 differ
diff --git a/assets/manual/photo_8_2026-01-30_17-15-31.jpg b/assets/manual/photo_8_2026-01-30_17-15-31.jpg
new file mode 100644
index 0000000..6614744
Binary files /dev/null and b/assets/manual/photo_8_2026-01-30_17-15-31.jpg differ
diff --git a/assets/manual/photo_9_2026-01-30_17-15-31.jpg b/assets/manual/photo_9_2026-01-30_17-15-31.jpg
new file mode 100644
index 0000000..b92136d
Binary files /dev/null and b/assets/manual/photo_9_2026-01-30_17-15-31.jpg differ
diff --git a/assets/manual/step1.jpg b/assets/manual/step1.jpg
new file mode 100644
index 0000000..b2dc3dc
Binary files /dev/null and b/assets/manual/step1.jpg differ
diff --git a/assets/manual/step2.jpg b/assets/manual/step2.jpg
new file mode 100644
index 0000000..9752559
Binary files /dev/null and b/assets/manual/step2.jpg differ
diff --git a/assets/manual/step3.jpg b/assets/manual/step3.jpg
new file mode 100644
index 0000000..421f1a4
Binary files /dev/null and b/assets/manual/step3.jpg differ
diff --git a/assets/manual/step4.jpg b/assets/manual/step4.jpg
new file mode 100644
index 0000000..001f414
Binary files /dev/null and b/assets/manual/step4.jpg differ
diff --git a/assets/manual/step5.jpg b/assets/manual/step5.jpg
new file mode 100644
index 0000000..746fb3a
Binary files /dev/null and b/assets/manual/step5.jpg differ
diff --git a/assets/manual/step6.jpg b/assets/manual/step6.jpg
new file mode 100644
index 0000000..304cea1
Binary files /dev/null and b/assets/manual/step6.jpg differ
diff --git a/assets/manual/step7.jpg b/assets/manual/step7.jpg
new file mode 100644
index 0000000..b3c8870
Binary files /dev/null and b/assets/manual/step7.jpg differ
diff --git a/components/AuthContext.tsx b/components/AuthContext.tsx
new file mode 100644
index 0000000..b5acfd4
--- /dev/null
+++ b/components/AuthContext.tsx
@@ -0,0 +1,32 @@
+// components/AuthContext.tsx
+import AsyncStorage from '@react-native-async-storage/async-storage';
+import React, { createContext, ReactNode, useContext, useEffect, useState } from 'react';
+
+interface AuthContextType {
+ accessToken: string | null;
+ setAccessToken: (token: string | null) => void;
+}
+
+const AuthContext = createContext({
+ accessToken: null,
+ setAccessToken: () => {},
+});
+
+export const AuthProvider = ({ children }: { children: ReactNode }) => {
+ const [accessToken, setAccessToken] = useState(null);
+
+ // AsyncStorage dan tokenni yuklash
+ useEffect(() => {
+ const loadToken = async () => {
+ const token = await AsyncStorage.getItem('access_token');
+ if (token) setAccessToken(token);
+ };
+ loadToken();
+ }, []);
+
+ return (
+ {children}
+ );
+};
+
+export const useAuth = () => useContext(AuthContext);
diff --git a/components/NotificationProvider.tsx b/components/NotificationProvider.tsx
new file mode 100644
index 0000000..930d34a
--- /dev/null
+++ b/components/NotificationProvider.tsx
@@ -0,0 +1,67 @@
+import * as Device from 'expo-device';
+import * as Notifications from 'expo-notifications';
+import { Platform } from 'react-native';
+
+Notifications.setNotificationHandler({
+ handleNotification: async () => ({
+ shouldPlaySound: true,
+ shouldShowAlert: true,
+ shouldSetBadge: true,
+ shouldShowBanner: true,
+ shouldShowList: true,
+ }),
+});
+
+export async function registerForPushNotificationsAsync() {
+ let token;
+
+ if (Platform.OS === 'android') {
+ await Notifications.setNotificationChannelAsync('default', {
+ name: 'default',
+ importance: Notifications.AndroidImportance.MAX,
+ vibrationPattern: [0, 250, 250, 250],
+ lightColor: '#FF231F7C',
+ });
+ }
+
+ if (Device.isDevice) {
+ const { status: existingStatus } = await Notifications.getPermissionsAsync();
+ let finalStatus = existingStatus;
+
+ if (existingStatus !== 'granted') {
+ const { status } = await Notifications.requestPermissionsAsync();
+ finalStatus = status;
+ }
+
+ if (finalStatus !== 'granted') {
+ console.log('Notification uchun ruxsat berilmadi!');
+ return;
+ }
+
+ token = (
+ await Notifications.getExpoPushTokenAsync({
+ projectId: '9a281404-9d04-4493-b630-66c35af03ace',
+ })
+ ).data;
+
+ console.log('Push Token:', token);
+ } else {
+ console.log('Push notification faqat real qurilmalarda ishlaydi!');
+ }
+
+ return token;
+}
+
+export async function sendTestNotification() {
+ await Notifications.scheduleNotificationAsync({
+ content: {
+ title: 'Test xabar 📬',
+ body: 'Bu test notification! Notification tizimi muvaffaqiyatli ishlayapti.',
+ data: { screen: 'monitoring', type: 'test' },
+ },
+ trigger: {
+ type: Notifications.SchedulableTriggerInputTypes.TIME_INTERVAL,
+ seconds: 3, // 2 o'rniga 3
+ },
+ });
+}
diff --git a/components/ui/CountriesList.tsx b/components/ui/CountriesList.tsx
index f0b3de7..986f86d 100644
--- a/components/ui/CountriesList.tsx
+++ b/components/ui/CountriesList.tsx
@@ -1,6 +1,7 @@
import { useTheme } from '@/components/ThemeContext';
import { products_api } from '@/screens/home/lib/api';
import { useInfiniteQuery } from '@tanstack/react-query';
+import { Image } from 'expo-image';
import { Building2, ChevronDown, ChevronUp } from 'lucide-react-native';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -74,7 +75,7 @@ export default function CountriesList({ search }: { search: string }) {
item.id.toString()}
- contentContainerStyle={{ gap: 12 }}
+ contentContainerStyle={{ gap: 5 }}
onEndReached={loadMore}
onEndReachedThreshold={0.4}
ListFooterComponent={
@@ -83,7 +84,7 @@ export default function CountriesList({ search }: { search: string }) {
showsVerticalScrollIndicator={false}
renderItem={({ item }) => {
const isOpen = openedCountryId === item.id;
-
+ const flagCode = item.flag ? item.flag.toLowerCase() : ''; // "uz"
return (
{/* Davlat sarlavhasi */}
@@ -92,9 +93,24 @@ export default function CountriesList({ search }: { search: string }) {
onPress={() => toggleAccordion(item.id)}
activeOpacity={0.8}
>
-
- {item.name}
-
+
+
+
+
+ {item.name}
+
+
(null);
+ const snapPoints = useMemo(() => ['60%', '85%'], []);
+
+ const [activeSheet, setActiveSheet] = useState(null);
const [selectedCategories, setSelectedCategories] = useState(null);
const [selectedCountry, setSelectedCountry] = useState('all');
const [selectedRegion, setSelectedRegion] = useState('all');
@@ -46,7 +69,7 @@ export default function FilterUI({ back, onApply, setStep, setFiltered }: Filter
select: (res) => res.data?.data || [],
});
- const { mutate } = useMutation({
+ const { mutate, isPending } = useMutation({
mutationFn: (params: any) => products_api.businessAbout(params),
onSuccess: (data) => {
setStep('items');
@@ -80,177 +103,409 @@ export default function FilterUI({ back, onApply, setStep, setFiltered }: Filter
return region?.districts || [];
}, [regions, selectedRegion]);
+ const openSheet = useCallback((type: SheetType) => {
+ setActiveSheet(type);
+ setTimeout(() => {
+ bottomSheetRef.current?.snapToIndex(0);
+ }, 100);
+ }, []);
+
+ const closeSheet = useCallback(() => {
+ bottomSheetRef.current?.close();
+ setTimeout(() => setActiveSheet(null), 300);
+ }, []);
+
+ const renderBackdrop = useCallback(
+ (props: any) => (
+
+ ),
+ []
+ );
+
+ const getSelectedLabel = useCallback(
+ (type: SheetType) => {
+ switch (type) {
+ case 'country':
+ if (selectedCountry === 'all') return t('Barchasi');
+ return (
+ countryResponse?.find((c) => c.id?.toString() === selectedCountry)?.name || t('Tanlang')
+ );
+ case 'region':
+ if (selectedRegion === 'all') return t('Barchasi');
+ return regions.find((r) => r.id?.toString() === selectedRegion)?.name || t('Tanlang');
+ case 'district':
+ if (selectedDistrict === 'all') return t('Barchasi');
+ return districts.find((d) => d.id?.toString() === selectedDistrict)?.name || t('Tanlang');
+ case 'category':
+ return selectedCategories?.name || t('Tanlang');
+ default:
+ return t('Tanlang');
+ }
+ },
+ [
+ selectedCountry,
+ selectedRegion,
+ selectedDistrict,
+ selectedCategories,
+ countryResponse,
+ regions,
+ districts,
+ t,
+ ]
+ );
+
+ const FilterButton = useCallback(
+ ({
+ label,
+ value,
+ onPress,
+ disabled = false,
+ }: {
+ label: string;
+ value: string;
+ onPress: () => void;
+ disabled?: boolean;
+ }) => (
+
+
+
+ {label}
+
+
+
+ {value}
+
+
+
+
+
+ ),
+ [isDark]
+ );
+
+ const renderListItem = useCallback(
+ ({
+ item,
+ onSelect,
+ selectedId,
+ }: {
+ item: any;
+ onSelect: (id: string) => void;
+ selectedId: string;
+ }) => {
+ const isSelected = selectedId === (item.id?.toString() || 'all');
+ const flagCode = item.flag ? item.flag.toLowerCase() : ''; // "uz"
+ return (
+ onSelect(item.id?.toString() || 'all')}
+ activeOpacity={0.7}
+ >
+
+ {item.flag && (
+
+ )}
+
+ {item.name}
+
+
+ {isSelected && (
+
+
+
+ )}
+
+ );
+ },
+ [isDark]
+ );
+
+ const renderSheetContent = useCallback(() => {
+ if (activeSheet === 'category') {
+ return (
+
+
+
+ );
+ }
+
+ let data: any[] = [];
+ let onSelect: (id: string) => void = () => {};
+ let selectedId = '';
+
+ switch (activeSheet) {
+ case 'country':
+ data = [{ id: 'all', name: t('Barchasi') }, ...(countryResponse || [])];
+ onSelect = (id) => {
+ setSelectedCountry(id);
+ setSelectedRegion('all');
+ setSelectedDistrict('all');
+ closeSheet();
+ };
+ selectedId = selectedCountry;
+ break;
+ case 'region':
+ data = [{ id: 'all', name: t('Barchasi') }, ...regions];
+ onSelect = (id) => {
+ setSelectedRegion(id);
+ setSelectedDistrict('all');
+ closeSheet();
+ };
+ selectedId = selectedRegion;
+ break;
+ case 'district':
+ data = [{ id: 'all', name: t('Barchasi') }, ...districts];
+ onSelect = (id) => {
+ setSelectedDistrict(id);
+ closeSheet();
+ };
+ selectedId = selectedDistrict;
+ break;
+ }
+
+ return (
+ item.id?.toString() || 'all'}
+ contentContainerStyle={styles.listContainer}
+ showsVerticalScrollIndicator={false}
+ bounces={true}
+ overScrollMode="always"
+ renderItem={({ item }: { item: any }) => renderListItem({ item, onSelect, selectedId })}
+ />
+ );
+ }, [
+ activeSheet,
+ selectedCategories,
+ countryResponse,
+ regions,
+ districts,
+ selectedCountry,
+ selectedRegion,
+ selectedDistrict,
+ t,
+ closeSheet,
+ renderListItem,
+ ]);
+
+ const getSheetTitle = useCallback(() => {
+ switch (activeSheet) {
+ case 'country':
+ return t('Davlat');
+ case 'region':
+ return t('Viloyat');
+ case 'district':
+ return t('Tuman');
+ case 'category':
+ return t('Sohalar');
+ default:
+ return '';
+ }
+ }, [activeSheet, t]);
+
if (isLoading) {
return (
-
-
+
+
+
+
);
}
- // Single Tag Component
- const Tag = ({
- label,
- selected,
- onPress,
- }: {
- label: string;
- selected: boolean;
- onPress: () => void;
- }) => (
-
-
- {label}
-
-
- );
-
return (
-
- {/* Header */}
-
-
-
-
-
+
+
+ {/* Header */}
+
+
+ {t('Filter')}
+
+
+
+
+
- {/* Scrollable Content */}
-
- {/* Country Filter */}
-
- {t('Davlat')}
-
-
- setSelectedCountry('all')}
+ {/* Filter Options */}
+
+ openSheet('country')}
/>
- {countryResponse?.map((c) => (
- {
- setSelectedCountry(c.id?.toString() || 'all');
- setSelectedRegion('all');
- setSelectedDistrict('all');
- }}
- />
- ))}
-
- {/* Region Filter */}
- {regions.length > 0 && (
- <>
-
- {t('Viloyat')}
+ openSheet('region')}
+ disabled={selectedCountry === 'all' || regions.length === 0}
+ />
+
+ openSheet('district')}
+ disabled={selectedRegion === 'all' || districts.length === 0}
+ />
+
+ openSheet('category')}
+ />
+
+
+ {/* Apply Button */}
+
+
+ {isPending ? (
+
+ ) : (
+ {t("Natijalarni ko'rish")}
+ )}
+
+
+
+ {/* Bottom Sheet */}
+ setActiveSheet(null)}
+ backdropComponent={renderBackdrop}
+ backgroundStyle={[styles.bottomSheetBackground, isDark ? styles.darkBg : styles.lightBg]}
+ handleIndicatorStyle={[
+ styles.handleIndicator,
+ isDark ? styles.darkHandleIndicator : styles.lightHandleIndicator,
+ ]}
+ android_keyboardInputMode="adjustResize"
+ keyboardBehavior="interactive"
+ keyboardBlurBehavior="restore"
+ >
+
+
+ {getSheetTitle()}
-
- setSelectedRegion('all')}
- />
- {regions.map((r) => (
- {
- setSelectedRegion(r.id?.toString() || 'all');
- setSelectedDistrict('all');
- }}
- />
- ))}
-
- >
- )}
-
- {/* District Filter */}
- {districts.length > 0 && (
- <>
-
- {t('Tuman')}
-
-
- setSelectedDistrict('all')}
- />
- {districts.map((d) => (
- setSelectedDistrict(d.id?.toString() || 'all')}
- />
- ))}
-
- >
- )}
-
- {/* Industry Selection */}
-
- {t('Sohalar')}
-
-
-
-
- {/* Fixed Apply Button */}
-
-
- {t("Natijalarni ko'rish")}
-
+
+
+
+
+ {renderSheetContent()}
+
-
+
);
}
const styles = StyleSheet.create({
- container: { flex: 1 },
+ container: {
+ flex: 1,
+ },
darkBg: {
backgroundColor: '#0f172a',
},
lightBg: {
backgroundColor: '#f8fafc',
},
- backBtn: {
- paddingHorizontal: 10,
- paddingVertical: 10,
- borderRadius: 10,
- marginTop: 10,
+ header: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ paddingHorizontal: 20,
+ paddingVertical: 16,
+ borderBottomWidth: 1,
+ },
+ headerTitle: {
+ fontSize: 20,
+ fontWeight: '700',
+ },
+ closeBtn: {
+ padding: 8,
+ borderRadius: 8,
+ borderWidth: 1,
+ },
+ darkCloseBtn: {
+ backgroundColor: '#1e293b',
+ borderColor: '#334155',
+ },
+ lightCloseBtn: {
+ backgroundColor: '#ffffff',
+ borderColor: '#e2e8f0',
+ },
+ content: {
+ flex: 1,
+ padding: 20,
+ gap: 12,
+ },
+ loadingContainer: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ filterBtn: {
+ padding: 16,
+ borderRadius: 12,
borderWidth: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
@@ -258,81 +513,56 @@ const styles = StyleSheet.create({
shadowRadius: 2,
elevation: 1,
},
- darkBackBtn: {
+ darkFilterBtn: {
backgroundColor: '#1e293b',
borderColor: '#334155',
},
- lightBackBtn: {
+ lightFilterBtn: {
backgroundColor: '#ffffff',
borderColor: '#e2e8f0',
},
- btn: {
- justifyContent: 'flex-end',
- alignItems: 'flex-end',
- paddingHorizontal: 16,
+ disabledBtn: {
+ opacity: 0.5,
},
- sectionTitle: {
- fontSize: 16,
- fontWeight: '700',
- marginBottom: 10,
+ filterBtnContent: {
+ flexDirection: 'column',
+ gap: 8, // 4 o'rniga 8 qildim
},
+ filterLabel: {
+ fontSize: 13,
+ fontWeight: '600',
+ marginBottom: 2, // 4 o'rniga 2 qildim
+ },
+ filterValueContainer: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ gap: 8, // ChevronRight va text orasida gap
+ },
+ filterValue: {
+ fontSize: 15,
+ fontWeight: '500',
+ flex: 1,
+ },
+
darkText: {
color: '#f1f5f9',
},
lightText: {
color: '#0f172a',
},
- scrollRow: { flexDirection: 'row', marginBottom: 12, gap: 10 },
-
- tag: {
- paddingHorizontal: 16,
- paddingVertical: 10,
- borderRadius: 12,
- marginRight: 10,
- borderWidth: 1,
- shadowColor: '#000',
- shadowOffset: { width: 0, height: 1 },
- shadowOpacity: 0.05,
- shadowRadius: 2,
- elevation: 1,
- },
- darkTag: {
- backgroundColor: '#1e293b',
- borderColor: '#334155',
- },
- lightTag: {
- backgroundColor: '#ffffff',
- borderColor: '#e2e8f0',
- },
- tagSelected: {
- backgroundColor: '#3b82f6',
- borderColor: '#3b82f6',
- shadowColor: '#3b82f6',
- shadowOffset: { width: 0, height: 2 },
- shadowOpacity: 0.3,
- shadowRadius: 4,
- elevation: 5,
- },
- tagText: {
- fontWeight: '500',
- },
- darkTagText: {
+ darkValueText: {
color: '#cbd5e1',
},
- lightTagText: {
+ lightValueText: {
color: '#64748b',
},
- tagTextSelected: {
- color: '#ffffff',
- fontWeight: '600',
+ disabledText: {
+ opacity: 0.5,
},
-
applyBtnWrapper: {
- position: 'absolute',
- bottom: 55,
- left: 16,
- right: 16,
- zIndex: 10,
+ padding: 20,
+ borderTopWidth: 1,
},
applyBtn: {
backgroundColor: '#3b82f6',
@@ -344,7 +574,98 @@ const styles = StyleSheet.create({
shadowOpacity: 0.3,
shadowRadius: 4,
elevation: 5,
- marginBottom: 20,
},
- applyBtnText: { color: '#ffffff', fontWeight: '700', fontSize: 16 },
+ applyBtnDisabled: {
+ opacity: 0.7,
+ },
+ applyBtnText: {
+ color: '#ffffff',
+ fontWeight: '700',
+ fontSize: 16,
+ },
+ bottomSheetBackground: {
+ borderTopLeftRadius: 24,
+ borderTopRightRadius: 24,
+ shadowColor: '#000',
+ shadowOffset: { width: 0, height: -2 },
+ shadowOpacity: 0.1,
+ shadowRadius: 8,
+ elevation: 5,
+ },
+ handleIndicator: {
+ width: 40,
+ height: 4,
+ borderRadius: 2,
+ },
+ darkHandleIndicator: {
+ backgroundColor: '#475569',
+ },
+ lightHandleIndicator: {
+ backgroundColor: '#cbd5e1',
+ },
+ sheetHeader: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ paddingHorizontal: 20,
+ paddingVertical: 16,
+ borderBottomWidth: 1,
+ },
+ sheetTitle: {
+ fontSize: 18,
+ fontWeight: '700',
+ },
+ sheetCloseBtn: {
+ padding: 4,
+ },
+ listContainer: {
+ padding: 16,
+ paddingBottom: 40,
+ },
+ scrollViewContent: {
+ padding: 16,
+ paddingBottom: 40,
+ },
+ listItem: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ padding: 16,
+ borderRadius: 12,
+ marginBottom: 8,
+ borderWidth: 1,
+ },
+ darkListItem: {
+ backgroundColor: '#1e293b',
+ borderColor: '#334155',
+ },
+ lightListItem: {
+ backgroundColor: '#ffffff',
+ borderColor: '#e2e8f0',
+ },
+ selectedListItem: {
+ backgroundColor: '#3b82f6',
+ borderColor: '#3b82f6',
+ },
+ listItemText: {
+ fontSize: 15,
+ fontWeight: '500',
+ },
+ selectedListItemText: {
+ color: '#ffffff',
+ fontWeight: '600',
+ },
+ checkmark: {
+ width: 24,
+ height: 24,
+ borderRadius: 12,
+ backgroundColor: '#ffffff',
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ checkmarkText: {
+ color: '#3b82f6',
+ fontSize: 16,
+ fontWeight: '700',
+ },
});
diff --git a/components/ui/FilteredItems.tsx b/components/ui/FilteredItems.tsx
index c35311c..fa4fc07 100644
--- a/components/ui/FilteredItems.tsx
+++ b/components/ui/FilteredItems.tsx
@@ -42,10 +42,7 @@ export default function FilteredItems({ data, back }: FilteredItemsProps) {
if (selectedItem) {
return (
-
+
{/* Back Button */}
0 ? (
item.id.toString()}
renderItem={({ item }) => (
{
+export const CustomHeader = ({
+ logoutbtn = false,
+ notif = true,
+}: {
+ logoutbtn?: boolean;
+ notif?: boolean;
+}) => {
const { isDark } = useTheme();
- const { t } = useTranslation();
const { logout } = useAuth();
+ const { data, isLoading } = useQuery({
+ queryKey: ['notification-list'],
+ queryFn: () => user_api.notification_list({ page: 1, page_size: 1 }),
+ });
+ const unreadCount = data?.data?.data.unread_count ?? 0;
+
return (
-
- {t('app.name', 'InfoTarget')}
-
+
{logoutbtn && (
)}
+ {notif && (
+ router.push('/profile/notification')} // yoki '/notifications' sahifasiga
+ style={styles.bellContainer}
+ >
+
+
+ {unreadCount > 0 && (
+
+ {unreadCount > 99 ? '99+' : unreadCount}
+
+ )}
+
+ {/* Agar yuklanayotgan bo'lsa kichik indikator (ixtiyoriy) */}
+ {isLoading && unreadCount === 0 && }
+
+ )}
);
};
@@ -57,7 +86,8 @@ const styles = StyleSheet.create({
logoWrapper: {
flexDirection: 'row',
alignItems: 'center',
- gap: 12,
+ alignContent: 'center',
+ gap: 5,
},
logoContainer: {
@@ -89,4 +119,38 @@ const styles = StyleSheet.create({
lightText: {
color: '#0f172a',
},
+ bellContainer: {
+ position: 'relative',
+ },
+
+ badge: {
+ position: 'absolute',
+ top: -6,
+ right: -6,
+ minWidth: 18,
+ height: 18,
+ borderRadius: 9,
+ backgroundColor: '#ef4444', // qizil badge
+ justifyContent: 'center',
+ alignItems: 'center',
+ paddingHorizontal: 4,
+ borderWidth: 1.5,
+ },
+
+ badgeText: {
+ color: '#ffffff',
+ fontSize: 10,
+ fontWeight: 'bold',
+ },
+
+ loadingDot: {
+ position: 'absolute',
+ top: 0,
+ right: 0,
+ width: 8,
+ height: 8,
+ borderRadius: 4,
+ backgroundColor: '#3b82f6',
+ opacity: 0.7,
+ },
});
diff --git a/google-services.json b/google-services.json
new file mode 100644
index 0000000..c62394c
--- /dev/null
+++ b/google-services.json
@@ -0,0 +1,29 @@
+{
+ "project_info": {
+ "project_number": "1082838592913",
+ "project_id": "infotarget-b4d7b",
+ "storage_bucket": "infotarget-b4d7b.firebasestorage.app"
+ },
+ "client": [
+ {
+ "client_info": {
+ "mobilesdk_app_id": "1:1082838592913:android:862235507e6fa20aa23e30",
+ "android_client_info": {
+ "package_name": "com.felix.infotarget"
+ }
+ },
+ "oauth_client": [],
+ "api_key": [
+ {
+ "current_key": "AIzaSyAKFx1WLRSdT4ykyTadtPXlb6WMOm4FNCE"
+ }
+ ],
+ "services": {
+ "appinvite_service": {
+ "other_platform_oauth_client": []
+ }
+ }
+ }
+ ],
+ "configuration_version": "1"
+}
\ No newline at end of file
diff --git a/hooks/useNotifications.ts b/hooks/useNotifications.ts
new file mode 100644
index 0000000..0700757
--- /dev/null
+++ b/hooks/useNotifications.ts
@@ -0,0 +1,66 @@
+import httpClient from '@/api/httpClient';
+import { registerForPushNotificationsAsync } from '@/components/NotificationProvider';
+import { useQueryClient } from '@tanstack/react-query';
+import * as Notifications from 'expo-notifications';
+import { router } from 'expo-router';
+import { useEffect, useRef } from 'react';
+import { Platform } from 'react-native';
+import { getToken } from './storage.native';
+
+export interface IRegisterDeviceBody {
+ token: string;
+ platform: string;
+}
+
+const commonRequests = {
+ /**
+ * Register device for notification
+ * @param body token
+ * @returns
+ */
+ async registerDevice(body: IRegisterDeviceBody) {
+ const response = await httpClient.post('https://api.infotarget.uz/api/push-token/', body, {
+ headers: {
+ Authorization: `Bearer ${getToken()}`,
+ },
+ });
+ return response;
+ },
+};
+
+export function useNotifications() {
+ const notificationListener = useRef(null);
+ const responseListener = useRef(null);
+ const queryClinet = useQueryClient();
+ useEffect(() => {
+ registerForPushNotificationsAsync().then((token) => {
+ if (!token) return null;
+
+ const body: IRegisterDeviceBody = {
+ token: token,
+ platform: Platform.OS,
+ };
+ commonRequests.registerDevice(body);
+ });
+
+ notificationListener.current = Notifications.addNotificationReceivedListener(
+ (notification) => {}
+ );
+
+ responseListener.current = Notifications.addNotificationResponseReceivedListener((response) => {
+ const data = response.notification.request.content.data;
+ queryClinet.refetchQueries({ queryKey: ['notification-list'] });
+ queryClinet.refetchQueries({ queryKey: ['notifications-list'] });
+ if (data?.screen === '/profile/notification') {
+ return;
+ } else {
+ router.push('/profile/notification');
+ }
+ });
+
+ return () => {
+ notificationListener.current?.remove();
+ responseListener.current?.remove();
+ };
+ }, []);
+}
diff --git a/i18n/locales/en.json b/i18n/locales/en.json
index 613d59c..da24488 100644
--- a/i18n/locales/en.json
+++ b/i18n/locales/en.json
@@ -38,7 +38,7 @@
"Qayta urinish": "Retry",
"Bosh sahifa": "Home",
"E'lon joylashtirish": "Post Announcement",
- "E'lonlar": "Announcements",
+ "E'lonlar": "Ads",
"Profil": "Profile",
"Davlat": "Country",
"Barchasi": "All",
@@ -145,5 +145,43 @@
"Tilni tanlang": "Select language",
"Rejimni tanlang": "Select mode",
"Tungi rejim": "Dark mode",
- "Yorug' rejim": "Light mode"
+ "Yorug' rejim": "Light mode",
+ "E'lon qo'shish": "Add announcement",
+ "Qo'shish": "Add",
+ "Refferallarim": "My referrals",
+ "Davlat xizmatlari": "Public services",
+ "foydalanuvchi": "customer",
+ "Referral yaratildi": "Referral created",
+ "Kod aynan 9 ta belgidan iborat bo‘lishi kerak": "Code must be exactly 9 characters",
+ "Tavsif kamida 5 ta belgidan iborat bo‘lishi kerak": "Description must be at least 5 characters",
+ "Agent uchun foiz majburiy": "Agent percentage is required",
+ "Referral yaratish": "Create Referral",
+ "Referral nomi": "Referral Name",
+ "Agentmi?": "Is Agent?",
+ "Referral foizi (%)": "Referral Percentage (%)",
+ "Yoshi": "Age",
+ "Noma'lum": "Unknown",
+ "Jinsi": "Gender",
+ "male": "Male",
+ "female": "Female",
+ "Foydalanish qo'lanmasi": "User Manual",
+ "Ro'yxatdan o'tish (Registratsiya) – 1 daqiqa ichida": "Registration – in 1 minute",
+ "Platformaga kirish uchun avval ro'yxatdan o'ting.": "First, register to access the platform.",
+ "Profilni to'ldirish va tasdiqlash": "Complete and verify your profile",
+ "Muhim: Ro'yxatdan o'tgandan keyin profil to'liq bo'lishi kerak — aks holda platforma cheklangan rejimda ishlaydi.": "Important: After registration, the profile must be complete; otherwise the platform works in limited mode.",
+ "Xodimlarni qo'shish": "Add employees",
+ "Profil ichida Xodimlar bo'limiga o'ting va + tugmasi orqali xodim qo'shing.": "Go to the Employees section in the profile and add an employee using the + button.",
+ "E'lon berish va o'z mahsulot/xizmatlaringizni ko'rsatish": "Post announcements and showcase your products/services",
+ "Pastki menyudan E'lonlar bo'limiga o'ting va yangi e'lon yarating.": "Go to the Announcements section in the bottom menu and create a new announcement.",
+ "Mijozlarni qidirish va topish": "Search and find clients",
+ "Filtrdan foydalanib kerakli kompaniyalarni toping va profiliga kirib xabar yuboring.": "Use the filter to find the desired companies and send a message to their profile.",
+ "Muhim maslahatlar va xavfsizlik": "Important tips and safety",
+ "Faqat tasdiqlangan profillarga ishonch bilan murojaat qiling.": "Only contact verified profiles.",
+ "Qo'llanma video": "Manual Video",
+ "Bildirishnomalar": "Notifications",
+ "Bildirishnomalarni yuklashda muammo bo'ldi": "There was a problem loading notifications",
+ "Hozir": "Just now",
+ "daqiqa oldin": "minute ago",
+ "soat oldin": "hours ago",
+ "kun oldin": "days ago"
}
diff --git a/i18n/locales/ru.json b/i18n/locales/ru.json
index 99f293b..2919927 100644
--- a/i18n/locales/ru.json
+++ b/i18n/locales/ru.json
@@ -145,5 +145,42 @@
"Tilni tanlang": "Выберите язык",
"Rejimni tanlang": "Выберите режим",
"Tungi rejim": "Ночной режим",
- "Yorug' rejim": "Дневной режим"
+ "Yorug' rejim": "Дневной режим",
+ "Qo'shish": "Добавить",
+ "Refferallarim": "Мои рефералы",
+ "Davlat xizmatlari": "Государственные услуги",
+ "foydalanuvchi": "пользователь",
+ "Referral yaratildi": "Рефераль создан",
+ "Kod aynan 9 ta belgidan iborat bo‘lishi kerak": "Код должен содержать ровно 9 символов",
+ "Tavsif kamida 5 ta belgidan iborat bo‘lishi kerak": "Описание должно содержать не менее 5 символов",
+ "Agent uchun foiz majburiy": "Процент для агента обязателен",
+ "Referral yaratish": "Создать реферал",
+ "Referral nomi": "Название реферала",
+ "Agentmi?": "Является агентом?",
+ "Referral foizi (%)": "Процент реферала (%)",
+ "Yoshi": "Возраст",
+ "Noma'lum": "Неизвестный",
+ "Jinsi": "Пол",
+ "male": "Мужской",
+ "female": "Женщина",
+ "Foydalanish qo'lanmasi": "Инструкция по использованию",
+ "Ro'yxatdan o'tish (Registratsiya) – 1 daqiqa ichida": "Регистрация – за 1 минуту",
+ "Platformaga kirish uchun avval ro'yxatdan o'ting.": "Сначала войдите в систему, зарегистрировавшись.",
+ "Profilni to'ldirish va tasdiqlash": "Заполнение и подтверждение профиля",
+ "Muhim: Ro'yxatdan o'tgandan keyin profil to'liq bo'lishi kerak — aks holda platforma cheklangan rejimda ishlaydi.": "Важно: после регистрации профиль должен быть заполнен полностью, иначе платформа работает в ограниченном режиме.",
+ "Xodimlarni qo'shish": "Добавление сотрудников",
+ "Profil ichida Xodimlar bo'limiga o'ting va + tugmasi orqali xodim qo'shing.": "Перейдите в раздел сотрудников в профиле и добавьте сотрудника через кнопку +.",
+ "E'lon berish va o'z mahsulot/xizmatlaringizni ko'rsatish": "Создание объявлений и показ своих продуктов/услуг",
+ "Pastki menyudan E'lonlar bo'limiga o'ting va yangi e'lon yarating.": "Перейдите в раздел объявлений в нижнем меню и создайте новое объявление.",
+ "Mijozlarni qidirish va topish": "Поиск и нахождение клиентов",
+ "Filtrdan foydalanib kerakli kompaniyalarni toping va profiliga kirib xabar yuboring.": "Используйте фильтр, чтобы найти нужные компании, и отправьте сообщение в их профиль.",
+ "Muhim maslahatlar va xavfsizlik": "Важные советы и безопасность",
+ "Faqat tasdiqlangan profillarga ishonch bilan murojaat qiling.": "Обращайтесь только к подтвержденным профилям.",
+ "Qo'llanma video": "Видео-инструкция",
+ "Bildirishnomalar": "Уведомления",
+ "Bildirishnomalarni yuklashda muammo bo'ldi": "Возникла проблема с загрузкой уведомлений",
+ "Hozir": "Сейчас",
+ "daqiqa oldin": "минут назад",
+ "soat oldin": "час назад",
+ "kun oldin": "дней назад"
}
diff --git a/i18n/locales/uz.json b/i18n/locales/uz.json
index 64642a0..eb04d97 100644
--- a/i18n/locales/uz.json
+++ b/i18n/locales/uz.json
@@ -36,7 +36,7 @@
"Davlatlar": "Davlatlar",
"Ma'lumot yuklashda xatolik": "Ma'lumot yuklashda xatolik",
"Qayta urinish": "Qayta urinish",
- "Bosh sahifa": "Bosh sahifa",
+ "Bosh sahifa": "Asosiy",
"E'lon joylashtirish": "E'lon joylashtirish",
"E'lonlar": "E'lonlar",
"Profil": "Profil",
@@ -145,5 +145,42 @@
"Tilni tanlang": "Tilni tanlang",
"Rejimni tanlang": "Rejimni tanlang",
"Tungi rejim": "Tungi rejim",
- "Yorug' rejim": "Yorug' rejim"
+ "Yorug' rejim": "Yorug' rejim",
+ "E'lon qo'shish": "E'lon qo'shish",
+ "Refferallarim": "Refferallarim",
+ "Davlat xizmatlari": "Davlat xizmatlari",
+ "foydalanuvchi": "foydalanuvchi",
+ "Referral yaratildi": "Referral yaratildi",
+ "Kod aynan 9 ta belgidan iborat bo‘lishi kerak": "Kod aynan 9 ta belgidan iborat bo‘lishi kerak",
+ "Tavsif kamida 5 ta belgidan iborat bo‘lishi kerak": "Tavsif kamida 5 ta belgidan iborat bo‘lishi kerak",
+ "Agent uchun foiz majburiy": "Agent uchun foiz majburiy",
+ "Referral yaratish": "Referral yaratish",
+ "Referral nomi": "Referral nomi",
+ "Agentmi?": "Agentmi?",
+ "Referral foizi (%)": "Referral foizi (%)",
+ "Yoshi": "Yoshi",
+ "Noma'lum": "Noma'lum",
+ "Jinsi": "Jinsi",
+ "male": "Erkak",
+ "female": "Ayol",
+ "Foydalanish qo'lanmasi": "Foydalanish qo'lanmasi",
+ "Ro'yxatdan o'tish (Registratsiya) – 1 daqiqa ichida": "Ro'yxatdan o'tish (Registratsiya) – 1 daqiqa ichida",
+ "Platformaga kirish uchun avval ro'yxatdan o'ting.": "Platformaga kirish uchun avval ro'yxatdan o'ting.",
+ "Profilni to'ldirish va tasdiqlash": "Profilni to'ldirish va tasdiqlash",
+ "Muhim: Ro'yxatdan o'tgandan keyin profil to'liq bo'lishi kerak — aks holda platforma cheklangan rejimda ishlaydi.": "Muhim: Ro'yxatdan o'tgandan keyin profil to'liq bo'lishi kerak — aks holda platforma cheklangan rejimda ishlaydi.",
+ "Xodimlarni qo'shish": "Xodimlarni qo'shish",
+ "Profil ichida Xodimlar bo'limiga o'ting va + tugmasi orqali xodim qo'shing.": "Profil ichida Xodimlar bo'limiga o'ting va + tugmasi orqali xodim qo'shing.",
+ "E'lon berish va o'z mahsulot/xizmatlaringizni ko'rsatish": "E'lon berish va o'z mahsulot/xizmatlaringizni ko'rsatish",
+ "Pastki menyudan E'lonlar bo'limiga o'ting va yangi e'lon yarating.": "Pastki menyudan E'lonlar bo'limiga o'ting va yangi e'lon yarating.",
+ "Mijozlarni qidirish va topish": "Mijozlarni qidirish va topish",
+ "Filtrdan foydalanib kerakli kompaniyalarni toping va profiliga kirib xabar yuboring.": "Filtrdan foydalanib kerakli kompaniyalarni toping va profiliga kirib xabar yuboring.",
+ "Muhim maslahatlar va xavfsizlik": "Muhim maslahatlar va xavfsizlik",
+ "Faqat tasdiqlangan profillarga ishonch bilan murojaat qiling.": "Faqat tasdiqlangan profillarga ishonch bilan murojaat qiling.",
+ "Qo'llanma video": "Qo'llanma video",
+ "Bildirishnomalar": "Bildirishnomalar",
+ "Bildirishnomalarni yuklashda muammo bo'ldi": "Bildirishnomalarni yuklashda muammo bo'ldi",
+ "Hozir": "Hozir",
+ "daqiqa oldin": "daqiqa oldin",
+ "soat oldin": "soat oldin",
+ "kun oldin": "kun oldin"
}
diff --git a/package-lock.json b/package-lock.json
index f6c07e0..124e5c6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -25,8 +25,10 @@
"expo": "~54.0.31",
"expo-av": "~16.0.8",
"expo-blur": "~15.0.8",
+ "expo-clipboard": "~8.0.8",
"expo-constants": "~18.0.13",
"expo-dev-client": "~6.0.20",
+ "expo-device": "~8.0.10",
"expo-font": "~14.0.10",
"expo-haptics": "~15.0.8",
"expo-image": "~3.0.11",
@@ -34,6 +36,7 @@
"expo-linear-gradient": "~15.0.8",
"expo-linking": "~8.0.11",
"expo-navigation-bar": "~5.0.10",
+ "expo-notifications": "~0.32.16",
"expo-router": "~6.0.21",
"expo-splash-screen": "~31.0.13",
"expo-status-bar": "~3.0.9",
@@ -59,6 +62,7 @@
"react-native-screens": "~4.16.0",
"react-native-svg": "^15.15.1",
"react-native-web": "~0.21.0",
+ "react-native-webview": "13.15.0",
"react-native-worklets": "^0.5.1",
"react-stately": "^3.39.0",
"tailwind-variants": "^0.1.20",
@@ -2917,6 +2921,12 @@
"url": "https://github.com/sponsors/nzakas"
}
},
+ "node_modules/@ide/backoff": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@ide/backoff/-/backoff-1.0.0.tgz",
+ "integrity": "sha512-F0YfUDjvT+Mtt/R4xdl2X0EYCHMMiJqNLdxHD++jDT5ydEFIyqbCHh51Qx2E211dgZprPKhV7sHmnXKpLuvc5g==",
+ "license": "MIT"
+ },
"node_modules/@internationalized/date": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.10.1.tgz",
@@ -7528,6 +7538,19 @@
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
"license": "MIT"
},
+ "node_modules/assert": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz",
+ "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "is-nan": "^1.3.2",
+ "object-is": "^1.1.5",
+ "object.assign": "^4.1.4",
+ "util": "^0.12.5"
+ }
+ },
"node_modules/async-function": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz",
@@ -7554,7 +7577,6 @@
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
"integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"possible-typed-array-names": "^1.0.0"
@@ -7809,6 +7831,12 @@
"@babel/core": "^7.0.0"
}
},
+ "node_modules/badgin": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/badgin/-/badgin-1.2.3.tgz",
+ "integrity": "sha512-NQGA7LcfCpSzIbGRbkgjgdWkjy7HI+Th5VLxTJfW5EeaAf3fnS+xWQaQOCYiny+q6QSvxqoSO04vCx+4u++EJw==",
+ "license": "MIT"
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -8029,7 +8057,6 @@
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
"integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
- "dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.0",
@@ -8061,7 +8088,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
@@ -8738,7 +8764,6 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
- "dev": true,
"license": "MIT",
"dependencies": {
"es-define-property": "^1.0.0",
@@ -8765,7 +8790,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"define-data-property": "^1.0.1",
@@ -9719,6 +9743,15 @@
}
}
},
+ "node_modules/expo-application": {
+ "version": "7.0.8",
+ "resolved": "https://registry.npmjs.org/expo-application/-/expo-application-7.0.8.tgz",
+ "integrity": "sha512-qFGyxk7VJbrNOQWBbE09XUuGuvkOgFS9QfToaK2FdagM2aQ+x3CvGV2DuVgl/l4ZxPgIf3b/MNh9xHpwSwn74Q==",
+ "license": "MIT",
+ "peerDependencies": {
+ "expo": "*"
+ }
+ },
"node_modules/expo-asset": {
"version": "12.0.12",
"resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-12.0.12.tgz",
@@ -9762,6 +9795,17 @@
"react-native": "*"
}
},
+ "node_modules/expo-clipboard": {
+ "version": "8.0.8",
+ "resolved": "https://registry.npmjs.org/expo-clipboard/-/expo-clipboard-8.0.8.tgz",
+ "integrity": "sha512-VKoBkHIpZZDJTB0jRO4/PZskHdMNOEz3P/41tmM6fDuODMpqhvyWK053X0ebspkxiawJX9lX33JXHBCvVsTTOA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "expo": "*",
+ "react": "*",
+ "react-native": "*"
+ }
+ },
"node_modules/expo-constants": {
"version": "18.0.13",
"resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-18.0.13.tgz",
@@ -9849,6 +9893,44 @@
"expo": "*"
}
},
+ "node_modules/expo-device": {
+ "version": "8.0.10",
+ "resolved": "https://registry.npmjs.org/expo-device/-/expo-device-8.0.10.tgz",
+ "integrity": "sha512-jd5BxjaF7382JkDMaC+P04aXXknB2UhWaVx5WiQKA05ugm/8GH5uaz9P9ckWdMKZGQVVEOC8MHaUADoT26KmFA==",
+ "license": "MIT",
+ "dependencies": {
+ "ua-parser-js": "^0.7.33"
+ },
+ "peerDependencies": {
+ "expo": "*"
+ }
+ },
+ "node_modules/expo-device/node_modules/ua-parser-js": {
+ "version": "0.7.41",
+ "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.41.tgz",
+ "integrity": "sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ua-parser-js"
+ },
+ {
+ "type": "paypal",
+ "url": "https://paypal.me/faisalman"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/faisalman"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "ua-parser-js": "script/cli.js"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/expo-file-system": {
"version": "19.0.21",
"resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-19.0.21.tgz",
@@ -10019,6 +10101,26 @@
"react-native": "*"
}
},
+ "node_modules/expo-notifications": {
+ "version": "0.32.16",
+ "resolved": "https://registry.npmjs.org/expo-notifications/-/expo-notifications-0.32.16.tgz",
+ "integrity": "sha512-QQD/UA6v7LgvwIJ+tS7tSvqJZkdp0nCSj9MxsDk/jU1GttYdK49/5L2LvE/4U0H7sNBz1NZAyhDZozg8xgBLXw==",
+ "license": "MIT",
+ "dependencies": {
+ "@expo/image-utils": "^0.8.8",
+ "@ide/backoff": "^1.0.0",
+ "abort-controller": "^3.0.0",
+ "assert": "^2.0.0",
+ "badgin": "^1.1.5",
+ "expo-application": "~7.0.8",
+ "expo-constants": "~18.0.13"
+ },
+ "peerDependencies": {
+ "expo": "*",
+ "react": "*",
+ "react-native": "*"
+ }
+ },
"node_modules/expo-router": {
"version": "6.0.21",
"resolved": "https://registry.npmjs.org/expo-router/-/expo-router-6.0.21.tgz",
@@ -10481,7 +10583,6 @@
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
"integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"is-callable": "^1.2.7"
@@ -10591,7 +10692,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz",
"integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -10854,7 +10954,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"es-define-property": "^1.0.0"
@@ -11230,6 +11329,22 @@
"loose-envify": "^1.0.0"
}
},
+ "node_modules/is-arguments": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz",
+ "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-array-buffer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@@ -11334,7 +11449,6 @@
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
"integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -11447,7 +11561,6 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz",
"integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.4",
@@ -11489,6 +11602,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-nan": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz",
+ "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.0",
+ "define-properties": "^1.1.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-negative-zero": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
@@ -11541,7 +11670,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
"integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
- "dev": true,
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
@@ -11624,7 +11752,6 @@
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz",
"integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"which-typed-array": "^1.1.16"
@@ -13426,11 +13553,26 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/object-is": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz",
+ "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -13440,7 +13582,6 @@
"version": "4.1.7",
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz",
"integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.8",
@@ -13991,7 +14132,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
"integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -15214,6 +15354,20 @@
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
"license": "MIT"
},
+ "node_modules/react-native-webview": {
+ "version": "13.15.0",
+ "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.15.0.tgz",
+ "integrity": "sha512-Vzjgy8mmxa/JO6l5KZrsTC7YemSdq+qB01diA0FqjUTaWGAGwuykpJ73MDj3+mzBSlaDxAEugHzTtkUQkQEQeQ==",
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^4.0.0",
+ "invariant": "2.2.4"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*"
+ }
+ },
"node_modules/react-native-worklets": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.5.2.tgz",
@@ -15923,7 +16077,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
"integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
@@ -16076,7 +16229,6 @@
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"define-data-property": "^1.1.4",
@@ -17413,6 +17565,19 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
+ "node_modules/util": {
+ "version": "0.12.5",
+ "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
+ "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==",
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "is-arguments": "^1.0.4",
+ "is-generator-function": "^1.0.7",
+ "is-typed-array": "^1.1.3",
+ "which-typed-array": "^1.1.2"
+ }
+ },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -17639,7 +17804,6 @@
"version": "1.1.20",
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz",
"integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"available-typed-arrays": "^1.0.7",
diff --git a/package.json b/package.json
index 287f0cf..bead4be 100644
--- a/package.json
+++ b/package.json
@@ -29,8 +29,10 @@
"expo": "~54.0.31",
"expo-av": "~16.0.8",
"expo-blur": "~15.0.8",
+ "expo-clipboard": "~8.0.8",
"expo-constants": "~18.0.13",
"expo-dev-client": "~6.0.20",
+ "expo-device": "~8.0.10",
"expo-font": "~14.0.10",
"expo-haptics": "~15.0.8",
"expo-image": "~3.0.11",
@@ -38,6 +40,7 @@
"expo-linear-gradient": "~15.0.8",
"expo-linking": "~8.0.11",
"expo-navigation-bar": "~5.0.10",
+ "expo-notifications": "~0.32.16",
"expo-router": "~6.0.21",
"expo-splash-screen": "~31.0.13",
"expo-status-bar": "~3.0.9",
@@ -63,6 +66,7 @@
"react-native-screens": "~4.16.0",
"react-native-svg": "^15.15.1",
"react-native-web": "~0.21.0",
+ "react-native-webview": "13.15.0",
"react-native-worklets": "^0.5.1",
"react-stately": "^3.39.0",
"tailwind-variants": "^0.1.20",
diff --git a/screens/auth/confirm/ConfirmScreen.tsx b/screens/auth/confirm/ConfirmScreen.tsx
index 7891b27..8176765 100644
--- a/screens/auth/confirm/ConfirmScreen.tsx
+++ b/screens/auth/confirm/ConfirmScreen.tsx
@@ -21,6 +21,7 @@ import {
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 useTokenStore from '../login/lib/hook';
import ConfirmForm from './ConfirmForm';
const ConfirmScreen = () => {
@@ -28,6 +29,7 @@ const ConfirmScreen = () => {
const [phoneOTP, setPhone] = useState('');
const [error, setError] = useState('');
const { login } = useAuth();
+ const { savedToken } = useTokenStore();
const [resendTimer, setResendTimer] = useState(60);
@@ -59,6 +61,7 @@ const ConfirmScreen = () => {
onSuccess: async (res) => {
await AsyncStorage.removeItem('phone');
await AsyncStorage.setItem('access_token', res.data.data.token.access);
+ savedToken(res.data.data.token.access);
await login(res.data.data.token.access);
await AsyncStorage.setItem('refresh_token', res.data.data.token.refresh);
router.replace('/(dashboard)');
diff --git a/screens/auth/login/lib/hook.ts b/screens/auth/login/lib/hook.ts
new file mode 100644
index 0000000..8a1149b
--- /dev/null
+++ b/screens/auth/login/lib/hook.ts
@@ -0,0 +1,16 @@
+import { create } from 'zustand';
+
+type State = {
+ token: string | null;
+};
+
+type Actions = {
+ savedToken: (token: string | null) => void;
+};
+
+const useTokenStore = create((set) => ({
+ token: null,
+ savedToken: (token: string | null) => set(() => ({ token })),
+}));
+
+export default useTokenStore;
diff --git a/screens/auth/register-confirm/ConfirmScreen.tsx b/screens/auth/register-confirm/ConfirmScreen.tsx
index 7730b46..91ba39c 100644
--- a/screens/auth/register-confirm/ConfirmScreen.tsx
+++ b/screens/auth/register-confirm/ConfirmScreen.tsx
@@ -21,6 +21,7 @@ import {
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 useTokenStore from '../login/lib/hook';
import ConfirmForm from './ConfirmForm';
const RegisterConfirmScreen = () => {
@@ -28,6 +29,7 @@ const RegisterConfirmScreen = () => {
const [phoneOTP, setPhone] = useState('');
const [error, setError] = useState('');
const { login } = useAuth();
+ const { savedToken } = useTokenStore();
const [resendTimer, setResendTimer] = useState(60);
@@ -59,6 +61,7 @@ const RegisterConfirmScreen = () => {
onSuccess: async (res) => {
await AsyncStorage.removeItem('phone');
await AsyncStorage.setItem('access_token', res.data.data.token.access);
+ savedToken(res.data.data.token.access);
await AsyncStorage.setItem('refresh_token', res.data.data.token.refresh);
await login(res.data.data.token.access);
router.replace('/(dashboard)');
diff --git a/screens/create-ads/ui/CreateAdsScreens.tsx b/screens/create-ads/ui/CreateAdsScreens.tsx
index 9c9893e..e6fd0bd 100644
--- a/screens/create-ads/ui/CreateAdsScreens.tsx
+++ b/screens/create-ads/ui/CreateAdsScreens.tsx
@@ -209,7 +209,7 @@ export default function CreateAdsScreens() {
behavior="padding"
style={[styles.safeArea, isDark ? styles.darkBg : styles.lightBg]}
>
-
+
{currentStep === 1
? t("E'lon ma'lumotlari")
@@ -235,43 +235,47 @@ export default function CreateAdsScreens() {
/>
)}
{currentStep === 4 && }
+
+ {currentStep > 1 && currentStep !== 4 && (
+ setCurrentStep((s) => s - 1)}
+ >
+
+ {t('Orqaga')}
+
+
+ )}
+
+ {
+ 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();
+ }}
+ >
+
+ {currentStep === 3
+ ? t('Yaratish')
+ : currentStep === 4
+ ? t("To'lash")
+ : t('Keyingisi')}
+
+
+
{/* FOOTER */}
-
- {currentStep > 1 && currentStep !== 4 && (
- setCurrentStep((s) => s - 1)}
- >
-
- {t('Orqaga')}
-
-
- )}
-
- {
- 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();
- }}
- >
-
- {currentStep === 3 ? t('Yaratish') : currentStep === 4 ? t("To'lash") : t('Keyingisi')}
-
-
-
{/* PAYMENT BOTTOM SHEET */}
> {
+ const res = await httpClient.get(API_URLS.Goverment_Service, { params });
+ return res;
+ },
+};
diff --git a/screens/e-services/lib/types.ts b/screens/e-services/lib/types.ts
new file mode 100644
index 0000000..3e7e0e3
--- /dev/null
+++ b/screens/e-services/lib/types.ts
@@ -0,0 +1,21 @@
+export interface GovermentServiceData {
+ status: boolean;
+ data: {
+ links: {
+ previous: null | string;
+ next: null | string;
+ };
+ total_items: number;
+ total_pages: number;
+ page_size: number;
+ current_page: number;
+ results: GovermentServiceDataRes[];
+ };
+}
+
+export interface GovermentServiceDataRes {
+ id: number;
+ name: string;
+ url: string;
+ logo: string;
+}
diff --git a/screens/e-services/ui/EServices.tsx b/screens/e-services/ui/EServices.tsx
new file mode 100644
index 0000000..0343d57
--- /dev/null
+++ b/screens/e-services/ui/EServices.tsx
@@ -0,0 +1,199 @@
+// EServicesScreen.tsx
+import { useTheme } from '@/components/ThemeContext';
+import { useInfiniteQuery } from '@tanstack/react-query';
+import { Image } from 'expo-image';
+import { ChevronLeft, XIcon } from 'lucide-react-native';
+import React, { useCallback, useRef, 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';
+import { eservices_api } from '../lib/api';
+
+const { width: SCREEN_WIDTH } = Dimensions.get('window');
+const PAGE_SIZE = 10;
+
+export interface GovermentServiceDataRes {
+ id: number;
+ name: string;
+ url: string;
+ logo: string;
+}
+
+export default function EServicesScreen() {
+ const { isDark } = useTheme();
+ const [webUrl, setWebUrl] = useState(null);
+ const [modalVisible, setModalVisible] = useState(false);
+ const webviewRef = useRef(null); // WebView ref for goBack
+
+ const { data, isLoading, isError, fetchNextPage, hasNextPage, isFetchingNextPage } =
+ useInfiniteQuery({
+ queryKey: ['goverment_service'],
+ queryFn: async ({ pageParam = 1 }) => {
+ const response = await eservices_api.list({
+ page: pageParam,
+ page_size: PAGE_SIZE,
+ });
+ 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 openWebView = (url: string) => {
+ setWebUrl(url);
+ setModalVisible(true);
+ };
+
+ const renderItem = useCallback(
+ ({ item }: { item: GovermentServiceDataRes }) => (
+ openWebView(item.url)}
+ >
+
+ {item.name}
+
+ ),
+ [isDark]
+ );
+
+ if (isLoading) {
+ return (
+
+
+
+ );
+ }
+
+ if (isError) {
+ return (
+
+ Xatolik yuz berdi
+
+ );
+ }
+
+ return (
+
+ item.id.toString()}
+ renderItem={renderItem}
+ numColumns={2}
+ columnWrapperStyle={{ justifyContent: 'space-between', marginBottom: 12 }}
+ contentContainerStyle={{ padding: 16 }}
+ onEndReached={() => hasNextPage && fetchNextPage()}
+ onEndReachedThreshold={0.4}
+ ListFooterComponent={
+ isFetchingNextPage ? : null
+ }
+ showsVerticalScrollIndicator={false}
+ />
+
+ {/* WebView Modal */}
+ {/* WebView Modal */}
+
+
+ {/* Header */}
+
+ {/* Back tugmasi */}
+ {
+ if (webviewRef.current) webviewRef.current.goBack();
+ }}
+ hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
+ >
+
+
+
+ {/* Close tugmasi */}
+ setModalVisible(false)}
+ hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
+ >
+
+
+
+
+ {/* WebView */}
+ {webUrl && (
+ (
+
+ )}
+ />
+ )}
+
+
+
+ );
+}
+
+const CARD_WIDTH = (SCREEN_WIDTH - 48) / 2;
+
+const styles = StyleSheet.create({
+ center: { flex: 1, justifyContent: 'center', alignItems: 'center' },
+ card: {
+ width: CARD_WIDTH,
+ borderRadius: 12,
+ padding: 12,
+ alignItems: 'center',
+ },
+ logo: {
+ width: 60,
+ height: 60,
+ marginBottom: 8,
+ },
+ name: {
+ fontSize: 14,
+ fontWeight: '600',
+ textAlign: 'center',
+ },
+ darkShadow: {
+ shadowColor: '#000',
+ shadowOffset: { width: 0, height: 2 },
+ shadowOpacity: 0.4,
+ shadowRadius: 6,
+ elevation: 3,
+ },
+ lightShadow: {
+ shadowColor: '#000',
+ shadowOffset: { width: 0, height: 2 },
+ shadowOpacity: 0.1,
+ shadowRadius: 4,
+ elevation: 2,
+ },
+});
diff --git a/screens/home/lib/types.ts b/screens/home/lib/types.ts
index a508d38..b61dfe0 100644
--- a/screens/home/lib/types.ts
+++ b/screens/home/lib/types.ts
@@ -79,6 +79,7 @@ export interface CountryBody {
export interface CountryResponse {
id: number;
name: string;
+ flag: string;
companies: {
id: number;
company_name: string;
diff --git a/screens/home/ui/HomeScreen.tsx b/screens/home/ui/HomeScreen.tsx
index 3ea7eb7..45f386f 100644
--- a/screens/home/ui/HomeScreen.tsx
+++ b/screens/home/ui/HomeScreen.tsx
@@ -15,6 +15,7 @@ import React, { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
ActivityIndicator,
+ Modal,
ScrollView,
StyleSheet,
Text,
@@ -22,7 +23,8 @@ import {
TouchableOpacity,
View,
} from 'react-native';
-import { RefreshControl } from 'react-native-gesture-handler';
+import { GestureHandlerRootView, RefreshControl } from 'react-native-gesture-handler';
+import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
function Loading() {
return (
@@ -84,83 +86,96 @@ export default function HomeScreen() {
}
}, [activeTab, query]);
- if (showFilter && step === 'filter') {
- return (
- setShowFilter(false)} setStep={setStep} setFiltered={setFiltered} />
- );
- }
+ const handleCloseFilter = () => {
+ setShowFilter(false);
+ setStep('filter');
+ };
+ // Show filtered items if filter was applied
if (showFilter && step === 'items') {
return (
- {
- setShowFilter(false);
- setStep('filter');
- }}
- />
+
+
+
);
}
return (
-
- }
- >
-
+
+
+ }
+ >
+
-
- {/* Qidiruv va filter */}
-
-
-
-
-
- setShowFilter(true)}
- activeOpacity={0.7}
- >
-
-
-
-
-
-
- {error && (
-
- {t("Ma'lumot yuklashda xatolik")}
-
- {t('Qayta urinish')}
+
+ {/* Qidiruv va filter */}
+
+
+
+
+
+ setShowFilter(true)}
+ activeOpacity={0.7}
+ >
+
- )}
- {isLoading && !refreshing && }
+
- {!isLoading && RenderedView}
-
-
+ {error && (
+
+ {t("Ma'lumot yuklashda xatolik")}
+
+ {t('Qayta urinish')}
+
+
+ )}
+
+ {isLoading && !refreshing && }
+
+ {!isLoading && RenderedView}
+
+
+
+ {/* Filter Modal */}
+
+
+
+
+
+
+
+
+
);
}
diff --git a/screens/profile/lib/api.ts b/screens/profile/lib/api.ts
index 2312dc9..2d36ea0 100644
--- a/screens/profile/lib/api.ts
+++ b/screens/profile/lib/api.ts
@@ -7,6 +7,7 @@ import {
MyAdsData,
MyAdsDataRes,
MyBonusesData,
+ NotificationListRes,
UserInfoResponseData,
} from './type';
@@ -35,6 +36,8 @@ export const user_api = {
person_type: 'employee' | 'legal_entity' | 'ytt' | 'band';
phone: string;
activate_types: number[];
+ age: number;
+ gender: 'male' | 'female';
}) {
const res = await httpClient.patch(API_URLS.User_Update, body);
return res;
@@ -105,4 +108,32 @@ export const user_api = {
const res = await httpClient.get(API_URLS.Detail_Products(id));
return res;
},
+
+ async my_referrals(params: { page: number; page_size: number }) {
+ const res = await httpClient.get(API_URLS.My_Refferals, { params });
+ return res;
+ },
+
+ async create_referral(body: {
+ code: string;
+ referral_share: number;
+ description: string;
+ is_agent: boolean;
+ }) {
+ const res = await httpClient.post(API_URLS.My_Refferals, body);
+ return res;
+ },
+
+ async notification_list(params: {
+ page: number;
+ page_size: number;
+ }): Promise> {
+ const res = await httpClient.get(API_URLS.Notification_List, { params });
+ return res;
+ },
+
+ async is_ready_id(id: number) {
+ const res = await httpClient.post(API_URLS.Notification_Ready(id));
+ return res;
+ },
};
diff --git a/screens/profile/lib/type.ts b/screens/profile/lib/type.ts
index caf1544..79c0445 100644
--- a/screens/profile/lib/type.ts
+++ b/screens/profile/lib/type.ts
@@ -77,6 +77,8 @@ export interface UserInfoResponseData {
company_image: null | string;
address: null | string;
district: number;
+ age: null | number;
+ gender: 'male' | 'female' | null;
parent: null | string;
user_tg_ids: number[];
};
@@ -166,3 +168,30 @@ export interface MyBonusesDataRes {
percent: number;
created_at: string;
}
+
+export interface NotificationListRes {
+ status: boolean;
+ data: {
+ links: {
+ previous: null | string;
+ next: null | string;
+ };
+ total_items: number;
+ total_pages: number;
+ page_size: number;
+ current_page: number;
+ unread_count: number;
+
+ results: NotificationListDataRes[];
+ };
+}
+
+export interface NotificationListDataRes {
+ id: number;
+ title: string;
+ description: string;
+ is_send: boolean;
+ is_read: boolean;
+ created_at: string;
+ updated_at: string;
+}
diff --git a/screens/profile/ui/CreateReferrals.tsx b/screens/profile/ui/CreateReferrals.tsx
new file mode 100644
index 0000000..57b8456
--- /dev/null
+++ b/screens/profile/ui/CreateReferrals.tsx
@@ -0,0 +1,233 @@
+import { useTheme } from '@/components/ThemeContext';
+import { useMutation, useQueryClient } from '@tanstack/react-query';
+import { useRouter } from 'expo-router';
+import { ArrowLeft } from 'lucide-react-native';
+import React, { useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import {
+ ActivityIndicator,
+ Pressable,
+ ScrollView,
+ StyleSheet,
+ Switch,
+ Text,
+ TextInput,
+ ToastAndroid,
+ View,
+} from 'react-native';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { user_api } from '../lib/api';
+
+type FormType = {
+ code: string;
+ referral_share: string;
+ description: string;
+ is_agent: boolean;
+};
+
+export default function CreateReferrals() {
+ const { isDark } = useTheme();
+ const { t } = useTranslation();
+ const router = useRouter();
+ const queryClient = useQueryClient();
+ const [form, setForm] = useState({
+ code: '',
+ referral_share: '',
+ description: '',
+ is_agent: false,
+ });
+
+ const { mutate, isPending } = useMutation({
+ mutationFn: (body: {
+ code: string;
+ referral_share: number;
+ description: string;
+ is_agent: boolean;
+ }) => user_api.create_referral(body),
+ onSuccess: () => {
+ ToastAndroid.show(t('Referral yaratildi'), ToastAndroid.SHORT);
+ queryClient.refetchQueries({ queryKey: ['my_referrals'] });
+ router.back();
+ },
+ onError: () => {
+ ToastAndroid.show(t('Xatolik yuz berdi'), ToastAndroid.SHORT);
+ },
+ });
+
+ const [errors, setErrors] = useState({});
+
+ const update = (key: keyof FormType, value: any) => setForm((p) => ({ ...p, [key]: value }));
+
+ 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';
+
+ if (form.is_agent) {
+ if (!form.referral_share || Number(form.referral_share) <= 0)
+ e.referral_share = 'Agent uchun foiz majburiy';
+ }
+
+ setErrors(e);
+ return Object.keys(e).length === 0;
+ };
+
+ const handleSave = () => {
+ if (!validate()) return;
+
+ const payload = {
+ code: form.code,
+ referral_share: form.is_agent ? Number(form.referral_share) : 0,
+ description: form.description,
+ is_agent: form.is_agent,
+ };
+
+ mutate(payload);
+ };
+
+ return (
+
+ {/* HEADER */}
+
+ router.back()}>
+
+
+
+
+ {t('Referral yaratish')}
+
+
+
+ {isPending ? (
+
+ ) : (
+ {t('Saqlash')}
+ )}
+
+
+
+
+ {/* NOM */}
+
+ {t('Referral nomi')}
+
+
+ update('code', v)}
+ />
+
+ {errors.code && {t(errors.code)}}
+
+ {/* TAVSIF */}
+ {t('Tavsif')}
+
+ update('description', v)}
+ />
+
+ {errors.description && {t(errors.description)}}
+
+ {/* AGENT SWITCH */}
+
+
+ {t('Agentmi?')}
+
+ {
+ update('is_agent', v);
+ if (!v) update('referral_share', '');
+ }}
+ />
+
+
+ {/* 👉 FOIZ FAQAT AGENT YOQILGANDA */}
+ {form.is_agent && (
+ <>
+
+ {t('Referral foizi (%)')}
+
+
+ {
+ // faqat 1–5 oralig‘ini qabul qiladi
+ if (v === '') {
+ update('referral_share', '');
+ return;
+ }
+
+ const num = Number(v);
+ if (num >= 1 && num <= 5) {
+ update('referral_share', v);
+ }
+ }}
+ />
+
+ {errors.referral_share && {t(errors.referral_share)}}
+ >
+ )}
+
+
+ );
+}
+
+const theme = (isDark: boolean) => ({
+ backgroundColor: isDark ? '#1e293b' : '#fff',
+ borderColor: isDark ? '#334155' : '#e2e8f0',
+});
+
+const styles = StyleSheet.create({
+ header: {
+ padding: 16,
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ elevation: 3,
+ },
+ headerTitle: { fontSize: 18, fontWeight: '700' },
+ save: { color: '#3b82f6', fontSize: 16, fontWeight: '600' },
+
+ container: { padding: 16, gap: 10 },
+ label: { fontSize: 15, fontWeight: '700' },
+ error: { color: '#ef4444', fontSize: 13, marginLeft: 6 },
+
+ inputBox: {
+ flexDirection: 'row',
+ borderRadius: 16,
+ borderWidth: 1,
+ paddingHorizontal: 6,
+ height: 56,
+ },
+ textArea: { height: 120, alignItems: 'flex-start' },
+ input: {
+ flex: 1,
+ fontSize: 16,
+ },
+
+ switchRow: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ marginTop: 10,
+ },
+});
diff --git a/screens/profile/ui/ManualTab.tsx b/screens/profile/ui/ManualTab.tsx
new file mode 100644
index 0000000..7b03eae
--- /dev/null
+++ b/screens/profile/ui/ManualTab.tsx
@@ -0,0 +1,190 @@
+import { useTheme } from '@/components/ThemeContext';
+import { ResizeMode, Video } from 'expo-av';
+import { useRouter } from 'expo-router';
+import { ArrowLeft } from 'lucide-react-native';
+import React, { useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import {
+ Dimensions,
+ Image,
+ Pressable,
+ ScrollView,
+ StyleSheet,
+ Text,
+ TouchableOpacity,
+ View,
+} from 'react-native';
+
+const { width } = Dimensions.get('window');
+
+type ManualStep = {
+ title: string;
+ text: string;
+ image?: any;
+};
+
+export function ManualTab() {
+ const { isDark } = useTheme();
+ const { t } = useTranslation();
+ const router = useRouter();
+ const [videoLang, setVideoLang] = useState<'uz' | 'ru' | 'en'>('uz');
+
+ const theme = {
+ background: isDark ? '#0f172a' : '#f8fafc',
+ cardBg: isDark ? '#1e293b' : '#ffffff',
+ text: isDark ? '#ffffff' : '#0f172a',
+ textSecondary: isDark ? '#94a3b8' : '#64748b',
+ primary: '#3b82f6',
+ };
+
+ const steps: ManualStep[] = [
+ {
+ title: "Foydalanish qo'lanmasi",
+ text: "Foydalanish qo'lanmasi",
+ image: require('@/assets/manual/step1.jpg'),
+ },
+ {
+ title: "Ro'yxatdan o'tish (Registratsiya) – 1 daqiqa ichida",
+ text: "Platformaga kirish uchun avval ro'yxatdan o'ting.",
+ image: require('@/assets/manual/step2.jpg'),
+ },
+ {
+ title: "Profilni to'ldirish va tasdiqlash",
+ text: "Muhim: Ro'yxatdan o'tgandan keyin profil to'liq bo'lishi kerak — aks holda platforma cheklangan rejimda ishlaydi.",
+ image: require('@/assets/manual/step3.jpg'),
+ },
+ {
+ title: "Xodimlarni qo'shish",
+ text: "Profil ichida Xodimlar bo'limiga o'ting va + tugmasi orqali xodim qo'shing.",
+ image: require('@/assets/manual/step4.jpg'),
+ },
+ {
+ title: "E'lon berish va o'z mahsulot/xizmatlaringizni ko'rsatish",
+ text: "Pastki menyudan E'lonlar bo'limiga o'ting va yangi e'lon yarating.",
+ image: require('@/assets/manual/step5.jpg'),
+ },
+ {
+ title: 'Mijozlarni qidirish va topish',
+ text: 'Filtrdan foydalanib kerakli kompaniyalarni toping va profiliga kirib xabar yuboring.',
+ image: require('@/assets/manual/step6.jpg'),
+ },
+ {
+ title: 'Muhim maslahatlar va xavfsizlik',
+ text: 'Faqat tasdiqlangan profillarga ishonch bilan murojaat qiling.',
+ image: require('@/assets/manual/step7.jpg'),
+ },
+ ];
+
+ const videos: Record<'uz' | 'ru' | 'en', any> = {
+ 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 handleVideoChange = (lang: 'uz' | 'ru' | 'en') => {
+ setVideoLang(lang);
+ };
+
+ return (
+
+
+ router.push('/profile')}>
+
+
+
+
+ {t("Foydalanish qo'lanmasi")}
+
+
+
+ {steps.map((step, index) => (
+
+ {t(step.title)}
+
+ {t(step.text)}
+
+ {step.image && }
+
+ ))}
+
+ {/* Video bo'limi */}
+
+ {t("Qo'llanma video")}
+
+ {/* Til tanlash tugmalari */}
+
+ handleVideoChange('uz')}
+ >
+ O'zbek
+
+
+ handleVideoChange('ru')}
+ >
+ Русский
+
+
+ handleVideoChange('en')}
+ >
+ English
+
+
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: { flex: 1, padding: 16 },
+ card: { borderRadius: 16, padding: 16, marginBottom: 16 },
+ headerTitle: { fontSize: 18, fontWeight: '700', lineHeight: 24 },
+ text: { fontSize: 14, lineHeight: 20 },
+ image: { width: width - 64, height: 200, marginTop: 12, borderRadius: 12 },
+ video: { width: width - 64, height: 320, marginTop: 12, borderRadius: 12 },
+ buttonRow: { flexDirection: 'row', justifyContent: 'space-around', marginVertical: 12 },
+ langButton: {
+ paddingVertical: 8,
+ paddingHorizontal: 16,
+ borderRadius: 8,
+ borderWidth: 1,
+ },
+ header: {
+ padding: 16,
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'flex-start',
+ gap: 10,
+ elevation: 3,
+ },
+});
diff --git a/screens/profile/ui/NotificationTab.tsx b/screens/profile/ui/NotificationTab.tsx
new file mode 100644
index 0000000..8b5438a
--- /dev/null
+++ b/screens/profile/ui/NotificationTab.tsx
@@ -0,0 +1,407 @@
+import { useTheme } from '@/components/ThemeContext';
+import { useInfiniteQuery, useMutation, useQueryClient } from '@tanstack/react-query';
+import { router } from 'expo-router';
+import { ArrowLeft } from 'lucide-react-native';
+import { useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import {
+ ActivityIndicator,
+ Animated,
+ FlatList,
+ Pressable,
+ StyleSheet,
+ Text,
+ TouchableOpacity,
+ View,
+} from 'react-native';
+import { user_api } from '../lib/api';
+import { NotificationListDataRes } from '../lib/type';
+
+const PAGE_SIZE = 10;
+
+export function NotificationTab() {
+ const { isDark } = useTheme();
+ const { t } = useTranslation();
+
+ const { data, isLoading, isError, fetchNextPage, hasNextPage, isFetchingNextPage, refetch } =
+ useInfiniteQuery({
+ queryKey: ['notifications-list'],
+ queryFn: async ({ pageParam = 1 }) => {
+ const response = await user_api.notification_list({
+ page: pageParam,
+ page_size: PAGE_SIZE,
+ });
+ return response.data.data;
+ },
+ getNextPageParam: (lastPage) =>
+ lastPage.current_page < lastPage.total_pages ? lastPage.current_page + 1 : undefined,
+ initialPageParam: 1,
+ });
+ const notifications = data?.pages.flatMap((p) => p.results) ?? [];
+
+ if (isLoading) {
+ return (
+
+
+
+
+
+ );
+ }
+
+ if (isError) {
+ return (
+
+
+ {t('Xatolik yuz berdi')}
+ {t("Bildirishnomalarni yuklashda muammo bo'ldi")}
+ refetch()}>
+ {t('Qayta urinish')}
+
+
+
+ );
+ }
+
+ return (
+
+
+ router.push('/profile')}>
+
+
+
+
+ {t('Bildirishnomalar')}
+
+
+ item.id.toString()}
+ contentContainerStyle={styles.listContent}
+ showsVerticalScrollIndicator={false}
+ renderItem={({ item, index }) => }
+ onEndReached={() => {
+ if (hasNextPage && !isFetchingNextPage) {
+ fetchNextPage();
+ }
+ }}
+ onEndReachedThreshold={0.5}
+ ListFooterComponent={
+ isFetchingNextPage ? (
+
+ ) : null
+ }
+ refreshing={isLoading}
+ onRefresh={refetch}
+ />
+
+ );
+}
+
+/* ---------------- CARD ---------------- */
+
+function NotificationCard({ item }: { item: NotificationListDataRes }) {
+ const queryClient = useQueryClient();
+ const { t } = useTranslation();
+ const [scaleAnim] = useState(new Animated.Value(1));
+ const { mutate } = useMutation({
+ mutationFn: (id: number) => user_api.is_ready_id(id),
+ onSuccess: () => {
+ queryClient.refetchQueries({ queryKey: ['notification-list'] });
+ },
+ });
+
+ const handlePressIn = () => {
+ Animated.spring(scaleAnim, {
+ toValue: 0.96,
+ useNativeDriver: true,
+ }).start();
+ };
+
+ const handlePressOut = () => {
+ Animated.spring(scaleAnim, {
+ toValue: 1,
+ friction: 4,
+ tension: 50,
+ useNativeDriver: true,
+ }).start();
+ };
+
+ const handlePress = (id: number) => {
+ if (!item.is_read) {
+ mutate(id);
+ }
+ };
+
+ return (
+
+ handlePress(item.id)}
+ style={[styles.card, !item.is_read && styles.unreadCard]}
+ >
+
+
+
+ {item.title}
+
+ {!item.is_read && }
+
+
+
+ {item.description}
+
+
+ {formatDate(item.created_at, t)}
+
+
+
+ );
+}
+
+/* ---------------- HELPERS ---------------- */
+
+function formatDate(date: string, t: any) {
+ const now = new Date();
+ const notifDate = new Date(date);
+ const diffMs = now.getTime() - notifDate.getTime();
+ const diffMins = Math.floor(diffMs / 60000);
+ const diffHours = Math.floor(diffMs / 3600000);
+ const diffDays = Math.floor(diffMs / 86400000);
+
+ if (diffMins < 1) return 'Hozir';
+ if (diffMins < 60) return `${diffMins} ${t('daqiqa oldin')}`;
+ if (diffHours < 24) return `${diffHours} ${t('soat oldin')}`;
+ if (diffDays < 7) return `${diffDays} ${t('kun oldin')}`;
+
+ return notifDate.toLocaleDateString('uz-UZ', {
+ day: 'numeric',
+ month: 'short',
+ });
+}
+
+/* ---------------- STYLES ---------------- */
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#0a0c17',
+ },
+ listContent: {
+ padding: 16,
+ paddingBottom: 32,
+ },
+
+ /* Card Styles */
+ card: {
+ flexDirection: 'row',
+ backgroundColor: '#121826',
+ padding: 16,
+ borderRadius: 24,
+ borderWidth: 1,
+ borderColor: 'rgba(60, 70, 90, 0.18)',
+ shadowColor: '#000',
+ shadowOffset: { width: 0, height: 8 },
+ shadowOpacity: 0.35,
+ shadowRadius: 16,
+ elevation: 10,
+ overflow: 'hidden',
+ },
+ unreadCard: {
+ backgroundColor: '#1a2236',
+ borderColor: 'rgba(59, 130, 246, 0.4)',
+ shadowColor: '#3b82f6',
+ shadowOpacity: 0.28,
+ shadowRadius: 20,
+ elevation: 12,
+ },
+ iconContainer: {
+ width: 56,
+ height: 56,
+ borderRadius: 20,
+ backgroundColor: 'rgba(30, 38, 56, 0.7)',
+ alignItems: 'center',
+ justifyContent: 'center',
+ marginRight: 16,
+ },
+ unreadIconContainer: {
+ backgroundColor: 'rgba(59, 130, 246, 0.22)',
+ },
+ iconText: {
+ fontSize: 32,
+ },
+ cardContent: {
+ flex: 1,
+ justifyContent: 'center',
+ },
+ cardHeader: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ marginBottom: 6,
+ },
+ cardTitle: {
+ flex: 1,
+ color: '#d1d5db',
+ fontSize: 16.5,
+ fontWeight: '600',
+ letterSpacing: -0.1,
+ },
+ unreadTitle: {
+ color: '#f1f5f9',
+ fontWeight: '700',
+ },
+ unreadIndicator: {
+ width: 10,
+ height: 10,
+ borderRadius: 5,
+ backgroundColor: '#3b82f6',
+ marginLeft: 8,
+ shadowColor: '#3b82f6',
+ shadowOffset: { width: 0, height: 0 },
+ shadowOpacity: 0.7,
+ shadowRadius: 6,
+ },
+ cardMessage: {
+ color: '#9ca3af',
+ fontSize: 14.5,
+ lineHeight: 21,
+ marginBottom: 8,
+ },
+ cardTime: {
+ color: '#64748b',
+ fontSize: 12.5,
+ fontWeight: '500',
+ opacity: 0.9,
+ },
+
+ /* Loading State */
+ loadingContainer: {
+ flex: 1,
+ backgroundColor: '#0a0c17',
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ loadingContent: {
+ alignItems: 'center',
+ padding: 32,
+ },
+ loadingText: {
+ marginTop: 16,
+ color: '#94a3b8',
+ fontSize: 15,
+ fontWeight: '500',
+ },
+
+ /* Error State */
+ errorContainer: {
+ flex: 1,
+ backgroundColor: '#0a0c17',
+ alignItems: 'center',
+ justifyContent: 'center',
+ padding: 24,
+ },
+ errorContent: {
+ alignItems: 'center',
+ backgroundColor: '#151b2e',
+ padding: 32,
+ borderRadius: 24,
+ maxWidth: 320,
+ },
+ errorIconContainer: {
+ width: 80,
+ height: 80,
+ borderRadius: 40,
+ backgroundColor: '#1e2638',
+ alignItems: 'center',
+ justifyContent: 'center',
+ marginBottom: 20,
+ },
+ errorIcon: {
+ fontSize: 40,
+ },
+ errorTitle: {
+ color: '#ef4444',
+ fontSize: 20,
+ fontWeight: '700',
+ marginBottom: 8,
+ },
+ errorMessage: {
+ color: '#94a3b8',
+ fontSize: 14,
+ textAlign: 'center',
+ marginBottom: 24,
+ lineHeight: 20,
+ },
+ retryButton: {
+ backgroundColor: '#3b82f6',
+ paddingHorizontal: 32,
+ paddingVertical: 14,
+ borderRadius: 12,
+ shadowColor: '#3b82f6',
+ shadowOffset: { width: 0, height: 4 },
+ shadowOpacity: 0.3,
+ shadowRadius: 8,
+ elevation: 5,
+ },
+ retryButtonText: {
+ color: '#ffffff',
+ fontSize: 15,
+ fontWeight: '700',
+ },
+
+ /* Empty State */
+ emptyContainer: {
+ flex: 1,
+ backgroundColor: '#0a0c17',
+ alignItems: 'center',
+ justifyContent: 'center',
+ padding: 24,
+ },
+ emptyContent: {
+ alignItems: 'center',
+ maxWidth: 300,
+ },
+ emptyIconContainer: {
+ width: 100,
+ height: 100,
+ borderRadius: 50,
+ backgroundColor: '#151b2e',
+ alignItems: 'center',
+ justifyContent: 'center',
+ marginBottom: 24,
+ borderWidth: 2,
+ borderColor: '#1e2638',
+ },
+ emptyIcon: {
+ fontSize: 50,
+ },
+ emptyTitle: {
+ color: '#ffffff',
+ fontSize: 22,
+ fontWeight: '700',
+ marginBottom: 12,
+ textAlign: 'center',
+ },
+ emptyMessage: {
+ color: '#94a3b8',
+ fontSize: 15,
+ textAlign: 'center',
+ lineHeight: 22,
+ },
+ header: {
+ padding: 16,
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'flex-start',
+ gap: 10,
+ elevation: 3,
+ },
+ headerTitle: { fontSize: 18, fontWeight: '700', lineHeight: 24 },
+});
diff --git a/screens/profile/ui/PersonalInfoTab.tsx b/screens/profile/ui/PersonalInfoTab.tsx
index c6e0ef1..2d47826 100644
--- a/screens/profile/ui/PersonalInfoTab.tsx
+++ b/screens/profile/ui/PersonalInfoTab.tsx
@@ -4,11 +4,9 @@ import { Edit2, Plus } from 'lucide-react-native';
import { useEffect, useState } from 'react';
import { Alert, Modal, Pressable, StyleSheet, Text, TextInput, View } from 'react-native';
import { user_api } from '../lib/api';
-import { useProfileData } from '../lib/ProfileDataContext';
import { UserInfoResponseData } from '../lib/type';
export function PersonalInfoTab() {
- const { personalInfo, updatePersonalInfo } = useProfileData();
const [editModalVisible, setEditModalVisible] = useState(false);
const [addFieldModalVisible, setAddFieldModalVisible] = useState(false);
const [newField, setNewField] = useState('');
@@ -27,7 +25,6 @@ export function PersonalInfoTab() {
queryKey: ['get_me'],
queryFn: () => user_api.getMe(),
select: (res) => {
- setEditData(res.data.data);
setPhone(res.data.data.phone || '');
return res;
},
@@ -51,8 +48,11 @@ export function PersonalInfoTab() {
code: string;
};
}[];
- phone: string;
person_type: 'employee' | 'legal_entity' | 'ytt' | 'band';
+ phone: string;
+ activate_types: number[];
+ age: number;
+ gender: 'male' | 'female';
}) => user_api.updateMe(body),
onSuccess: (res) => {
queryClient.invalidateQueries({ queryKey: ['get_me'] });
@@ -102,9 +102,6 @@ export function PersonalInfoTab() {
const handleAddField = () => {
if (newField.trim()) {
- updatePersonalInfo({
- activityFields: [...personalInfo.activityFields, newField.trim()],
- });
setNewField('');
setAddFieldModalVisible(false);
}
@@ -116,18 +113,12 @@ export function PersonalInfoTab() {
{
text: 'Olib tashlash',
style: 'destructive',
- onPress: () => {
- updatePersonalInfo({
- activityFields: personalInfo.activityFields.filter((f) => f !== field),
- });
- },
},
]);
};
useEffect(() => {
if (me?.data.data) {
- setEditData(me.data.data);
setPhone(me.data.data.phone || '');
}
}, [me]);
@@ -201,7 +192,7 @@ export function PersonalInfoTab() {
Ism
setEditData((prev) => prev && { ...prev, director_full_name: text })
}
diff --git a/screens/profile/ui/ProfileScreen.tsx b/screens/profile/ui/ProfileScreen.tsx
index 02a1a1c..67e27a5 100644
--- a/screens/profile/ui/ProfileScreen.tsx
+++ b/screens/profile/ui/ProfileScreen.tsx
@@ -1,9 +1,13 @@
import { useTheme } from '@/components/ThemeContext';
import { useGlobalRefresh } from '@/components/ui/RefreshContext';
+import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'expo-router';
import {
Award,
+ Bell,
+ BookAIcon,
ChevronRight,
+ HandCoins,
Megaphone,
Package,
Settings,
@@ -13,6 +17,7 @@ import {
import { useTranslation } from 'react-i18next';
import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { RefreshControl } from 'react-native-gesture-handler';
+import { user_api } from '../lib/api';
export default function Profile() {
const router = useRouter();
@@ -20,12 +25,18 @@ export default function Profile() {
const { isDark } = useTheme();
const { t } = useTranslation();
+ const { data: me, isLoading } = useQuery({
+ queryKey: ['get_me'],
+ queryFn: () => user_api.getMe(),
+ });
+
const sections = [
{
title: 'Shaxsiy',
items: [
{ icon: User, label: "Shaxsiy ma'lumotlar", route: '/profile/personal-info' },
{ icon: Users, label: 'Xodimlar', route: '/profile/employees' },
+ { icon: Bell, label: 'Bildirishnomalar', route: '/profile/notification' },
],
},
{
@@ -34,11 +45,23 @@ export default function Profile() {
{ icon: Megaphone, label: "E'lonlar", route: '/profile/my-ads' },
{ icon: Award, label: 'Bonuslar', route: '/profile/bonuses' },
{ icon: Package, label: 'Xizmatlar', route: '/profile/products' },
+ ...(me?.data.data.can_create_referral
+ ? [
+ {
+ icon: HandCoins,
+ label: 'Refferallarim',
+ route: '/profile/my-referrals',
+ },
+ ]
+ : []),
],
},
{
title: 'Sozlamalar',
- items: [{ icon: Settings, label: 'Sozlamalar', route: '/profile/settings' }],
+ items: [
+ { icon: Settings, label: 'Sozlamalar', route: '/profile/settings' },
+ { icon: BookAIcon, label: "Foydalanish qo'lanmasi", route: '/profile/manual' },
+ ],
},
];
diff --git a/screens/profile/ui/RefferallsTab.tsx b/screens/profile/ui/RefferallsTab.tsx
new file mode 100644
index 0000000..f5ba03d
--- /dev/null
+++ b/screens/profile/ui/RefferallsTab.tsx
@@ -0,0 +1,203 @@
+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 React, { useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import {
+ ActivityIndicator,
+ FlatList,
+ Pressable,
+ RefreshControl,
+ StyleSheet,
+ Text,
+ ToastAndroid,
+ TouchableOpacity,
+ View,
+} from 'react-native';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { user_api } from '../lib/api';
+
+const PAGE_SIZE = 10;
+
+export function ReferralsTab() {
+ const router = useRouter();
+ const { isDark } = useTheme();
+ const { t } = useTranslation();
+ const [refreshing, setRefreshing] = useState(false);
+
+ const theme = {
+ background: isDark ? '#0f172a' : '#f8fafc',
+ cardBg: isDark ? '#1e293b' : '#ffffff',
+ text: isDark ? '#ffffff' : '#0f172a',
+ subText: isDark ? '#94a3b8' : '#64748b',
+ primary: '#3b82f6',
+ success: '#10b981',
+ };
+
+ const { data, isLoading, isError, fetchNextPage, hasNextPage, refetch } = useInfiniteQuery({
+ queryKey: ['my_referrals'],
+ queryFn: async ({ pageParam = 1 }) => {
+ const res = await user_api.my_referrals({
+ page: pageParam,
+ page_size: PAGE_SIZE,
+ });
+
+ const d = res.data.data;
+ return {
+ results: d.results ?? [],
+ current_page: d.current_page,
+ total_pages: d.total_pages,
+ };
+ },
+ getNextPageParam: (lastPage) =>
+ lastPage.current_page < lastPage.total_pages ? lastPage.current_page + 1 : undefined,
+ initialPageParam: 1,
+ });
+
+ const referrals = data?.pages.flatMap((p) => p.results) ?? [];
+
+ const onRefresh = async () => {
+ setRefreshing(true);
+ await refetch();
+ setRefreshing(false);
+ };
+
+ if (isLoading) {
+ return (
+
+
+
+ );
+ }
+
+ if (isError) {
+ return (
+
+ {t('Xatolik yuz berdi')}
+
+ );
+ }
+
+ return (
+
+ {/* Header */}
+
+ router.push('/profile')}>
+
+
+ {t('Refferallarim')}
+ router.push('/profile/added-referalls')}>
+
+
+
+
+ item.id.toString()}
+ contentContainerStyle={styles.list}
+ refreshControl={
+
+ }
+ onEndReached={() => hasNextPage && fetchNextPage()}
+ renderItem={({ item }) => (
+
+
+
+
+ {item.code}
+
+ {
+ await Clipboard.setStringAsync(
+ `https://t.me/infotargetbot/join?startapp=${item.code}`
+ );
+ ToastAndroid.show('Refferal kopiya qilindi', ToastAndroid.SHORT);
+ }}
+ >
+
+
+
+
+ {item.description}
+
+
+
+
+
+ {item.referral_registered_count} {t('foydalanuvchi')}
+
+
+
+
+ {item.referral_income_amount} {t("so'm")}
+
+
+
+ )}
+ ListEmptyComponent={
+
+ {t('Refferallar topilmadi')}
+
+ }
+ />
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: { flex: 1 },
+ center: { flex: 1, justifyContent: 'center', alignItems: 'center' },
+
+ topHeader: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ padding: 16,
+ alignItems: 'center',
+ },
+ headerTitle: { fontSize: 18, fontWeight: '700' },
+
+ list: { padding: 16, gap: 12 },
+
+ card: {
+ borderRadius: 16,
+ padding: 16,
+ gap: 10,
+ },
+
+ cardHeader: {
+ flexDirection: 'row',
+ gap: 8,
+ alignItems: 'center',
+ },
+
+ code: {
+ fontSize: 16,
+ fontWeight: '700',
+ },
+
+ footer: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ marginTop: 6,
+ },
+
+ row: {
+ flexDirection: 'row',
+ gap: 6,
+ alignItems: 'center',
+ },
+
+ meta: {},
+ amount: {
+ fontWeight: '700',
+ },
+});