surge/mobile/app/(tabs)/explore.tsx

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