feat: explore tab, React Query data layer, programs, sync, analytics, testing infrastructure

- Replace browse tab with Supabase-connected explore tab with filters
- Add React Query for data fetching with loading states
- Add 3 structured programs with weekly progression
- Add Supabase anonymous auth sync service
- Add PostHog analytics with screen tracking and events
- Add comprehensive test strategy (Vitest + Maestro E2E)
- Add RevenueCat subscription system with DEV simulation
- Add i18n translations for new screens (EN/FR/DE/ES)
- Add data deletion modal, sync consent modal
- Add assessment screen and program routes
- Add GitHub Actions CI workflow
- Update activity store with sync integration
This commit is contained in:
Millian Lamiaux
2026-03-24 12:04:48 +01:00
parent 8703c484e8
commit cd065d07c3
138 changed files with 26819 additions and 1043 deletions

View File

@@ -3,7 +3,7 @@
* Celebration with real data from activity store
*/
import { useRef, useEffect, useMemo } from 'react'
import { useRef, useEffect, useMemo, useState } from 'react'
import {
View,
Text as RNText,
@@ -23,9 +23,12 @@ import * as Sharing from 'expo-sharing'
import { useTranslation } from 'react-i18next'
import { useHaptics } from '@/src/shared/hooks'
import { useActivityStore } from '@/src/shared/stores'
import { useActivityStore, useUserStore } from '@/src/shared/stores'
import { getWorkoutById, getPopularWorkouts } from '@/src/shared/data'
import { useTranslatedWorkout, useTranslatedWorkouts } from '@/src/shared/data/useTranslatedData'
import { SyncConsentModal } from '@/src/shared/components/SyncConsentModal'
import { enableSync } from '@/src/shared/services/sync'
import type { WorkoutSessionData } from '@/src/shared/types'
import { useThemeColors, BRAND } from '@/src/shared/theme'
import type { ThemeColors } from '@/src/shared/theme/types'
@@ -270,6 +273,10 @@ export default function WorkoutCompleteScreen() {
const history = useActivityStore((s) => s.history)
const recentWorkouts = history.slice(0, 1)
// Sync consent modal state
const [showSyncPrompt, setShowSyncPrompt] = useState(false)
const { profile, setSyncStatus } = useUserStore()
// Get the most recent result for this workout
const latestResult = recentWorkouts[0]
const resultCalories = latestResult?.calories ?? workout?.calories ?? 45
@@ -299,6 +306,54 @@ export default function WorkoutCompleteScreen() {
router.push(`/workout/${workoutId}`)
}
// Check if we should show sync prompt (after first workout for premium users)
useEffect(() => {
if (profile.syncStatus === 'prompt-pending') {
// Wait a moment for the user to see their results first
const timer = setTimeout(() => {
setShowSyncPrompt(true)
}, 1500)
return () => clearTimeout(timer)
}
}, [profile.syncStatus])
const handleSyncAccept = async () => {
setShowSyncPrompt(false)
// Prepare data for sync
const profileData = {
name: profile.name,
fitnessLevel: profile.fitnessLevel,
goal: profile.goal,
weeklyFrequency: profile.weeklyFrequency,
barriers: profile.barriers,
onboardingCompletedAt: new Date().toISOString(),
}
// Get all workout history for retroactive sync
const workoutHistory: WorkoutSessionData[] = history.map((w) => ({
workoutId: w.workoutId,
completedAt: new Date(w.completedAt).toISOString(),
durationSeconds: w.durationMinutes * 60,
caloriesBurned: w.calories,
}))
// Enable sync
const result = await enableSync(profileData, workoutHistory)
if (result.success) {
setSyncStatus('synced', result.userId || null)
} else {
// Show error - sync failed
setSyncStatus('never-synced')
}
}
const handleSyncDecline = () => {
setShowSyncPrompt(false)
setSyncStatus('never-synced') // Reset so we don't ask again
}
// Simulate percentile
const burnBarPercentile = Math.min(95, Math.max(40, Math.round((resultCalories / (workout?.calories ?? 45)) * 70)))
@@ -385,6 +440,13 @@ export default function WorkoutCompleteScreen() {
</PrimaryButton>
</View>
</View>
{/* Sync Consent Modal */}
<SyncConsentModal
visible={showSyncPrompt}
onAccept={handleSyncAccept}
onDecline={handleSyncDecline}
/>
</View>
)
}