complated
This commit is contained in:
@@ -17,7 +17,6 @@ import {
|
||||
ToastAndroid,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { user_api } from '../lib/api';
|
||||
|
||||
export default function AddEmployee() {
|
||||
@@ -73,7 +72,7 @@ export default function AddEmployee() {
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView style={[styles.container, { backgroundColor: theme.background }]}>
|
||||
<View style={[styles.container, { backgroundColor: theme.background }]}>
|
||||
<View style={styles.topHeader}>
|
||||
<Pressable onPress={() => router.push('/profile/employees')}>
|
||||
<ArrowLeft color={theme.text} />
|
||||
@@ -140,7 +139,7 @@ export default function AddEmployee() {
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { user_api } from '../lib/api';
|
||||
import StepOneServices from './StepOneService';
|
||||
|
||||
@@ -127,7 +126,7 @@ export default function AddService() {
|
||||
);
|
||||
|
||||
return (
|
||||
<SafeAreaView style={{ flex: 1, backgroundColor: isDark ? '#0f172a' : '#f8fafc' }}>
|
||||
<View style={{ flex: 1, backgroundColor: isDark ? '#0f172a' : '#f8fafc' }}>
|
||||
<View style={[styles.header, { backgroundColor: isDark ? '#0f172a' : '#ffffff' }]}>
|
||||
<View
|
||||
style={{ flexDirection: 'row', gap: 10, alignContent: 'center', alignItems: 'center' }}
|
||||
@@ -176,7 +175,7 @@ export default function AddService() {
|
||||
)}
|
||||
{step === 2 && <StepTwo ref={stepTwoRef} formData={formData} updateForm={updateForm} />}
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import PAYME from '@/assets/images/Payme_NEW.png';
|
||||
import { useTheme } from '@/components/ThemeContext';
|
||||
import { price_calculation } from '@/screens/create-ads/lib/api';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { BottomSheetBackdrop, BottomSheetModal, BottomSheetScrollView } from '@gorhom/bottom-sheet';
|
||||
import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
|
||||
import { useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { ResizeMode, Video } from 'expo-av';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { ArrowLeft, EyeIcon, Megaphone, Plus } from 'lucide-react-native';
|
||||
@@ -9,18 +11,21 @@ import React, { useCallback, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
Dimensions,
|
||||
FlatList,
|
||||
Image,
|
||||
Linking,
|
||||
Pressable,
|
||||
RefreshControl,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { user_api } from '../lib/api';
|
||||
import { MyAdsDataRes } from '../lib/type';
|
||||
|
||||
const PAGE_SIZE = 10;
|
||||
const { width } = Dimensions.get('window');
|
||||
@@ -47,8 +52,8 @@ export function AnnouncementsTab() {
|
||||
};
|
||||
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [selectedAnnouncement, setSelectedAnnouncement] = useState<any>(null);
|
||||
const [sheetOpen, setSheetOpen] = useState(false);
|
||||
const [selectedAnnouncement, setSelectedAnnouncement] = useState<MyAdsDataRes | null>(null);
|
||||
const [sheetOpen, setSheetOpen] = useState(false); const bottomSheetModalRef = useRef<BottomSheetModal>(null);
|
||||
|
||||
const { data, isLoading, isError, fetchNextPage, hasNextPage, refetch } = useInfiniteQuery({
|
||||
queryKey: ['my_ads'],
|
||||
@@ -78,12 +83,12 @@ export function AnnouncementsTab() {
|
||||
isError: detailError,
|
||||
} = useQuery({
|
||||
queryKey: ['my_ads_id', selectedAnnouncement?.id],
|
||||
queryFn: () => user_api.my_ads_detail(selectedAnnouncement.id),
|
||||
queryFn: () => user_api.my_ads_detail(selectedAnnouncement?.id!),
|
||||
select: (res) => res.data.data,
|
||||
enabled: !!selectedAnnouncement && sheetOpen,
|
||||
});
|
||||
|
||||
const openSheet = (item: any) => {
|
||||
const openSheet = (item: MyAdsDataRes) => {
|
||||
setSelectedAnnouncement(item);
|
||||
setSheetOpen(true);
|
||||
requestAnimationFrame(() => bottomSheetRef.current?.present());
|
||||
@@ -125,9 +130,35 @@ export function AnnouncementsTab() {
|
||||
|
||||
const formatAmount = (amount: number) => new Intl.NumberFormat('uz-UZ').format(amount) + " so'm";
|
||||
|
||||
const { mutate: payment } = useMutation({
|
||||
mutationFn: (body: { return_url: string; adId: number; paymentType: 'payme' | 'referral' }) =>
|
||||
price_calculation.payment(body),
|
||||
onSuccess: async (res, variables) => {
|
||||
if (variables.paymentType === 'payme') {
|
||||
await Linking.openURL(res.data.url);
|
||||
router.push('/profile/my-ads');
|
||||
} else {
|
||||
router.push('/profile/my-ads');
|
||||
}
|
||||
},
|
||||
onError: (err) => {
|
||||
Alert.alert('Xatolik yuz berdi', err.message);
|
||||
},
|
||||
});
|
||||
|
||||
const sendPayment = ({ type, id }: { id: number, type: 'payme' | 'referral' }) => {
|
||||
payment({
|
||||
adId: id,
|
||||
paymentType: type,
|
||||
return_url: 'https://infotarget.uz/en/main/dashboard',
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<View>
|
||||
<View style={styles.topHeader}>
|
||||
<Pressable onPress={() => router.push('/profile')}>
|
||||
<ArrowLeft color={theme.text} />
|
||||
@@ -138,7 +169,7 @@ export function AnnouncementsTab() {
|
||||
</Pressable>
|
||||
</View>
|
||||
<ActivityIndicator size={'large'} />
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -304,6 +335,63 @@ export function AnnouncementsTab() {
|
||||
</>
|
||||
)}
|
||||
</BottomSheetScrollView>
|
||||
|
||||
{detail?.status === 'pending' && (
|
||||
<View style={[styles.footerContainer, { backgroundColor: theme.sheetBg }]}>
|
||||
<TouchableOpacity
|
||||
style={[styles.paymentButton, { backgroundColor: theme.primary }]}
|
||||
onPress={() => {
|
||||
bottomSheetModalRef.current?.present();
|
||||
}}
|
||||
>
|
||||
<Text style={styles.paymentButtonText}>{t("To'lov qilish")}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
</BottomSheetModal>
|
||||
|
||||
<BottomSheetModal
|
||||
ref={bottomSheetModalRef}
|
||||
index={0}
|
||||
snapPoints={['70%', '95%']}
|
||||
backdropComponent={renderBackdrop}
|
||||
handleIndicatorStyle={{ backgroundColor: '#94a3b8', width: 50 }}
|
||||
backgroundStyle={{ backgroundColor: isDark ? '#0f172a' : '#ffffff' }}
|
||||
enablePanDownToClose
|
||||
>
|
||||
<BottomSheetScrollView
|
||||
style={styles.sheetContent}
|
||||
contentContainerStyle={styles.sheetContentContainer}
|
||||
>
|
||||
<View style={{ padding: 20 }}>
|
||||
<Text style={[styles.sheetTitle, isDark ? styles.darkText : styles.lightText]}>
|
||||
{t("To'lov turini tanlang")}
|
||||
</Text>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.paymentItem,
|
||||
isDark ? styles.darkPaymentItem : styles.lightPaymentItem,
|
||||
{ flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'center' },
|
||||
]}
|
||||
onPress={() => sendPayment({ id: selectedAnnouncement?.id!, type: 'payme' })}
|
||||
>
|
||||
<Image source={PAYME} style={{ width: 80, height: 80 }} />
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.paymentItem,
|
||||
isDark ? styles.darkPaymentItem : styles.lightPaymentItem,
|
||||
]}
|
||||
onPress={() => sendPayment({ id: selectedAnnouncement?.id!, type: 'referral' })}
|
||||
>
|
||||
<Text style={[styles.paymentText, isDark ? styles.darkText : styles.lightText]}>
|
||||
{t('Referal orqali')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</BottomSheetScrollView>
|
||||
</BottomSheetModal>
|
||||
</View>
|
||||
);
|
||||
@@ -321,7 +409,17 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
headerTitle: { fontSize: 18, fontWeight: '700' },
|
||||
|
||||
list: { padding: 16, gap: 12 },
|
||||
sheetContent: { flex: 1 },
|
||||
sheetContentContainer: { paddingBottom: 40 },
|
||||
|
||||
darkText: {
|
||||
color: '#f1f5f9',
|
||||
},
|
||||
lightText: {
|
||||
color: '#0f172a',
|
||||
},
|
||||
|
||||
list: { padding: 16, paddingBottom: 30, gap: 12 },
|
||||
card: { borderRadius: 16, padding: 16, gap: 8 },
|
||||
cardImage: { width: '100%', height: 160, borderRadius: 12 },
|
||||
|
||||
@@ -329,6 +427,24 @@ const styles = StyleSheet.create({
|
||||
title: { fontSize: 16, fontWeight: '700' },
|
||||
desc: { lineHeight: 20 },
|
||||
|
||||
paymentItem: {
|
||||
height: 56,
|
||||
borderRadius: 14,
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 16,
|
||||
marginBottom: 12,
|
||||
},
|
||||
darkPaymentItem: {
|
||||
backgroundColor: '#1e293b',
|
||||
},
|
||||
lightPaymentItem: {
|
||||
backgroundColor: '#f8fafc',
|
||||
},
|
||||
paymentText: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
},
|
||||
|
||||
footer: { flexDirection: 'row', justifyContent: 'space-between' },
|
||||
metaText: {},
|
||||
date: {},
|
||||
@@ -363,6 +479,25 @@ const styles = StyleSheet.create({
|
||||
value: { flex: 1 },
|
||||
price: { fontWeight: '700' },
|
||||
|
||||
footerContainer: {
|
||||
padding: 16,
|
||||
paddingBottom: 20,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: '#e2e8f0',
|
||||
},
|
||||
paymentButton: {
|
||||
paddingVertical: 14,
|
||||
paddingHorizontal: 24,
|
||||
borderRadius: 12,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
paymentButtonText: {
|
||||
color: '#ffffff',
|
||||
fontSize: 16,
|
||||
fontWeight: '700',
|
||||
},
|
||||
|
||||
loading: {},
|
||||
error: {},
|
||||
});
|
||||
|
||||
@@ -6,7 +6,6 @@ import { ArrowLeft, Award, Percent } from 'lucide-react-native';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ActivityIndicator, FlatList, Pressable, StyleSheet, Text, View } from 'react-native';
|
||||
import { RefreshControl } from 'react-native-gesture-handler';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { user_api } from '../lib/api';
|
||||
|
||||
const PAGE_SIZE = 10;
|
||||
@@ -60,7 +59,7 @@ export default function BonusesScreen() {
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView style={[styles.container, { backgroundColor: isDark ? '#0f172a' : '#f8fafc' }]}>
|
||||
<View style={[styles.container, { backgroundColor: isDark ? '#0f172a' : '#f8fafc' }]}>
|
||||
<View style={[styles.topHeader, { backgroundColor: isDark ? '#0f172a' : '#ffffff' }]}>
|
||||
<Pressable onPress={() => router.push('/profile')}>
|
||||
<ArrowLeft color={isDark ? '#fff' : '#0f172a'} />
|
||||
@@ -153,7 +152,7 @@ export default function BonusesScreen() {
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -164,6 +163,7 @@ const styles = StyleSheet.create({
|
||||
list: {
|
||||
padding: 16,
|
||||
gap: 16,
|
||||
paddingBottom: 30,
|
||||
},
|
||||
card: {
|
||||
borderRadius: 20,
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
ToastAndroid,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { user_api } from '../lib/api';
|
||||
|
||||
type FormType = {
|
||||
@@ -89,7 +88,7 @@ export default function CreateReferrals() {
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView style={{ flex: 1, backgroundColor: isDark ? '#0f172a' : '#f8fafc' }}>
|
||||
<View style={{ flex: 1, backgroundColor: isDark ? '#0f172a' : '#f8fafc' }}>
|
||||
{/* HEADER */}
|
||||
<View style={[styles.header, { backgroundColor: isDark ? '#0f172a' : '#fff' }]}>
|
||||
<Pressable onPress={() => router.back()}>
|
||||
@@ -187,7 +186,7 @@ export default function CreateReferrals() {
|
||||
</>
|
||||
)}
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import { ArrowLeft, Loader } from 'lucide-react-native';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Alert, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { user_api } from '../lib/api';
|
||||
import StepOneServices from './StepOneService';
|
||||
|
||||
@@ -145,7 +144,7 @@ export default function EditService() {
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView style={{ flex: 1, backgroundColor: isDark ? '#0f172a' : '#f8fafc' }}>
|
||||
<View style={{ flex: 1, backgroundColor: isDark ? '#0f172a' : '#f8fafc' }}>
|
||||
<View style={[styles.header, { backgroundColor: isDark ? '#0f172a' : '#ffffff' }]}>
|
||||
<View style={{ flexDirection: 'row', gap: 10, alignItems: 'center' }}>
|
||||
<Pressable onPress={handleBack}>
|
||||
@@ -179,7 +178,7 @@ export default function EditService() {
|
||||
)}
|
||||
{step === 2 && <StepTwo ref={stepTwoRef} formData={formData} updateForm={updateForm} />}
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { user_api } from '../lib/api';
|
||||
import { ExployeesDataResponse } from '../lib/type';
|
||||
|
||||
@@ -84,7 +83,7 @@ export function EmployeesTab() {
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<SafeAreaView style={[styles.container, { backgroundColor: theme.background }]}>
|
||||
<View style={[styles.container, { backgroundColor: theme.background }]}>
|
||||
<View style={styles.topHeader}>
|
||||
<Pressable onPress={() => router.push('/profile')}>
|
||||
<ArrowLeft color={theme.text} />
|
||||
@@ -95,7 +94,7 @@ export function EmployeesTab() {
|
||||
</Pressable>
|
||||
</View>
|
||||
<ActivityIndicator size={'large'} />
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -110,7 +109,7 @@ export function EmployeesTab() {
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView style={[styles.container, { backgroundColor: theme.background }]}>
|
||||
<View style={[styles.container, { backgroundColor: theme.background }]}>
|
||||
<View style={styles.topHeader}>
|
||||
<Pressable onPress={() => router.push('/profile')}>
|
||||
<ArrowLeft color={theme.text} />
|
||||
@@ -152,14 +151,14 @@ export function EmployeesTab() {
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1 },
|
||||
addButton: { padding: 8 },
|
||||
list: { padding: 16, gap: 12 },
|
||||
list: { padding: 16, gap: 12, paddingBottom: 30 },
|
||||
card: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import { useTheme } from '@/components/ThemeContext';
|
||||
import { router } from 'expo-router';
|
||||
import { VideoView, useVideoPlayer } from 'expo-video';
|
||||
import { ArrowLeft, Check, ChevronDown, X } from 'lucide-react-native';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { ArrowLeft, Play, X } from 'lucide-react-native';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Dimensions,
|
||||
FlatList,
|
||||
Image,
|
||||
Pressable,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import ImageViewer from 'react-native-image-zoom-viewer';
|
||||
import Modal from 'react-native-modal';
|
||||
|
||||
const { width } = Dimensions.get('window');
|
||||
|
||||
type ManualStep = {
|
||||
image: any;
|
||||
description?: string;
|
||||
@@ -39,6 +37,7 @@ const languages: Language[] = [
|
||||
export function ManualTab() {
|
||||
const { isDark } = useTheme();
|
||||
const { t, i18n } = useTranslation();
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
|
||||
/** 🔹 Modal states */
|
||||
const [imageVisible, setImageVisible] = useState(false);
|
||||
@@ -69,9 +68,9 @@ export function ManualTab() {
|
||||
|
||||
/** 🔹 Video manbalari (SELECT ga bog‘liq) */
|
||||
const videos = {
|
||||
uz: require('@/assets/manual/manual_video_uz.mp4'),
|
||||
ru: require('@/assets/manual/manual_video_ru.mp4'),
|
||||
en: require('@/assets/manual/manual_video_en.mp4'),
|
||||
uz: require('@/assets/manual/manual_video_uz.webm'),
|
||||
ru: require('@/assets/manual/manual_video_ru.webm'),
|
||||
en: require('@/assets/manual/manual_video_en.webm'),
|
||||
};
|
||||
|
||||
const player = useVideoPlayer(videos[selectedLang], (player) => {
|
||||
@@ -161,6 +160,17 @@ export function ManualTab() {
|
||||
|
||||
const selectedLanguage = languages.find((l) => l.code === selectedLang);
|
||||
|
||||
useEffect(() => {
|
||||
// listener qo'shish
|
||||
const subscription = player.addListener('playingChange', (state) => {
|
||||
setIsPlaying(state.isPlaying);
|
||||
});
|
||||
|
||||
return () => {
|
||||
subscription.remove();
|
||||
};
|
||||
}, [player]);
|
||||
|
||||
const renderStep = ({ item, index }: { item: ManualStep; index: number }) => (
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
@@ -186,18 +196,17 @@ export function ManualTab() {
|
||||
{t("Foydalanish qo'llanmasi")}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<Text style={[styles.subtitle, { color: theme.textMuted }]}>
|
||||
{t("Quyidagi qisqa video yoki rasmlarni ko'rib chiqing")}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* VIDEO */}
|
||||
<View style={styles.section}>
|
||||
<Text style={[styles.sectionTitle, { color: theme.text }]}>
|
||||
{t("Foydalanish video qo'llanma")}
|
||||
{t("Foydalanish qo'llanmasi")}
|
||||
</Text>
|
||||
<View style={{ alignContent: 'flex-end', alignItems: 'flex-end', paddingHorizontal: 16 }}>
|
||||
<Text style={[styles.subtitle, { color: theme.textMuted }]}>
|
||||
{t("Quyidagi qisqa video yoki rasmlarni ko'rib chiqing")}
|
||||
</Text>
|
||||
{/* <View style={{ alignContent: 'flex-end', alignItems: 'flex-end', paddingHorizontal: 16 }}>
|
||||
<Pressable
|
||||
style={[
|
||||
styles.langSelector,
|
||||
@@ -210,7 +219,7 @@ export function ManualTab() {
|
||||
</View>
|
||||
<ChevronDown color={theme.textMuted} />
|
||||
</Pressable>
|
||||
</View>
|
||||
</View> */}
|
||||
|
||||
<View style={[styles.videoCard, { backgroundColor: theme.cardBg }]}>
|
||||
<VideoView
|
||||
@@ -218,16 +227,49 @@ export function ManualTab() {
|
||||
style={styles.video}
|
||||
allowsFullscreen
|
||||
allowsPictureInPicture
|
||||
nativeControls={true}
|
||||
contentFit="cover"
|
||||
/>
|
||||
|
||||
{!isPlaying && (
|
||||
<View
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
backgroundColor: 'white',
|
||||
borderRadius: 50,
|
||||
width: 40,
|
||||
height: 40,
|
||||
justifyContent: 'center',
|
||||
alignContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
onPress={() => {
|
||||
player.play();
|
||||
setIsPlaying(true);
|
||||
}}
|
||||
>
|
||||
<Play color={'white'} size={26} fill={'black'} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* RASMLAR */}
|
||||
<View style={styles.section}>
|
||||
<Text style={[styles.sectionTitle, { color: theme.text }]}>
|
||||
{/* <Text style={[styles.sectionTitle, { color: theme.text }]}>
|
||||
{t('Foydalanish rasm qo‘llanma')}
|
||||
</Text>
|
||||
</Text> */}
|
||||
|
||||
<FlatList
|
||||
horizontal
|
||||
@@ -254,7 +296,7 @@ export function ManualTab() {
|
||||
</Modal>
|
||||
|
||||
{/* LANGUAGE MODAL */}
|
||||
<Modal isVisible={langPickerVisible} onBackdropPress={() => setLangPickerVisible(false)}>
|
||||
{/* <Modal isVisible={langPickerVisible} onBackdropPress={() => setLangPickerVisible(false)}>
|
||||
<View style={[styles.langModalContent, { backgroundColor: theme.cardBg }]}>
|
||||
<Text style={{ color: 'white', marginBottom: 10, fontSize: 18 }}>
|
||||
{t('Video tilini tanlang')}
|
||||
@@ -299,7 +341,7 @@ export function ManualTab() {
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</Modal>
|
||||
</Modal> */}
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
@@ -308,8 +350,8 @@ const styles = StyleSheet.create({
|
||||
container: { flex: 1 },
|
||||
hero: { padding: 20 },
|
||||
topHeader: { flexDirection: 'row', alignItems: 'center', gap: 12 },
|
||||
headerTitle: { fontSize: 22, fontWeight: '700' },
|
||||
subtitle: { marginTop: 8 },
|
||||
headerTitle: { fontSize: 22, fontWeight: '700', marginHorizontal: 16 },
|
||||
subtitle: { fontSize: 16, marginTop: 5, fontWeight: '500', marginHorizontal: 16 },
|
||||
|
||||
section: { marginBottom: 28 },
|
||||
sectionTitle: { fontSize: 20, fontWeight: '700', marginLeft: 16 },
|
||||
@@ -329,6 +371,7 @@ const styles = StyleSheet.create({
|
||||
|
||||
videoCard: {
|
||||
marginHorizontal: 16,
|
||||
marginTop: 10,
|
||||
borderRadius: 20,
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useTheme } from '@/components/ThemeContext';
|
||||
import { useGlobalRefresh } from '@/components/ui/RefreshContext';
|
||||
import { useInfiniteQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { Image as ExpoImage } from 'expo-image';
|
||||
import { useRouter } from 'expo-router';
|
||||
@@ -15,20 +14,18 @@ import {
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { RefreshControl } from 'react-native-gesture-handler';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { user_api } from '../lib/api';
|
||||
|
||||
const PAGE_SIZE = 5;
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
export default function MyServicesScreen() {
|
||||
const router = useRouter();
|
||||
const { onRefresh, refreshing } = useGlobalRefresh();
|
||||
const queryClient = useQueryClient();
|
||||
const { isDark } = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
/* ================= QUERY ================= */
|
||||
const { data, isLoading, isError, fetchNextPage, hasNextPage } = useInfiniteQuery({
|
||||
const { data, isLoading, isError, fetchNextPage, hasNextPage, isRefetching } = useInfiniteQuery({
|
||||
queryKey: ['my_services'],
|
||||
queryFn: async ({ pageParam = 1 }) => {
|
||||
const res = await user_api.my_sevices({
|
||||
@@ -69,9 +66,13 @@ export default function MyServicesScreen() {
|
||||
]);
|
||||
};
|
||||
|
||||
const onRefresh = () => {
|
||||
queryClient.refetchQueries({ queryKey: ['my_services'] });
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<SafeAreaView style={[styles.container, { backgroundColor: isDark ? '#0f172a' : '#f8fafc' }]}>
|
||||
<View style={[styles.container, { backgroundColor: isDark ? '#0f172a' : '#f8fafc' }]}>
|
||||
<View style={[styles.topHeader, { backgroundColor: isDark ? '#0f172a' : '#ffffff' }]}>
|
||||
<Pressable onPress={() => router.push('/profile')}>
|
||||
<ArrowLeft color={isDark ? '#fff' : '#0f172a'} />
|
||||
@@ -84,7 +85,7 @@ export default function MyServicesScreen() {
|
||||
</Pressable>
|
||||
</View>
|
||||
<ActivityIndicator size={'large'} />
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -97,7 +98,7 @@ export default function MyServicesScreen() {
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView style={[styles.container, { backgroundColor: isDark ? '#0f172a' : '#f8fafc' }]}>
|
||||
<View style={[styles.container, { backgroundColor: isDark ? '#0f172a' : '#f8fafc' }]}>
|
||||
{/* HEADER */}
|
||||
<View style={[styles.topHeader, { backgroundColor: isDark ? '#0f172a' : '#ffffff' }]}>
|
||||
<Pressable onPress={() => router.push('/profile')}>
|
||||
@@ -119,7 +120,7 @@ export default function MyServicesScreen() {
|
||||
onEndReached={() => hasNextPage && fetchNextPage()}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
refreshing={isRefetching}
|
||||
onRefresh={onRefresh}
|
||||
colors={['#2563eb']}
|
||||
tintColor="#2563eb"
|
||||
@@ -235,7 +236,7 @@ export default function MyServicesScreen() {
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -253,7 +254,7 @@ const styles = StyleSheet.create({
|
||||
elevation: 3,
|
||||
},
|
||||
headerTitle: { fontSize: 18, fontWeight: '700', flex: 1, marginLeft: 10 },
|
||||
list: { padding: 16, gap: 16 },
|
||||
list: { padding: 16, gap: 16, paddingBottom: 30 },
|
||||
card: { borderRadius: 20, overflow: 'hidden' },
|
||||
mediaContainer: { width: '100%', height: 200 },
|
||||
media: { width: '100%', height: '100%' },
|
||||
|
||||
@@ -38,6 +38,15 @@ export function NotificationTab() {
|
||||
initialPageParam: 1,
|
||||
});
|
||||
const notifications = data?.pages.flatMap((p) => p.results) ?? [];
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { mutate: markAllAsRead, isPending: isMarkingAllRead } = useMutation({
|
||||
mutationFn: () => user_api.mark_all_as_read(),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['notifications-list'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['notification-list'] });
|
||||
},
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
@@ -82,6 +91,25 @@ export function NotificationTab() {
|
||||
<Text style={[styles.headerTitle, { color: isDark ? '#f1f5f9' : '#0f172a' }]}>
|
||||
{t('Bildirishnomalar')}
|
||||
</Text>
|
||||
|
||||
{notifications.some((n) => !n.is_read) && (
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.markAllButton,
|
||||
{ backgroundColor: isDark ? '#1e293b' : '#e0f2fe', borderColor: '#3b82f6' },
|
||||
]}
|
||||
onPress={() => markAllAsRead()}
|
||||
disabled={isMarkingAllRead}
|
||||
>
|
||||
{isMarkingAllRead ? (
|
||||
<ActivityIndicator size="small" color="#3b82f6" />
|
||||
) : (
|
||||
<Text style={[styles.markAllText, { color: '#3b82f6' }]}>
|
||||
{t("Barchasi o'qildi")}
|
||||
</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
<FlatList
|
||||
data={notifications}
|
||||
@@ -372,5 +400,19 @@ const styles = StyleSheet.create({
|
||||
fontSize: 18,
|
||||
fontWeight: '700',
|
||||
lineHeight: 24,
|
||||
flex: 1,
|
||||
},
|
||||
markAllButton: {
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 8,
|
||||
borderWidth: 1,
|
||||
minWidth: 80,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
markAllText: {
|
||||
fontSize: 13,
|
||||
fontWeight: '600',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import AdsLogo from '@/assets/images/one_click.png';
|
||||
import { useTheme } from '@/components/ThemeContext';
|
||||
import { useGlobalRefresh } from '@/components/ui/RefreshContext';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Image } from 'expo-image';
|
||||
import { useRouter } from 'expo-router';
|
||||
import {
|
||||
Award,
|
||||
@@ -8,6 +10,7 @@ import {
|
||||
BookAIcon,
|
||||
ChevronRight,
|
||||
HandCoins,
|
||||
LucideIcon,
|
||||
Megaphone,
|
||||
Package,
|
||||
Settings,
|
||||
@@ -19,6 +22,11 @@ import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-nati
|
||||
import { RefreshControl } from 'react-native-gesture-handler';
|
||||
import { user_api } from '../lib/api';
|
||||
|
||||
interface SectionType {
|
||||
title: string;
|
||||
items: { icon: LucideIcon; label: string; route: string; badge?: number; image?: any }[];
|
||||
}
|
||||
|
||||
export default function Profile() {
|
||||
const router = useRouter();
|
||||
const { onRefresh, refreshing } = useGlobalRefresh();
|
||||
@@ -36,7 +44,7 @@ export default function Profile() {
|
||||
queryFn: () => user_api.getMe(),
|
||||
});
|
||||
|
||||
const sections = [
|
||||
const sections: SectionType[] = [
|
||||
{
|
||||
title: 'Shaxsiy',
|
||||
items: [
|
||||
@@ -53,17 +61,17 @@ export default function Profile() {
|
||||
{
|
||||
title: 'Faoliyat',
|
||||
items: [
|
||||
{ icon: Megaphone, label: "E'lonlar", route: '/profile/my-ads' },
|
||||
{ icon: Megaphone, label: "E'lonlar", image: AdsLogo, 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',
|
||||
},
|
||||
]
|
||||
{
|
||||
icon: HandCoins,
|
||||
label: 'Refferallarim',
|
||||
route: '/profile/my-referrals',
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
@@ -79,6 +87,7 @@ export default function Profile() {
|
||||
return (
|
||||
<ScrollView
|
||||
style={[styles.content, isDark ? styles.darkBg : styles.lightBg]}
|
||||
contentContainerStyle={{ paddingBottom: 90 }}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
@@ -107,17 +116,32 @@ export default function Profile() {
|
||||
>
|
||||
<item.icon size={24} color="#3b82f6" />
|
||||
|
||||
{item.badge && (
|
||||
{item?.badge && (
|
||||
<View style={styles.badge}>
|
||||
<Text style={styles.badgeText}>{item.badge}</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<Text style={[styles.cardLabel, isDark ? styles.darkText : styles.lightText]}>
|
||||
{t(item.label)}
|
||||
</Text>
|
||||
|
||||
<View style={{ flex: 1 }}>
|
||||
{item.image ? (
|
||||
<View style={{ flex: 1 }}>
|
||||
<Image
|
||||
source={item.image}
|
||||
style={{ width: '60%', height: 40 }}
|
||||
contentFit="contain"
|
||||
/>
|
||||
<Text style={[styles.cardLabel, isDark ? styles.darkText : styles.lightText, { fontSize: 14, marginTop: 4, fontWeight: '500' }]}>
|
||||
{t("Bir Zumda Jonatish")}
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View>
|
||||
<Text style={[styles.cardLabel, isDark ? styles.darkText : styles.lightText]}>
|
||||
{t(item.label)}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
<ChevronRight size={20} color={isDark ? '#64748b' : '#94a3b8'} />
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
@@ -131,7 +155,7 @@ export default function Profile() {
|
||||
const styles = StyleSheet.create({
|
||||
content: {
|
||||
flex: 1,
|
||||
marginBottom: 50,
|
||||
paddingBottom: 120,
|
||||
},
|
||||
|
||||
darkBg: {
|
||||
|
||||
@@ -8,8 +8,10 @@ import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
FlatList,
|
||||
Platform,
|
||||
Pressable,
|
||||
RefreshControl,
|
||||
Share,
|
||||
StyleSheet,
|
||||
Text,
|
||||
ToastAndroid,
|
||||
@@ -64,9 +66,31 @@ export function ReferralsTab() {
|
||||
setRefreshing(false);
|
||||
};
|
||||
|
||||
// Clipboard + Share funksiyasi
|
||||
const handleCopyAndShare = async (code: string) => {
|
||||
const referralLink = `https://t.me/infotargetbot/join?startapp=${code}`;
|
||||
|
||||
// Clipboard-ga nusxa olish
|
||||
await Clipboard.setStringAsync(referralLink);
|
||||
|
||||
// Share qilish
|
||||
try {
|
||||
await Share.share({
|
||||
message: referralLink,
|
||||
title: t('Referal linkni ulashish'),
|
||||
});
|
||||
} catch (err) {
|
||||
console.log('Share error:', err);
|
||||
}
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
ToastAndroid.show(t('Refferal kopiya qilindi'), ToastAndroid.SHORT);
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<SafeAreaView style={{ flex: 1 }}>
|
||||
<SafeAreaView style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
||||
<ActivityIndicator size="large" />
|
||||
</SafeAreaView>
|
||||
);
|
||||
@@ -103,26 +127,12 @@ export function ReferralsTab() {
|
||||
onEndReached={() => hasNextPage && fetchNextPage()}
|
||||
renderItem={({ item }) => (
|
||||
<View style={[styles.card, { backgroundColor: theme.cardBg }]}>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<View style={styles.cardRow}>
|
||||
<View style={styles.cardHeader}>
|
||||
<HandCoins size={20} color={theme.primary} />
|
||||
<Text style={[styles.code, { color: theme.text }]}>{item.code}</Text>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
onPress={async () => {
|
||||
await Clipboard.setStringAsync(
|
||||
`https://t.me/infotargetbot/join?startapp=${item.code}`
|
||||
);
|
||||
ToastAndroid.show('Refferal kopiya qilindi', ToastAndroid.SHORT);
|
||||
}}
|
||||
>
|
||||
<TouchableOpacity onPress={() => handleCopyAndShare(item.code)}>
|
||||
<CopyIcon size={20} color={theme.primary} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
@@ -165,7 +175,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
headerTitle: { fontSize: 18, fontWeight: '700' },
|
||||
|
||||
list: { padding: 16, gap: 12 },
|
||||
list: { padding: 16, gap: 12, paddingBottom: 30 },
|
||||
|
||||
card: {
|
||||
borderRadius: 16,
|
||||
@@ -173,6 +183,13 @@ const styles = StyleSheet.create({
|
||||
gap: 10,
|
||||
},
|
||||
|
||||
cardRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
cardHeader: {
|
||||
flexDirection: 'row',
|
||||
gap: 8,
|
||||
|
||||
Reference in New Issue
Block a user