166 lines
4.2 KiB
TypeScript
166 lines
4.2 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { View, Text, StyleSheet, FlatList, TouchableOpacity, RefreshControl } from 'react-native';
|
|
import { useRouter } from 'expo-router';
|
|
import { withObservables } from '@nozbe/watermelondb/react';
|
|
import { database } from '../../src/db';
|
|
import Challenge from '../../src/db/models/Challenge';
|
|
import { syncChallenges } from '../../src/db/sync';
|
|
import { Colors } from '@/constants/theme';
|
|
|
|
const ChallengeItem = ({ challenge, onPress }: { challenge: Challenge; onPress: () => void }) => (
|
|
<TouchableOpacity style={styles.card} onPress={onPress}>
|
|
<View style={styles.cardHeader}>
|
|
<Text style={styles.cardTitle}>{challenge.name}</Text>
|
|
<View style={[styles.badge, { backgroundColor: getDifficultyColor(challenge.difficulty) }]}>
|
|
<Text style={styles.badgeText}>{challenge.difficulty}</Text>
|
|
</View>
|
|
</View>
|
|
<Text style={styles.cardDescription} numberOfLines={2}>
|
|
{challenge.description}
|
|
</Text>
|
|
<View style={styles.cardFooter}>
|
|
<Text style={styles.durationText}>{challenge.durationDays} Days</Text>
|
|
</View>
|
|
</TouchableOpacity>
|
|
);
|
|
|
|
const getDifficultyColor = (difficulty: string) => {
|
|
switch (difficulty?.toLowerCase()) {
|
|
case 'beginner': return '#4CAF50';
|
|
case 'moderate': return '#FF9800';
|
|
case 'hard': return '#F44336';
|
|
case 'extreme': return '#9C27B0';
|
|
default: return '#757575';
|
|
}
|
|
};
|
|
|
|
const ChallengeList = ({ challenges }: { challenges: Challenge[] }) => {
|
|
const router = useRouter();
|
|
const [refreshing, setRefreshing] = useState(false);
|
|
|
|
const onRefresh = async () => {
|
|
setRefreshing(true);
|
|
await syncChallenges();
|
|
setRefreshing(false);
|
|
};
|
|
|
|
useEffect(() => {
|
|
// Initial sync
|
|
syncChallenges();
|
|
}, []);
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<Text style={styles.title}>Explore Challenges</Text>
|
|
<FlatList
|
|
data={challenges}
|
|
keyExtractor={(item) => item.id}
|
|
renderItem={({ item }) => (
|
|
<ChallengeItem
|
|
challenge={item}
|
|
onPress={() => router.push(`/challenge/${item.id}`)}
|
|
/>
|
|
)}
|
|
contentContainerStyle={styles.listContent}
|
|
refreshControl={
|
|
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
|
|
}
|
|
ListEmptyComponent={
|
|
<View style={styles.emptyContainer}>
|
|
<Text style={styles.emptyText}>No challenges found.</Text>
|
|
<Text style={styles.emptySubText}>Pull to refresh to load challenges.</Text>
|
|
</View>
|
|
}
|
|
/>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
const enhance = withObservables([], () => ({
|
|
challenges: database.get<Challenge>('challenges').query(),
|
|
}));
|
|
|
|
export default enhance(ChallengeList);
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: '#f5f5f5',
|
|
paddingTop: 60, // Status bar spacing
|
|
},
|
|
title: {
|
|
fontSize: 28,
|
|
fontWeight: 'bold',
|
|
paddingHorizontal: 20,
|
|
marginBottom: 16,
|
|
color: '#333',
|
|
},
|
|
listContent: {
|
|
paddingHorizontal: 20,
|
|
paddingBottom: 20,
|
|
},
|
|
card: {
|
|
backgroundColor: 'white',
|
|
borderRadius: 12,
|
|
padding: 16,
|
|
marginBottom: 16,
|
|
shadowColor: '#000',
|
|
shadowOffset: { width: 0, height: 2 },
|
|
shadowOpacity: 0.1,
|
|
shadowRadius: 4,
|
|
elevation: 3,
|
|
},
|
|
cardHeader: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
marginBottom: 8,
|
|
},
|
|
cardTitle: {
|
|
fontSize: 18,
|
|
fontWeight: '600',
|
|
color: '#333',
|
|
flex: 1,
|
|
marginRight: 8,
|
|
},
|
|
badge: {
|
|
paddingHorizontal: 8,
|
|
paddingVertical: 4,
|
|
borderRadius: 4,
|
|
},
|
|
badgeText: {
|
|
color: 'white',
|
|
fontSize: 12,
|
|
fontWeight: 'bold',
|
|
textTransform: 'capitalize',
|
|
},
|
|
cardDescription: {
|
|
fontSize: 14,
|
|
color: '#666',
|
|
marginBottom: 12,
|
|
lineHeight: 20,
|
|
},
|
|
cardFooter: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
},
|
|
durationText: {
|
|
fontSize: 14,
|
|
color: '#888',
|
|
fontWeight: '500',
|
|
},
|
|
emptyContainer: {
|
|
padding: 40,
|
|
alignItems: 'center',
|
|
},
|
|
emptyText: {
|
|
fontSize: 18,
|
|
color: '#666',
|
|
marginBottom: 8,
|
|
},
|
|
emptySubText: {
|
|
fontSize: 14,
|
|
color: '#999',
|
|
},
|
|
});
|