feat: update Home screen to use React Query with loading states
- Replace static data with React Query hooks - Add loading skeletons for all data sections - Show shimmer effect while data is loading - Handle empty and error states gracefully
This commit is contained in:
@@ -12,18 +12,12 @@ import Ionicons from '@expo/vector-icons/Ionicons'
|
||||
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useHaptics } from '@/src/shared/hooks'
|
||||
import { useHaptics, useFeaturedWorkouts, usePopularWorkouts, useCollections } from '@/src/shared/hooks'
|
||||
import { useUserStore, useActivityStore } from '@/src/shared/stores'
|
||||
import {
|
||||
getFeaturedWorkouts,
|
||||
getPopularWorkouts,
|
||||
COLLECTIONS,
|
||||
WORKOUTS,
|
||||
} from '@/src/shared/data'
|
||||
import { useTranslatedWorkouts, useTranslatedCollections } from '@/src/shared/data/useTranslatedData'
|
||||
import { StyledText } from '@/src/shared/components/StyledText'
|
||||
import { WorkoutCard } from '@/src/shared/components/WorkoutCard'
|
||||
import { CollectionCard } from '@/src/shared/components/CollectionCard'
|
||||
import { WorkoutCardSkeleton, CollectionCardSkeleton, StatsCardSkeleton } from '@/src/shared/components/loading/Skeleton'
|
||||
|
||||
import { useThemeColors, BRAND, GRADIENTS } from '@/src/shared/theme'
|
||||
import type { ThemeColors } from '@/src/shared/theme/types'
|
||||
@@ -69,10 +63,17 @@ export default function HomeScreen() {
|
||||
|
||||
const [selectedCategory, setSelectedCategory] = useState<WorkoutCategory | 'all'>('all')
|
||||
|
||||
const featured = getFeaturedWorkouts()[0] ?? WORKOUTS[0]
|
||||
const popular = getPopularWorkouts(6)
|
||||
const translatedPopular = useTranslatedWorkouts(popular)
|
||||
const translatedCollections = useTranslatedCollections(COLLECTIONS)
|
||||
// React Query hooks for live data
|
||||
const { data: featuredWorkouts = [], isLoading: isLoadingFeatured } = useFeaturedWorkouts()
|
||||
const { data: popularWorkouts = [], isLoading: isLoadingPopular } = usePopularWorkouts(6)
|
||||
const { data: collections = [], isLoading: isLoadingCollections } = useCollections()
|
||||
|
||||
const featured = featuredWorkouts[0]
|
||||
|
||||
const filteredWorkouts = useMemo(() => {
|
||||
if (selectedCategory === 'all') return popularWorkouts
|
||||
return popularWorkouts.filter((w) => w.category === selectedCategory)
|
||||
}, [popularWorkouts, selectedCategory])
|
||||
|
||||
const greeting = (() => {
|
||||
const hour = new Date().getHours()
|
||||
@@ -91,11 +92,6 @@ export default function HomeScreen() {
|
||||
router.push(`/collection/${id}`)
|
||||
}
|
||||
|
||||
const filteredWorkouts = useMemo(() => {
|
||||
if (selectedCategory === 'all') return translatedPopular
|
||||
return translatedPopular.filter((w) => w.category === selectedCategory)
|
||||
}, [translatedPopular, selectedCategory])
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { paddingTop: insets.top }]}>
|
||||
<ScrollView
|
||||
@@ -130,11 +126,15 @@ export default function HomeScreen() {
|
||||
{t('screens:home.featured')}
|
||||
</StyledText>
|
||||
</View>
|
||||
<WorkoutCard
|
||||
workout={featured}
|
||||
variant="featured"
|
||||
onPress={() => handleWorkoutPress(featured.id)}
|
||||
/>
|
||||
{isLoadingFeatured ? (
|
||||
<WorkoutCardSkeleton />
|
||||
) : featured ? (
|
||||
<WorkoutCard
|
||||
workout={featured}
|
||||
variant="featured"
|
||||
onPress={() => handleWorkoutPress(featured.id)}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
{/* Category Filter */}
|
||||
@@ -176,34 +176,41 @@ export default function HomeScreen() {
|
||||
</View>
|
||||
|
||||
{/* Popular Workouts - Horizontal */}
|
||||
{filteredWorkouts.length > 0 && (
|
||||
<View style={styles.section}>
|
||||
<View style={styles.sectionHeader}>
|
||||
<StyledText size={FONTS.TITLE_2} weight="semibold" color={colors.text.primary}>
|
||||
{t('screens:home.popularThisWeek')}
|
||||
<View style={styles.section}>
|
||||
<View style={styles.sectionHeader}>
|
||||
<StyledText size={FONTS.TITLE_2} weight="semibold" color={colors.text.primary}>
|
||||
{t('screens:home.popularThisWeek')}
|
||||
</StyledText>
|
||||
<Pressable hitSlop={8}>
|
||||
<StyledText size={FONTS.SUBHEADLINE} color={BRAND.PRIMARY} weight="medium">
|
||||
{t('seeAll')}
|
||||
</StyledText>
|
||||
<Pressable hitSlop={8}>
|
||||
<StyledText size={FONTS.SUBHEADLINE} color={BRAND.PRIMARY} weight="medium">
|
||||
{t('seeAll')}
|
||||
</StyledText>
|
||||
</Pressable>
|
||||
</View>
|
||||
<ScrollView
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={styles.workoutsScroll}
|
||||
>
|
||||
{filteredWorkouts.map((workout) => (
|
||||
</Pressable>
|
||||
</View>
|
||||
<ScrollView
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={styles.workoutsScroll}
|
||||
>
|
||||
{isLoadingPopular ? (
|
||||
// Loading skeletons
|
||||
<>
|
||||
<WorkoutCardSkeleton />
|
||||
<WorkoutCardSkeleton />
|
||||
<WorkoutCardSkeleton />
|
||||
</>
|
||||
) : (
|
||||
filteredWorkouts.map((workout: any) => (
|
||||
<WorkoutCard
|
||||
key={workout.id}
|
||||
workout={workout}
|
||||
variant="horizontal"
|
||||
onPress={() => handleWorkoutPress(workout.id)}
|
||||
/>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
)}
|
||||
))
|
||||
)}
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
{/* Collections Grid */}
|
||||
<View style={styles.section}>
|
||||
@@ -213,13 +220,21 @@ export default function HomeScreen() {
|
||||
</StyledText>
|
||||
</View>
|
||||
<View style={styles.collectionsGrid}>
|
||||
{translatedCollections.map((collection) => (
|
||||
<CollectionCard
|
||||
key={collection.id}
|
||||
collection={collection}
|
||||
onPress={() => handleCollectionPress(collection.id)}
|
||||
/>
|
||||
))}
|
||||
{isLoadingCollections ? (
|
||||
// Loading skeletons for collections
|
||||
<>
|
||||
<CollectionCardSkeleton />
|
||||
<CollectionCardSkeleton />
|
||||
</>
|
||||
) : (
|
||||
collections.map((collection) => (
|
||||
<CollectionCard
|
||||
key={collection.id}
|
||||
collection={collection}
|
||||
onPress={() => handleCollectionPress(collection.id)}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
@@ -19,5 +19,6 @@ export {
|
||||
useCollection,
|
||||
usePrograms,
|
||||
useFeaturedWorkouts,
|
||||
usePopularWorkouts,
|
||||
useWorkoutsByTrainer,
|
||||
} from './useSupabaseData'
|
||||
|
||||
Reference in New Issue
Block a user