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:
Millian Lamiaux
2026-03-17 14:29:27 +01:00
parent b1741e965c
commit 197324188c
2 changed files with 66 additions and 50 deletions

View File

@@ -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>