fitst commit
This commit is contained in:
281
screens/profile/ui/BonusesScreen.tsx
Normal file
281
screens/profile/ui/BonusesScreen.tsx
Normal file
@@ -0,0 +1,281 @@
|
||||
import { useTheme } from '@/components/ThemeContext';
|
||||
import { useGlobalRefresh } from '@/components/ui/RefreshContext';
|
||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { router } from 'expo-router';
|
||||
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;
|
||||
|
||||
export default function BonusesScreen() {
|
||||
const { onRefresh, refreshing } = useGlobalRefresh();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { isDark } = useTheme();
|
||||
|
||||
const formatAmount = (amount: number) => {
|
||||
return new Intl.NumberFormat('uz-UZ').format(amount) + " so'm";
|
||||
};
|
||||
|
||||
const { data, isLoading, isError, fetchNextPage, hasNextPage } = useInfiniteQuery({
|
||||
queryKey: ['my_bonuses'],
|
||||
queryFn: async ({ pageParam = 1 }) => {
|
||||
const res = await user_api.my_bonuses({
|
||||
page: pageParam,
|
||||
page_size: PAGE_SIZE,
|
||||
});
|
||||
|
||||
const d = res?.data?.data;
|
||||
return {
|
||||
results: d?.results ?? [],
|
||||
current_page: d?.current_page ?? 1,
|
||||
total_pages: d?.total_pages ?? 1,
|
||||
};
|
||||
},
|
||||
getNextPageParam: (lastPage) =>
|
||||
lastPage.current_page < lastPage.total_pages ? lastPage.current_page + 1 : undefined,
|
||||
initialPageParam: 1,
|
||||
});
|
||||
|
||||
const allBonuses = data?.pages.flatMap((p) => p.results) ?? [];
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<View style={[styles.center, { backgroundColor: isDark ? '#0f172a' : '#f8fafc' }]}>
|
||||
<ActivityIndicator size={'large'} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<View style={[styles.center, { backgroundColor: isDark ? '#0f172a' : '#f8fafc' }]}>
|
||||
<Text style={styles.error}>{t('Xatolik yuz berdi')}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView 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'} />
|
||||
</Pressable>
|
||||
<Text style={[styles.headerTitle, { color: isDark ? '#fff' : '#0f172a' }]}>
|
||||
{t('Bonuslar')}
|
||||
</Text>
|
||||
</View>
|
||||
<FlatList
|
||||
data={allBonuses}
|
||||
keyExtractor={(item) => item.id.toString()}
|
||||
onEndReached={() => hasNextPage && fetchNextPage()}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
colors={['#2563eb']}
|
||||
tintColor="#2563eb"
|
||||
/>
|
||||
}
|
||||
contentContainerStyle={styles.list}
|
||||
renderItem={({ item }) => (
|
||||
<View
|
||||
style={[
|
||||
styles.card,
|
||||
{
|
||||
backgroundColor: isDark ? '#1e293b' : '#ffffff',
|
||||
borderColor: isDark ? '#fbbf2420' : '#e2e8f0',
|
||||
shadowColor: isDark ? '#000' : '#0f172a',
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: isDark ? 0.3 : 0.08,
|
||||
shadowRadius: 12,
|
||||
elevation: 4,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<View style={styles.header}>
|
||||
<View
|
||||
style={[
|
||||
styles.iconContainer,
|
||||
{ backgroundColor: isDark ? '#3b82f614' : '#dbeafe' },
|
||||
]}
|
||||
>
|
||||
<Award size={28} color="#3b82f6" />
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
styles.percentageBadge,
|
||||
{ backgroundColor: isDark ? '#10b98120' : '#d1fae5' },
|
||||
]}
|
||||
>
|
||||
<Text style={[styles.percentageText, { color: isDark ? '#10b981' : '#059669' }]}>
|
||||
{item.percent}
|
||||
</Text>
|
||||
<Percent size={16} color={isDark ? '#10b981' : '#059669'} />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Text style={[styles.title, { color: isDark ? '#f8fafc' : '#0f172a' }]}>
|
||||
{item.ad.title}
|
||||
</Text>
|
||||
<Text style={[styles.description, { color: isDark ? '#94a3b8' : '#64748b' }]}>
|
||||
{item.ad.description}
|
||||
</Text>
|
||||
|
||||
<View style={[styles.footer, { borderTopColor: isDark ? '#334155' : '#e2e8f0' }]}>
|
||||
<View style={styles.amountContainer}>
|
||||
<Text style={[styles.amountLabel, { color: isDark ? '#64748b' : '#94a3b8' }]}>
|
||||
{t('Bonus miqdori')}
|
||||
</Text>
|
||||
<Text style={styles.amount}>{formatAmount(item.amount)}</Text>
|
||||
</View>
|
||||
<View style={styles.dateContainer}>
|
||||
<Text style={[styles.dateLabel, { color: isDark ? '#64748b' : '#94a3b8' }]}>
|
||||
{t('Yaratilgan sana')}
|
||||
</Text>
|
||||
<Text style={[styles.date, { color: isDark ? '#94a3b8' : '#64748b' }]}>
|
||||
{new Date(item.created_at).toLocaleDateString('uz-UZ')}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
ListEmptyComponent={
|
||||
<View style={styles.emptyContainer}>
|
||||
<Award size={64} color={isDark ? '#334155' : '#cbd5e1'} />
|
||||
<Text style={[styles.emptyText, { color: isDark ? '#64748b' : '#94a3b8' }]}>
|
||||
{t("Hozircha bonuslar yo'q")}
|
||||
</Text>
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
list: {
|
||||
padding: 16,
|
||||
gap: 16,
|
||||
},
|
||||
card: {
|
||||
borderRadius: 20,
|
||||
padding: 20,
|
||||
gap: 16,
|
||||
borderWidth: 1.5,
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
iconContainer: {
|
||||
width: 64,
|
||||
height: 64,
|
||||
borderRadius: 32,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
percentageBadge: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 10,
|
||||
borderRadius: 20,
|
||||
},
|
||||
percentageText: {
|
||||
fontSize: 17,
|
||||
fontWeight: '700' as const,
|
||||
},
|
||||
title: {
|
||||
fontSize: 20,
|
||||
fontWeight: '700' as const,
|
||||
lineHeight: 26,
|
||||
},
|
||||
description: {
|
||||
fontSize: 15,
|
||||
lineHeight: 22,
|
||||
},
|
||||
footer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-start',
|
||||
paddingTop: 12,
|
||||
borderTopWidth: 1,
|
||||
},
|
||||
amountContainer: {
|
||||
gap: 6,
|
||||
},
|
||||
amountLabel: {
|
||||
fontSize: 12,
|
||||
fontWeight: '500' as const,
|
||||
},
|
||||
amount: {
|
||||
fontSize: 22,
|
||||
fontWeight: '700' as const,
|
||||
color: '#3b82f6',
|
||||
},
|
||||
dateContainer: {
|
||||
gap: 6,
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
dateLabel: {
|
||||
fontSize: 12,
|
||||
fontWeight: '500' as const,
|
||||
},
|
||||
date: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600' as const,
|
||||
},
|
||||
emptyContainer: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: 80,
|
||||
gap: 16,
|
||||
},
|
||||
emptyText: {
|
||||
fontSize: 17,
|
||||
fontWeight: '600' as const,
|
||||
},
|
||||
topHeader: {
|
||||
flexDirection: 'row',
|
||||
gap: 10,
|
||||
padding: 16,
|
||||
alignItems: 'center',
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 4,
|
||||
elevation: 3,
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: '700',
|
||||
flex: 1,
|
||||
},
|
||||
themeToggle: {
|
||||
padding: 8,
|
||||
},
|
||||
center: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
loading: {
|
||||
fontSize: 16,
|
||||
fontWeight: '500',
|
||||
},
|
||||
error: {
|
||||
color: '#ef4444',
|
||||
fontSize: 16,
|
||||
fontWeight: '500',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user