diff --git a/app/(tabs)/CLAUDE.md b/app/(tabs)/CLAUDE.md
deleted file mode 100644
index 28c610c..0000000
--- a/app/(tabs)/CLAUDE.md
+++ /dev/null
@@ -1,43 +0,0 @@
-
-# Recent Activity
-
-
-
-### Feb 20, 2026
-
-| ID | Time | T | Title | Read |
-|----|------|---|-------|------|
-| #5056 | 8:24 AM | ✅ | Completed Host wrapper restoration in home screen | ~258 |
-| #5055 | " | ✅ | Re-added Host wrapper to home screen JSX | ~187 |
-| #5054 | " | ✅ | Re-added Host import to home screen | ~184 |
-| #5043 | 8:22 AM | ✅ | Removed closing Host tag from profile screen | ~210 |
-| #5042 | " | ✅ | Removed opening Host tag from profile screen | ~164 |
-| #5041 | " | ✅ | Removed closing Host tag from browse screen | ~187 |
-| #5040 | " | ✅ | Removed opening Host tag from browse screen | ~159 |
-| #5039 | 8:21 AM | ✅ | Removed closing Host tag from activity screen | ~193 |
-| #5038 | " | ✅ | Removed opening Host tag from activity screen | ~154 |
-
-### Apr 11, 2026
-
-| ID | Time | T | Title | Read |
-|----|------|---|-------|------|
-| #6124 | 7:41 PM | 🔵 | Home screen uses theme-based colors properly | ~229 |
-
-### Apr 13, 2026
-
-| ID | Time | T | Title | Read |
-|----|------|---|-------|------|
-| #6166 | 10:03 PM | ✅ | Updated Tab Layout Documentation | ~137 |
-| #6154 | 10:01 PM | 🔵 | Explored Explore Tab Structure | ~174 |
-
-### Apr 17, 2026
-
-| ID | Time | T | Title | Read |
-|----|------|---|-------|------|
-| #6349 | 9:48 AM | 🔄 | Removed usePurchases import from home screen | ~271 |
-| #6348 | " | 🔄 | Removed usePurchases hook from home screen | ~277 |
-| #6346 | 9:47 AM | 🔄 | Cleaned up unused imports in home screen after removing direct program navigation | ~321 |
-| #6343 | 9:46 AM | 🔄 | Refactored home screen body zone sections to clickable cards | ~400 |
-| #6342 | 9:44 AM | 🔄 | Removed direct program navigation handler from home screen | ~305 |
-| #6336 | 9:39 AM | 🔵 | Reviewed complete home screen implementation for body-zone workout programs | ~386 |
-
\ No newline at end of file
diff --git a/app/admin/_layout.tsx b/app/admin/_layout.tsx
deleted file mode 100644
index c3da7d7..0000000
--- a/app/admin/_layout.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { Stack } from 'expo-router'
-import { AdminAuthProvider, useAdminAuth } from '../../src/admin/components/AdminAuthProvider'
-import { View, ActivityIndicator } from 'react-native'
-import { Redirect } from 'expo-router'
-
-function AdminLayoutContent({ children }: { children: React.ReactNode }) {
- const { isAuthenticated, isAdmin, isLoading } = useAdminAuth()
-
- if (isLoading) {
- return (
-
-
-
- )
- }
-
- if (!isAuthenticated || !isAdmin) {
- return
- }
-
- return (
- <>
-
- {children}
- >
- )
-}
-
-export default function AdminLayout({ children }: { children: React.ReactNode }) {
- return (
-
- {children}
-
- )
-}
diff --git a/app/admin/collections.tsx b/app/admin/collections.tsx
deleted file mode 100644
index aeeeca6..0000000
--- a/app/admin/collections.tsx
+++ /dev/null
@@ -1,201 +0,0 @@
-import { useState } from 'react'
-import {
- View,
- Text,
- ScrollView,
- TouchableOpacity,
- StyleSheet,
- ActivityIndicator,
- Alert,
-} from 'react-native'
-import { useRouter } from 'expo-router'
-import { useCollections } from '../../src/shared/hooks/useSupabaseData'
-import { adminService } from '../../src/admin/services/adminService'
-import type { Collection } from '../../src/shared/types'
-
-export default function AdminCollectionsScreen() {
- const router = useRouter()
- const { data: collections = [], isLoading: loading, refetch } = useCollections()
- const [updatingId, setUpdatingId] = useState(null)
-
- const handleDelete = (collection: Collection) => {
- Alert.alert(
- 'Delete Collection',
- `Are you sure you want to delete "${collection.title}"?`,
- [
- { text: 'Cancel', style: 'cancel' },
- {
- text: 'Delete',
- style: 'destructive',
- onPress: async () => {
- Alert.alert('Info', 'Collection deletion not yet implemented')
- }
- },
- ]
- )
- }
-
- if (loading) {
- return (
-
-
-
- )
- }
-
- return (
-
-
- router.back()} style={styles.backButton}>
- ← Back
-
- Collections
-
- + Add
-
-
-
-
- {collections.map((collection: Collection) => (
-
-
- {collection.icon}
-
-
-
- {collection.title}
- {collection.description}
-
- {collection.workoutIds.length} workouts
-
-
-
-
-
- Edit
-
- handleDelete(collection)}
- disabled={updatingId === collection.id}
- >
-
- {updatingId === collection.id ? '...' : 'Delete'}
-
-
-
-
- ))}
-
-
- )
-}
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- backgroundColor: '#000',
- },
- centered: {
- justifyContent: 'center',
- alignItems: 'center',
- },
- header: {
- flexDirection: 'row',
- justifyContent: 'space-between',
- alignItems: 'center',
- padding: 20,
- paddingTop: 60,
- borderBottomWidth: 1,
- borderBottomColor: '#1C1C1E',
- },
- backButton: {
- padding: 8,
- },
- backText: {
- color: '#FF6B35',
- fontSize: 16,
- },
- title: {
- fontSize: 20,
- fontWeight: 'bold',
- color: '#fff',
- },
- addButton: {
- backgroundColor: '#FF6B35',
- paddingHorizontal: 16,
- paddingVertical: 8,
- borderRadius: 8,
- },
- addButtonText: {
- color: '#000',
- fontWeight: 'bold',
- },
- content: {
- flex: 1,
- padding: 16,
- },
- collectionCard: {
- backgroundColor: '#1C1C1E',
- borderRadius: 12,
- padding: 16,
- marginBottom: 12,
- flexDirection: 'row',
- alignItems: 'center',
- },
- iconContainer: {
- width: 48,
- height: 48,
- borderRadius: 24,
- backgroundColor: '#2C2C2E',
- justifyContent: 'center',
- alignItems: 'center',
- marginRight: 12,
- },
- icon: {
- fontSize: 24,
- },
- collectionInfo: {
- flex: 1,
- },
- collectionTitle: {
- fontSize: 16,
- fontWeight: 'bold',
- color: '#fff',
- marginBottom: 4,
- },
- collectionDescription: {
- fontSize: 14,
- color: '#999',
- marginBottom: 4,
- },
- collectionMeta: {
- fontSize: 12,
- color: '#666',
- },
- actions: {
- flexDirection: 'row',
- gap: 8,
- },
- editButton: {
- backgroundColor: '#2C2C2E',
- paddingHorizontal: 12,
- paddingVertical: 8,
- borderRadius: 6,
- },
- editText: {
- color: '#5AC8FA',
- },
- deleteButton: {
- backgroundColor: '#2C2C2E',
- paddingHorizontal: 12,
- paddingVertical: 8,
- borderRadius: 6,
- },
- disabledButton: {
- opacity: 0.5,
- },
- deleteText: {
- color: '#FF3B30',
- },
-})
diff --git a/app/admin/index.tsx b/app/admin/index.tsx
deleted file mode 100644
index e84c3a8..0000000
--- a/app/admin/index.tsx
+++ /dev/null
@@ -1,212 +0,0 @@
-import { useState, useCallback } from 'react'
-import {
- View,
- Text,
- ScrollView,
- TouchableOpacity,
- StyleSheet,
- RefreshControl,
-} from 'react-native'
-import { useRouter } from 'expo-router'
-import { useAdminAuth } from '../../src/admin/components/AdminAuthProvider'
-import { useWorkouts, useTrainers, useCollections } from '../../src/shared/hooks/useSupabaseData'
-
-export default function AdminDashboardScreen() {
- const router = useRouter()
- const { signOut } = useAdminAuth()
- const [refreshing, setRefreshing] = useState(false)
-
- const {
- data: workouts = [],
- isLoading: workoutsLoading,
- refetch: refetchWorkouts
- } = useWorkouts()
-
- const {
- data: trainers = [],
- isLoading: trainersLoading,
- refetch: refetchTrainers
- } = useTrainers()
-
- const {
- data: collections = [],
- isLoading: collectionsLoading,
- refetch: refetchCollections
- } = useCollections()
-
- const onRefresh = useCallback(async () => {
- setRefreshing(true)
- await Promise.all([
- refetchWorkouts(),
- refetchTrainers(),
- refetchCollections(),
- ])
- setRefreshing(false)
- }, [refetchWorkouts, refetchTrainers, refetchCollections])
-
- const handleLogout = async () => {
- await signOut()
- router.replace('/admin/login')
- }
-
- const isLoading = workoutsLoading || trainersLoading || collectionsLoading
-
- return (
-
-
- Admin Dashboard
-
- Logout
-
-
-
-
- }
- >
-
-
- {workouts.length}
- Workouts
-
-
- {trainers.length}
- Trainers
-
-
- {collections.length}
- Collections
-
-
-
- Quick Actions
-
-
- router.push('/admin/workouts')}
- >
- 💪
- Manage Workouts
- Add, edit, or delete workouts
-
-
- router.push('/admin/trainers')}
- >
- 👥
- Manage Trainers
- Update trainer profiles
-
-
- router.push('/admin/collections')}
- >
- 📁
- Manage Collections
- Organize workout collections
-
-
- router.push('/admin/media')}
- >
- 🎬
- Media Library
- Upload videos and images
-
-
-
-
- )
-}
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- backgroundColor: '#000',
- },
- header: {
- flexDirection: 'row',
- justifyContent: 'space-between',
- alignItems: 'center',
- padding: 20,
- paddingTop: 60,
- borderBottomWidth: 1,
- borderBottomColor: '#1C1C1E',
- },
- title: {
- fontSize: 28,
- fontWeight: 'bold',
- color: '#fff',
- },
- logoutButton: {
- padding: 8,
- },
- logoutText: {
- color: '#FF6B35',
- fontSize: 16,
- },
- content: {
- flex: 1,
- padding: 20,
- },
- statsGrid: {
- flexDirection: 'row',
- gap: 12,
- marginBottom: 32,
- },
- statCard: {
- flex: 1,
- backgroundColor: '#1C1C1E',
- borderRadius: 12,
- padding: 16,
- alignItems: 'center',
- },
- statNumber: {
- fontSize: 32,
- fontWeight: 'bold',
- color: '#FF6B35',
- },
- statLabel: {
- fontSize: 14,
- color: '#999',
- marginTop: 4,
- },
- sectionTitle: {
- fontSize: 20,
- fontWeight: 'bold',
- color: '#fff',
- marginBottom: 16,
- },
- actionsGrid: {
- flexDirection: 'row',
- flexWrap: 'wrap',
- gap: 12,
- },
- actionCard: {
- width: '47%',
- backgroundColor: '#1C1C1E',
- borderRadius: 12,
- padding: 20,
- marginBottom: 12,
- },
- actionIcon: {
- fontSize: 32,
- marginBottom: 12,
- },
- actionTitle: {
- fontSize: 16,
- fontWeight: 'bold',
- color: '#fff',
- marginBottom: 4,
- },
- actionDescription: {
- fontSize: 14,
- color: '#999',
- },
-})
diff --git a/app/admin/login.tsx b/app/admin/login.tsx
deleted file mode 100644
index b9ed77a..0000000
--- a/app/admin/login.tsx
+++ /dev/null
@@ -1,124 +0,0 @@
-import { useState } from 'react'
-import {
- View,
- Text,
- TextInput,
- TouchableOpacity,
- StyleSheet,
- ActivityIndicator,
-} from 'react-native'
-import { useAdminAuth } from '../../src/admin/components/AdminAuthProvider'
-
-export default function AdminLoginScreen() {
- const [email, setEmail] = useState('')
- const [password, setPassword] = useState('')
- const [error, setError] = useState('')
- const { signIn, isLoading } = useAdminAuth()
-
- const handleLogin = async () => {
- if (!email || !password) {
- setError('Please enter both email and password')
- return
- }
-
- setError('')
- try {
- await signIn(email, password)
- } catch (err) {
- setError(err instanceof Error ? err.message : 'Login failed')
- }
- }
-
- return (
-
-
- TabataFit Admin
- Sign in to manage content
-
- {error ? {error} : null}
-
-
-
-
-
-
- {isLoading ? (
-
- ) : (
- Sign In
- )}
-
-
-
- )
-}
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- backgroundColor: '#000',
- justifyContent: 'center',
- alignItems: 'center',
- padding: 20,
- },
- card: {
- backgroundColor: '#1C1C1E',
- borderRadius: 16,
- padding: 32,
- width: '100%',
- maxWidth: 400,
- },
- title: {
- fontSize: 28,
- fontWeight: 'bold',
- color: '#fff',
- marginBottom: 8,
- },
- subtitle: {
- fontSize: 16,
- color: '#999',
- marginBottom: 24,
- },
- errorText: {
- color: '#FF3B30',
- marginBottom: 16,
- },
- input: {
- backgroundColor: '#2C2C2E',
- borderRadius: 8,
- padding: 16,
- marginBottom: 16,
- color: '#fff',
- fontSize: 16,
- },
- button: {
- backgroundColor: '#FF6B35',
- borderRadius: 8,
- padding: 16,
- alignItems: 'center',
- },
- buttonText: {
- color: '#000',
- fontSize: 16,
- fontWeight: 'bold',
- },
-})
diff --git a/app/admin/media.tsx b/app/admin/media.tsx
deleted file mode 100644
index dd61643..0000000
--- a/app/admin/media.tsx
+++ /dev/null
@@ -1,201 +0,0 @@
-import { useState } from 'react'
-import {
- View,
- Text,
- ScrollView,
- TouchableOpacity,
- StyleSheet,
- ActivityIndicator,
- Alert,
-} from 'react-native'
-import { useRouter } from 'expo-router'
-import { supabase } from '../../src/shared/supabase'
-
-export default function AdminMediaScreen() {
- const router = useRouter()
- const [uploading, setUploading] = useState(false)
- const [activeTab, setActiveTab] = useState<'videos' | 'thumbnails' | 'avatars'>('videos')
-
- const handleUpload = async () => {
- Alert.alert('Info', 'File upload requires file picker integration. This is a placeholder.')
- }
-
- const handleDelete = async (path: string) => {
- Alert.alert(
- 'Delete File',
- `Are you sure you want to delete "${path}"?`,
- [
- { text: 'Cancel', style: 'cancel' },
- {
- text: 'Delete',
- style: 'destructive',
- onPress: async () => {
- try {
- const { error } = await supabase.storage
- .from(activeTab)
- .remove([path])
-
- if (error) throw error
- Alert.alert('Success', 'File deleted')
- } catch (err) {
- Alert.alert('Error', err instanceof Error ? err.message : 'Failed to delete')
- }
- }
- },
- ]
- )
- }
-
- return (
-
-
- router.back()} style={styles.backButton}>
- ← Back
-
- Media Library
-
- Upload
-
-
-
-
- {(['videos', 'thumbnails', 'avatars'] as const).map((tab) => (
- setActiveTab(tab)}
- >
-
- {tab.charAt(0).toUpperCase() + tab.slice(1)}
-
-
- ))}
-
-
-
-
- Storage Buckets
-
- • videos - Workout videos (MP4, MOV){'\n'}
- • thumbnails - Workout thumbnails (JPG, PNG){'\n'}
- • avatars - Trainer avatars (JPG, PNG)
-
-
-
-
- 🎬
- Media Management
-
- Upload and manage media files for workouts and trainers.{'\n\n'}
- This feature requires file picker integration.{'\n'}
- Files will be stored in Supabase Storage.
-
-
-
-
- )
-}
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- backgroundColor: '#000',
- },
- header: {
- flexDirection: 'row',
- justifyContent: 'space-between',
- alignItems: 'center',
- padding: 20,
- paddingTop: 60,
- borderBottomWidth: 1,
- borderBottomColor: '#1C1C1E',
- },
- backButton: {
- padding: 8,
- },
- backText: {
- color: '#FF6B35',
- fontSize: 16,
- },
- title: {
- fontSize: 20,
- fontWeight: 'bold',
- color: '#fff',
- },
- uploadButton: {
- backgroundColor: '#FF6B35',
- paddingHorizontal: 16,
- paddingVertical: 8,
- borderRadius: 8,
- },
- uploadButtonText: {
- color: '#000',
- fontWeight: 'bold',
- },
- tabs: {
- flexDirection: 'row',
- padding: 16,
- gap: 8,
- },
- tab: {
- flex: 1,
- paddingVertical: 12,
- paddingHorizontal: 16,
- borderRadius: 8,
- backgroundColor: '#1C1C1E',
- alignItems: 'center',
- },
- activeTab: {
- backgroundColor: '#FF6B35',
- },
- tabText: {
- color: '#999',
- fontWeight: '600',
- },
- activeTabText: {
- color: '#000',
- },
- content: {
- flex: 1,
- padding: 16,
- },
- infoCard: {
- backgroundColor: '#1C1C1E',
- borderRadius: 12,
- padding: 16,
- marginBottom: 16,
- },
- infoTitle: {
- fontSize: 16,
- fontWeight: 'bold',
- color: '#fff',
- marginBottom: 8,
- },
- infoText: {
- fontSize: 14,
- color: '#999',
- lineHeight: 20,
- },
- placeholderCard: {
- backgroundColor: '#1C1C1E',
- borderRadius: 12,
- padding: 32,
- alignItems: 'center',
- },
- placeholderIcon: {
- fontSize: 48,
- marginBottom: 16,
- },
- placeholderTitle: {
- fontSize: 18,
- fontWeight: 'bold',
- color: '#fff',
- marginBottom: 8,
- },
- placeholderText: {
- fontSize: 14,
- color: '#999',
- textAlign: 'center',
- lineHeight: 20,
- },
-})
diff --git a/app/admin/trainers.tsx b/app/admin/trainers.tsx
deleted file mode 100644
index 59a9ae6..0000000
--- a/app/admin/trainers.tsx
+++ /dev/null
@@ -1,194 +0,0 @@
-import { useState } from 'react'
-import {
- View,
- Text,
- ScrollView,
- TouchableOpacity,
- StyleSheet,
- ActivityIndicator,
- Alert,
-} from 'react-native'
-import { useRouter } from 'expo-router'
-import { useTrainers } from '../../src/shared/hooks/useSupabaseData'
-import { adminService } from '../../src/admin/services/adminService'
-import type { Trainer } from '../../src/shared/types'
-
-export default function AdminTrainersScreen() {
- const router = useRouter()
- const { data: trainers = [], isLoading: loading, refetch } = useTrainers()
- const [deletingId, setDeletingId] = useState(null)
-
- const handleDelete = (trainer: Trainer) => {
- Alert.alert(
- 'Delete Trainer',
- `Are you sure you want to delete "${trainer.name}"?`,
- [
- { text: 'Cancel', style: 'cancel' },
- {
- text: 'Delete',
- style: 'destructive',
- onPress: async () => {
- setDeletingId(trainer.id)
- try {
- await adminService.deleteTrainer(trainer.id)
- await refetch()
- } catch (err) {
- Alert.alert('Error', err instanceof Error ? err.message : 'Failed to delete')
- } finally {
- setDeletingId(null)
- }
- }
- },
- ]
- )
- }
-
- if (loading) {
- return (
-
-
-
- )
- }
-
- return (
-
-
- router.back()} style={styles.backButton}>
- ← Back
-
- Trainers
-
- + Add
-
-
-
-
- {trainers.map((trainer) => (
-
-
-
- {trainer.name}
-
- {trainer.specialty} • {trainer.workoutCount} workouts
-
-
-
-
-
- Edit
-
- handleDelete(trainer)}
- disabled={deletingId === trainer.id}
- >
-
- {deletingId === trainer.id ? '...' : 'Delete'}
-
-
-
-
- ))}
-
-
- )
-}
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- backgroundColor: '#000',
- },
- centered: {
- justifyContent: 'center',
- alignItems: 'center',
- },
- header: {
- flexDirection: 'row',
- justifyContent: 'space-between',
- alignItems: 'center',
- padding: 20,
- paddingTop: 60,
- borderBottomWidth: 1,
- borderBottomColor: '#1C1C1E',
- },
- backButton: {
- padding: 8,
- },
- backText: {
- color: '#FF6B35',
- fontSize: 16,
- },
- title: {
- fontSize: 20,
- fontWeight: 'bold',
- color: '#fff',
- },
- addButton: {
- backgroundColor: '#FF6B35',
- paddingHorizontal: 16,
- paddingVertical: 8,
- borderRadius: 8,
- },
- addButtonText: {
- color: '#000',
- fontWeight: 'bold',
- },
- content: {
- flex: 1,
- padding: 16,
- },
- trainerCard: {
- backgroundColor: '#1C1C1E',
- borderRadius: 12,
- padding: 16,
- marginBottom: 12,
- flexDirection: 'row',
- alignItems: 'center',
- },
- colorIndicator: {
- width: 12,
- height: 12,
- borderRadius: 6,
- marginRight: 12,
- },
- trainerInfo: {
- flex: 1,
- },
- trainerName: {
- fontSize: 16,
- fontWeight: 'bold',
- color: '#fff',
- marginBottom: 4,
- },
- trainerMeta: {
- fontSize: 14,
- color: '#999',
- },
- actions: {
- flexDirection: 'row',
- gap: 8,
- },
- editButton: {
- backgroundColor: '#2C2C2E',
- paddingHorizontal: 12,
- paddingVertical: 8,
- borderRadius: 6,
- },
- editText: {
- color: '#5AC8FA',
- },
- deleteButton: {
- backgroundColor: '#2C2C2E',
- paddingHorizontal: 12,
- paddingVertical: 8,
- borderRadius: 6,
- },
- disabledButton: {
- opacity: 0.5,
- },
- deleteText: {
- color: '#FF3B30',
- },
-})
diff --git a/app/admin/workouts.tsx b/app/admin/workouts.tsx
deleted file mode 100644
index 6dcfff8..0000000
--- a/app/admin/workouts.tsx
+++ /dev/null
@@ -1,190 +0,0 @@
-import { useState } from 'react'
-import {
- View,
- Text,
- ScrollView,
- TouchableOpacity,
- StyleSheet,
- ActivityIndicator,
- Alert,
-} from 'react-native'
-import { useRouter } from 'expo-router'
-import { useWorkouts } from '../../src/shared/hooks/useSupabaseData'
-import { adminService } from '../../src/admin/services/adminService'
-import type { Workout } from '../../src/shared/types'
-
-export default function AdminWorkoutsScreen() {
- const router = useRouter()
- const { data: workouts = [], isLoading: loading, error, refetch } = useWorkouts()
- const [deletingId, setDeletingId] = useState(null)
-
- const handleDelete = (workout: Workout) => {
- Alert.alert(
- 'Delete Workout',
- `Are you sure you want to delete "${workout.title}"?`,
- [
- { text: 'Cancel', style: 'cancel' },
- {
- text: 'Delete',
- style: 'destructive',
- onPress: async () => {
- setDeletingId(workout.id)
- try {
- await adminService.deleteWorkout(workout.id)
- await refetch()
- } catch (err) {
- Alert.alert('Error', err instanceof Error ? err.message : 'Failed to delete')
- } finally {
- setDeletingId(null)
- }
- }
- },
- ]
- )
- }
-
- if (loading) {
- return (
-
-
-
- )
- }
-
- return (
-
-
- router.back()} style={styles.backButton}>
- ← Back
-
- Workouts
-
- + Add
-
-
-
-
- {workouts.map((workout) => (
-
-
- {workout.title}
-
- {workout.category} • {workout.level} • {workout.duration}min
-
-
- {workout.rounds} rounds • {workout.calories} cal
-
-
-
-
- Edit
-
- handleDelete(workout)}
- disabled={deletingId === workout.id}
- >
-
- {deletingId === workout.id ? '...' : 'Delete'}
-
-
-
-
- ))}
-
-
- )
-}
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- backgroundColor: '#000',
- },
- centered: {
- justifyContent: 'center',
- alignItems: 'center',
- },
- header: {
- flexDirection: 'row',
- justifyContent: 'space-between',
- alignItems: 'center',
- padding: 20,
- paddingTop: 60,
- borderBottomWidth: 1,
- borderBottomColor: '#1C1C1E',
- },
- backButton: {
- padding: 8,
- },
- backText: {
- color: '#FF6B35',
- fontSize: 16,
- },
- title: {
- fontSize: 20,
- fontWeight: 'bold',
- color: '#fff',
- },
- addButton: {
- backgroundColor: '#FF6B35',
- paddingHorizontal: 16,
- paddingVertical: 8,
- borderRadius: 8,
- },
- addButtonText: {
- color: '#000',
- fontWeight: 'bold',
- },
- content: {
- flex: 1,
- padding: 16,
- },
- workoutCard: {
- backgroundColor: '#1C1C1E',
- borderRadius: 12,
- padding: 16,
- marginBottom: 12,
- flexDirection: 'row',
- justifyContent: 'space-between',
- alignItems: 'center',
- },
- workoutInfo: {
- flex: 1,
- },
- workoutTitle: {
- fontSize: 16,
- fontWeight: 'bold',
- color: '#fff',
- marginBottom: 4,
- },
- workoutMeta: {
- fontSize: 14,
- color: '#999',
- },
- actions: {
- flexDirection: 'row',
- gap: 8,
- },
- editButton: {
- backgroundColor: '#2C2C2E',
- paddingHorizontal: 12,
- paddingVertical: 8,
- borderRadius: 6,
- },
- editText: {
- color: '#5AC8FA',
- },
- deleteButton: {
- backgroundColor: '#2C2C2E',
- paddingHorizontal: 12,
- paddingVertical: 8,
- borderRadius: 6,
- },
- disabledButton: {
- opacity: 0.5,
- },
- deleteText: {
- color: '#FF3B30',
- },
-})
diff --git a/app/assessment.tsx b/app/assessment.tsx
deleted file mode 100644
index 3d4089a..0000000
--- a/app/assessment.tsx
+++ /dev/null
@@ -1,445 +0,0 @@
-/**
- * TabataFit Assessment Screen
- * Initial movement assessment to personalize experience
- */
-
-import { View, StyleSheet, ScrollView, Pressable } from 'react-native'
-import { useRouter } from 'expo-router'
-import { useSafeAreaInsets } from 'react-native-safe-area-context'
-import { LinearGradient } from 'expo-linear-gradient'
-import { Icon } from '@/src/shared/components/Icon'
-import { NativeButton } from '@/src/shared/components/native'
-
-import { useMemo, useState } from 'react'
-import { useTranslation } from 'react-i18next'
-import { useHaptics } from '@/src/shared/hooks'
-import { useProgramStore } from '@/src/shared/stores'
-import { ASSESSMENT_WORKOUT } from '@/src/shared/data/programs'
-import { StyledText } from '@/src/shared/components/StyledText'
-
-import { useThemeColors, BRAND } from '@/src/shared/theme'
-import { withOpacity } from '@/src/shared/utils/color'
-import type { ThemeColors } from '@/src/shared/theme/types'
-import { SPACING, LAYOUT } from '@/src/shared/constants/spacing'
-import { RADIUS } from '@/src/shared/constants/borderRadius'
-import { TEXT, GRADIENTS } from '@/src/shared/constants/colors'
-
-const FONTS = {
- LARGE_TITLE: 28,
- TITLE: 24,
- HEADLINE: 17,
- BODY: 16,
- CAPTION: 13,
-}
-
-export default function AssessmentScreen() {
- const { t } = useTranslation('screens')
- const insets = useSafeAreaInsets()
- const router = useRouter()
- const haptics = useHaptics()
- const colors = useThemeColors()
- const styles = useMemo(() => createStyles(colors), [colors])
-
- const [showIntro, setShowIntro] = useState(true)
- const skipAssessment = useProgramStore((s) => s.skipAssessment)
- const completeAssessment = useProgramStore((s) => s.completeAssessment)
-
- const handleSkip = () => {
- haptics.buttonTap()
- skipAssessment()
- router.back()
- }
-
- const handleStart = () => {
- haptics.buttonTap()
- setShowIntro(false)
- }
-
- const handleComplete = () => {
- haptics.workoutComplete()
- completeAssessment({
- completedAt: new Date().toISOString(),
- exercisesCompleted: ASSESSMENT_WORKOUT.exercises.map(e => e.name),
- })
- router.back()
- }
-
- if (!showIntro) {
- // Here we'd show the actual assessment workout player
- // For now, just show a completion screen
- return (
-
-
- setShowIntro(true)}>
-
-
-
- {t('assessment.title')}
-
-
-
-
-
-
-
- {ASSESSMENT_WORKOUT.exercises.map((exercise, index) => (
-
-
-
- {index + 1}
-
-
-
-
- {exercise.name}
-
-
- {exercise.duration}s • {exercise.purpose}
-
-
-
- ))}
-
-
-
-
- {t('assessment.tips')}
-
- {[1, 2, 3, 4].map((index) => (
-
-
-
- {t(`assessment.tip${index}`)}
-
-
- ))}
-
-
-
-
-
-
-
-
- )
- }
-
- return (
-
- {/* Header */}
-
-
-
-
-
-
-
-
- {/* Hero */}
-
-
-
-
-
-
- {t('assessment.welcomeTitle')}
-
-
-
- {t('assessment.welcomeDescription')}
-
-
-
- {/* Features */}
-
-
-
-
-
-
-
- {ASSESSMENT_WORKOUT.duration} {t('assessment.minutes')}
-
-
- {t('assessment.quickCheck')}
-
-
-
-
-
-
-
-
-
-
- {ASSESSMENT_WORKOUT.exercises.length} {t('assessment.movements')}
-
-
- {t('assessment.testMovements')}
-
-
-
-
-
-
-
-
-
-
- {t('assessment.noEquipment')}
-
-
- {t('assessment.justYourBody')}
-
-
-
-
-
- {/* Benefits */}
-
-
- {t('assessment.whatWeCheck')}
-
-
-
-
-
- {t('assessment.mobility')}
-
-
-
-
- {t('assessment.strength')}
-
-
-
-
- {t('assessment.stability')}
-
-
-
-
- {t('assessment.balance')}
-
-
-
-
-
-
- {/* Bottom Actions */}
-
-
-
-
-
-
- )
-}
-
-function createStyles(colors: ThemeColors) {
- return StyleSheet.create({
- container: {
- flex: 1,
- backgroundColor: colors.bg.base,
- },
- scrollView: {
- flex: 1,
- },
- scrollContent: {
- paddingHorizontal: LAYOUT.SCREEN_PADDING,
- },
-
- // Header
- header: {
- flexDirection: 'row',
- alignItems: 'center',
- justifyContent: 'space-between',
- paddingHorizontal: LAYOUT.SCREEN_PADDING,
- paddingVertical: SPACING[3],
- },
- backButton: {
- width: 40,
- height: 40,
- alignItems: 'center',
- justifyContent: 'center',
- },
- placeholder: {
- width: 40,
- },
-
- // Hero
- heroSection: {
- alignItems: 'center',
- marginTop: SPACING[4],
- marginBottom: SPACING[8],
- },
- iconContainer: {
- width: 100,
- height: 100,
- borderRadius: RADIUS.FULL,
- backgroundColor: withOpacity(BRAND.PRIMARY, 0.08),
- alignItems: 'center',
- justifyContent: 'center',
- marginBottom: SPACING[5],
- },
- heroTitle: {
- textAlign: 'center',
- marginBottom: SPACING[3],
- },
- heroDescription: {
- textAlign: 'center',
- lineHeight: 24,
- paddingHorizontal: SPACING[4],
- },
-
- // Features
- featuresSection: {
- marginBottom: SPACING[8],
- },
- featureItem: {
- flexDirection: 'row',
- alignItems: 'center',
- marginBottom: SPACING[4],
- backgroundColor: colors.bg.surface,
- padding: SPACING[4],
- borderRadius: RADIUS.LG,
- },
- featureIcon: {
- width: 48,
- height: 48,
- borderRadius: RADIUS.FULL,
- backgroundColor: withOpacity(BRAND.PRIMARY, 0.08),
- alignItems: 'center',
- justifyContent: 'center',
- marginRight: SPACING[3],
- },
- featureText: {
- flex: 1,
- },
-
- // Benefits
- benefitsSection: {
- marginBottom: SPACING[6],
- },
- benefitsTitle: {
- marginBottom: SPACING[3],
- },
- benefitsList: {
- flexDirection: 'row',
- flexWrap: 'wrap',
- gap: SPACING[2],
- },
- benefitTag: {
- backgroundColor: colors.bg.surface,
- paddingHorizontal: SPACING[4],
- paddingVertical: SPACING[2],
- borderRadius: RADIUS.FULL,
- borderWidth: 1,
- borderColor: colors.border.dim,
- },
-
- // Assessment Container
- assessmentContainer: {
- marginTop: SPACING[2],
- },
- exerciseList: {
- marginBottom: SPACING[6],
- },
- exerciseItem: {
- flexDirection: 'row',
- alignItems: 'center',
- backgroundColor: colors.bg.surface,
- padding: SPACING[4],
- borderRadius: RADIUS.LG,
- marginBottom: SPACING[2],
- },
- exerciseNumber: {
- width: 36,
- height: 36,
- borderRadius: RADIUS.FULL,
- backgroundColor: withOpacity(BRAND.PRIMARY, 0.08),
- alignItems: 'center',
- justifyContent: 'center',
- marginRight: SPACING[3],
- },
- exerciseInfo: {
- flex: 1,
- },
-
- // Tips
- tipsSection: {
- backgroundColor: colors.bg.surface,
- borderRadius: RADIUS.LG,
- padding: SPACING[5],
- },
- tipsTitle: {
- marginBottom: SPACING[4],
- },
- tipItem: {
- flexDirection: 'row',
- alignItems: 'flex-start',
- marginBottom: SPACING[3],
- },
- tipText: {
- marginLeft: SPACING[2],
- flex: 1,
- lineHeight: 20,
- },
-
- // Bottom Bar
- bottomBar: {
- position: 'absolute',
- bottom: 0,
- left: 0,
- right: 0,
- backgroundColor: colors.bg.base,
- paddingHorizontal: LAYOUT.SCREEN_PADDING,
- paddingTop: SPACING[3],
- borderTopWidth: 1,
- borderTopColor: colors.border.dim,
- },
- ctaButton: {
- borderRadius: RADIUS.LG,
- overflow: 'hidden',
- marginBottom: SPACING[3],
- },
- ctaGradient: {
- flexDirection: 'row',
- alignItems: 'center',
- justifyContent: 'center',
- paddingVertical: SPACING[4],
- },
- ctaIcon: {
- marginLeft: SPACING[2],
- },
- skipButton: {
- alignItems: 'center',
- paddingVertical: SPACING[2],
- },
- })
-}
diff --git a/app/collection/CLAUDE.md b/app/collection/CLAUDE.md
deleted file mode 100644
index 31017cc..0000000
--- a/app/collection/CLAUDE.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-# Recent Activity
-
-
-
-### Apr 13, 2026
-
-| ID | Time | T | Title | Read |
-|----|------|---|-------|------|
-| #6159 | 10:02 PM | 🔵 | Examined Collection Screen Explore Reference | ~150 |
-
\ No newline at end of file
diff --git a/app/collection/[id].tsx b/app/collection/[id].tsx
deleted file mode 100644
index 0d2b83e..0000000
--- a/app/collection/[id].tsx
+++ /dev/null
@@ -1,245 +0,0 @@
-/**
- * TabataFit Collection Detail Screen
- * Shows collection info + list of workouts in that collection
- */
-
-import { useMemo } from 'react'
-import { View, StyleSheet, ScrollView, Pressable } from 'react-native'
-import { useRouter, useLocalSearchParams } from 'expo-router'
-import { useSafeAreaInsets } from 'react-native-safe-area-context'
-import { Icon } from '@/src/shared/components/Icon'
-import { useTranslation } from 'react-i18next'
-
-import { useHaptics } from '@/src/shared/hooks'
-import { useCollection } from '@/src/shared/hooks/useSupabaseData'
-import { getWorkoutById } from '@/src/shared/data'
-import { useTranslatedWorkouts } from '@/src/shared/data/useTranslatedData'
-import { StyledText } from '@/src/shared/components/StyledText'
-import { track } from '@/src/shared/services/analytics'
-
-import { useThemeColors, BRAND } from '@/src/shared/theme'
-import type { ThemeColors } from '@/src/shared/theme/types'
-import { SPACING, LAYOUT } from '@/src/shared/constants/spacing'
-import { RADIUS } from '@/src/shared/constants/borderRadius'
-import { TEXT, NAVY } from '@/src/shared/constants/colors'
-
-export default function CollectionDetailScreen() {
- const insets = useSafeAreaInsets()
- const router = useRouter()
- const haptics = useHaptics()
- const { t } = useTranslation()
- const { id } = useLocalSearchParams<{ id: string }>()
-
- const colors = useThemeColors()
- const styles = useMemo(() => createStyles(colors), [colors])
-
- const { data: collection, isLoading } = useCollection(id)
-
- // Resolve workouts from collection's workoutIds
- const rawWorkouts = useMemo(() => {
- if (!collection) return []
- return collection.workoutIds
- .map((wId) => getWorkoutById(wId))
- .filter(Boolean) as NonNullable>[]
- }, [collection])
-
- const workouts = useTranslatedWorkouts(rawWorkouts)
-
- const handleBack = () => {
- haptics.selection()
- router.back()
- }
-
- const handleWorkoutPress = (workoutId: string) => {
- haptics.buttonTap()
- track('collection_workout_tapped', {
- collection_id: id,
- workout_id: workoutId,
- })
- router.push(`/workout/${workoutId}`)
- }
-
- if (isLoading) {
- return (
-
- Loading...
-
- )
- }
-
- if (!collection) {
- return (
-
-
-
- Collection not found
-
-
- )
- }
-
- return (
-
- {/* Header */}
-
-
-
-
-
- {collection.title}
-
-
-
-
-
- {/* Hero Card */}
-
-
-
-
- {collection.icon}
-
-
- {collection.title}
-
-
- {collection.description}
-
-
- {t('plurals.workout', { count: workouts.length })}
-
-
-
-
- {/* Workout List */}
-
- {t('screens:explore.workouts')}
-
-
- {workouts.map((workout) => (
- handleWorkoutPress(workout.id)}
- >
-
-
-
-
-
- {workout.title}
-
-
- {t('durationLevel', {
- duration: workout.duration,
- level: t(`levels.${(workout.level ?? 'Beginner').toLowerCase()}`),
- })}
-
-
-
-
- {t('units.calUnit', { count: workout.calories })}
-
-
-
-
- ))}
-
- {workouts.length === 0 && (
-
-
-
- No workouts in this collection
-
-
- )}
-
-
- )
-}
-
-function createStyles(colors: ThemeColors) {
- return StyleSheet.create({
- container: {
- flex: 1,
- backgroundColor: colors.bg.base,
- },
- centered: {
- alignItems: 'center',
- justifyContent: 'center',
- },
- header: {
- flexDirection: 'row',
- alignItems: 'center',
- justifyContent: 'space-between',
- paddingHorizontal: LAYOUT.SCREEN_PADDING,
- paddingVertical: SPACING[3],
- },
- backButton: {
- width: 44,
- height: 44,
- alignItems: 'center',
- justifyContent: 'center',
- },
- heroCard: {
- height: 200,
- borderRadius: RADIUS.XL,
- overflow: 'hidden',
- backgroundColor: NAVY[700],
- },
- heroContent: {
- flex: 1,
- padding: SPACING[5],
- justifyContent: 'flex-end',
- },
- heroIcon: {
- marginBottom: SPACING[2],
- },
- scrollView: {
- flex: 1,
- },
- scrollContent: {
- paddingHorizontal: LAYOUT.SCREEN_PADDING,
- },
- workoutCard: {
- flexDirection: 'row',
- alignItems: 'center',
- paddingVertical: SPACING[3],
- paddingHorizontal: SPACING[4],
- backgroundColor: colors.bg.surface,
- borderRadius: RADIUS.LG,
- marginBottom: SPACING[2],
- gap: SPACING[3],
- },
- workoutAvatar: {
- width: 44,
- height: 44,
- borderRadius: RADIUS.FULL,
- alignItems: 'center',
- justifyContent: 'center',
- },
- workoutInfo: {
- flex: 1,
- gap: 2,
- },
- workoutMeta: {
- alignItems: 'flex-end',
- gap: 4,
- },
- emptyState: {
- alignItems: 'center',
- justifyContent: 'center',
- paddingVertical: SPACING[12],
- },
- })
-}
diff --git a/app/workout/CLAUDE.md b/app/workout/CLAUDE.md
deleted file mode 100644
index 42d3fc8..0000000
--- a/app/workout/CLAUDE.md
+++ /dev/null
@@ -1,20 +0,0 @@
-
-# Recent Activity
-
-
-
-### Feb 20, 2026
-
-| ID | Time | T | Title | Read |
-|----|------|---|-------|------|
-| #5045 | 8:22 AM | ✅ | Removed closing Host tag from workout detail screen | ~188 |
-| #5044 | " | ✅ | Removed opening Host tag from workout detail screen | ~158 |
-| #5032 | 8:19 AM | ✅ | Removed Host import from workout detail screen | ~194 |
-| #5025 | 8:18 AM | 🔵 | Workout detail screen properly wraps content with Host component | ~244 |
-
-### Apr 17, 2026
-
-| ID | Time | T | Title | Read |
-|----|------|---|-------|------|
-| #6366 | 10:21 AM | 🔵 | Verified workout program player integration in workout/[id].tsx | ~348 |
-
\ No newline at end of file
diff --git a/app/workout/[id].tsx b/app/workout/[id].tsx
deleted file mode 100644
index 0049502..0000000
--- a/app/workout/[id].tsx
+++ /dev/null
@@ -1,777 +0,0 @@
-/**
- * TabataFit Pre-Workout Detail Screen
- * Clean scrollable layout — native header, no hero
- */
-
-import React, { useEffect, useRef, useState } from 'react'
-import {
- View,
- Text as RNText,
- StyleSheet,
- ScrollView,
- Pressable,
- Animated,
-} from 'react-native'
-import { Stack } from 'expo-router'
-import { useRouter, useLocalSearchParams } from 'expo-router'
-import { useSafeAreaInsets } from 'react-native-safe-area-context'
-import { Icon } from '@/src/shared/components/Icon'
-import { VideoPlayer } from '@/src/shared/components/VideoPlayer'
-import { Image } from 'expo-image'
-import { useTranslation } from 'react-i18next'
-
-import { useHaptics } from '@/src/shared/hooks'
-import { usePurchases } from '@/src/shared/hooks/usePurchases'
-import { useUserStore } from '@/src/shared/stores'
-import { track } from '@/src/shared/services/analytics'
-import { canAccessWorkout, canAccessSession } from '@/src/shared/services/access'
-import { getTabataSessionById, isTabataSession } from '@/src/shared/data/tabata'
-import { isWorkoutProgramId, parseWorkoutProgramId, fetchProgramById, workoutProgramToTabataSession } from '@/src/shared/data/workoutPrograms'
-import { BODY_ZONE_META } from '@/src/shared/types/workoutProgram'
-import type { TabataSession } from '@/src/shared/types/program'
-import { getWorkoutById, getTrainerById, getWorkoutAccentColor } from '@/src/shared/data'
-import { useTranslatedWorkout, useMusicVibeLabel } from '@/src/shared/data/useTranslatedData'
-
-import { useThemeColors } from '@/src/shared/theme'
-import type { ThemeColors } from '@/src/shared/theme/types'
-import { TYPOGRAPHY } from '@/src/shared/constants/typography'
-import { SPACING, LAYOUT } from '@/src/shared/constants/spacing'
-import { RADIUS } from '@/src/shared/constants/borderRadius'
-import { SPRING } from '@/src/shared/constants/animations'
-import { TEXT, NAVY, BRAND, GREEN, AMBER, RED, DARK, BORDER_COLORS } from '@/src/shared/constants/colors'
-import { NativeButton } from '@/src/shared/components/native'
-
-// ─── Save Button (headerRight) ───────────────────────────────────────────────
-
-function SaveButton({
- isSaved,
- onPress,
- colors,
-}: {
- isSaved: boolean
- onPress: () => void
- colors: ThemeColors
-}) {
- return (
- [
- { width: 32, height: 32, alignItems: 'center', justifyContent: 'center' },
- pressed && { opacity: 0.6 },
- ]}
- >
-
-
- )
-}
-
-// ─── Main Screen ─────────────────────────────────────────────────────────────
-
-export default function WorkoutDetailScreen() {
- const insets = useSafeAreaInsets()
- const router = useRouter()
- const haptics = useHaptics()
- const { t } = useTranslation()
- const { id } = useLocalSearchParams<{ id: string }>()
-
- // ─── Dispatch: Workout Program → Tabata session → Legacy workout ─
- if (isWorkoutProgramId(id ?? '')) {
- return
- }
-
- if (isTabataSession(id ?? '')) {
- return
- }
-
- return
-}
-
-/**
- * Workout Program Detail — loads a program tabata and delegates to TabataSessionDetailScreen
- */
-function WorkoutProgramDetailScreen({ compositeId }: { compositeId: string }) {
- const [session, setSession] = React.useState(undefined)
- const [accent, setAccent] = React.useState(GREEN[500])
- const [isFree, setIsFree] = React.useState(false)
-
- React.useEffect(() => {
- let cancelled = false
- async function load() {
- const parsed = parseWorkoutProgramId(compositeId)
- if (!parsed) { if (!cancelled) setSession(null); return }
- const program = await fetchProgramById(parsed.programId)
- if (cancelled) return
- if (!program) { setSession(null); return }
- const tabataSession = workoutProgramToTabataSession(program)
- setSession(tabataSession)
- setIsFree(program.isFree === true)
- const zoneMeta = BODY_ZONE_META[program.bodyZone]
- setAccent(program.accentColor ?? zoneMeta.color)
- }
- load()
- return () => { cancelled = true }
- }, [compositeId])
-
- if (session === undefined) {
- return (
-
-
- Chargement...
-
- )
- }
-
- if (session === null) {
- return (
-
-
- Programme non trouvé
-
- )
- }
-
- return
-}
-
-/**
- * Tabata Session Detail — shows warmup, blocks, cooldown, tabata tips
- */
-function TabataSessionDetailScreen({
- sessionId,
- sessionOverride,
- accentOverride,
- isFreeOverride,
-}: {
- sessionId: string
- sessionOverride?: TabataSession
- accentOverride?: string
- isFreeOverride?: boolean
-}) {
- const router = useRouter()
- const insets = useSafeAreaInsets()
- const haptics = useHaptics()
- const session = sessionOverride ?? getTabataSessionById(sessionId)
- const { isPremium } = usePurchases()
- const canAccess = isFreeOverride !== undefined
- ? (isPremium || isFreeOverride)
- : canAccessSession(sessionId, isPremium)
-
- if (!session) {
- return (
-
-
- Séance non trouvée
-
- )
- }
-
- const programId = sessionId.startsWith('deb-') ? 'debutant' : sessionId.startsWith('int-') ? 'intermediaire' : sessionId.startsWith('avc-') ? 'avance' : 'bureau'
- const accentMap: Record = { debutant: GREEN[500], intermediaire: BRAND.INFO, avance: RED[500], bureau: AMBER[500] }
- const accent = accentOverride ?? accentMap[programId] ?? GREEN[500]
-
- const handleStart = () => {
- haptics.buttonTap()
- track('tabata_session_start_pressed', { session_id: sessionId })
- router.push(`/player/${sessionId}`)
- }
-
- return (
-
-
-
- {/* Session info */}
-
- {session.title}
- {session.description}
-
- {session.blocks.length} bloc{session.blocks.length > 1 ? 's' : ''}
- ·
- {session.totalDuration} min
- ·
- {session.calories} cal
-
- {/* Focus tags */}
-
- {session.focus.map((f, i) => (
-
- {f}
-
- ))}
-
-
-
- {/* Warmup */}
- {session.warmup.movements.length > 0 && (
-
- Échauffement · {Math.floor(session.warmup.totalDuration / 60)} min
- {session.warmup.movements.map((m, i) => (
-
- ●
- {m.name}
- {m.duration}s
-
- ))}
-
- )}
-
- {/* Blocks */}
- {session.blocks.map((block, bi) => (
-
- Bloc {bi + 1} · {block.rounds} rounds · {block.workTime}/{block.restTime}s
-
-
- Rounds impairs
- {block.oddExercise.name}
- {block.oddExercise.conseil ? 📋 {block.oddExercise.conseil} : null}
-
-
- Rounds pairs
- {block.evenExercise.name}
- {block.evenExercise.conseil ? 📋 {block.evenExercise.conseil} : null}
-
-
-
- ))}
-
- {/* Cooldown */}
- {session.cooldown.movements.length > 0 && (
-
- Retour au calme · {Math.floor(session.cooldown.totalDuration / 60)} min
- {session.cooldown.movements.map((m, i) => (
-
- ●
- {m.name}
- {m.duration}s
-
- ))}
-
- )}
-
- {/* Equipment */}
- {session.equipment.length > 0 && (
-
- Matériel
- {session.equipment.map((eq, i) => (
- • {eq}
- ))}
-
- )}
-
-
- {/* CTA */}
-
- {canAccess ? (
-
- Commencer la séance
-
- ) : (
- router.push('/paywall')}>
- Débloquer avec Premium
-
- )}
-
-
- )
-}
-
-/**
- * Legacy workout detail — original format
- */
-function LegacyWorkoutDetailScreen({ id }: { id: string }) {
- const insets = useSafeAreaInsets()
- const router = useRouter()
- const haptics = useHaptics()
- const { t } = useTranslation()
- const savedWorkouts = useUserStore((s) => s.savedWorkouts)
- const toggleSavedWorkout = useUserStore((s) => s.toggleSavedWorkout)
- const { isPremium } = usePurchases()
-
- const colors = useThemeColors()
- const isDark = colors.colorScheme === 'dark'
-
- const rawWorkout = getWorkoutById(id ?? '1')
- const workout = useTranslatedWorkout(rawWorkout)
- const musicVibeLabel = useMusicVibeLabel(rawWorkout?.musicVibe ?? '')
- const trainer = rawWorkout ? getTrainerById(rawWorkout.trainerId) : undefined
- const accentColor = GREEN[500]
-
- // CTA entrance
- const ctaAnim = useRef(new Animated.Value(0)).current
- useEffect(() => {
- Animated.sequence([
- Animated.delay(300),
- Animated.spring(ctaAnim, {
- toValue: 1,
- ...SPRING.GENTLE,
- useNativeDriver: true,
- }),
- ]).start()
- }, [])
-
- useEffect(() => {
- if (workout) {
- track('workout_detail_viewed', {
- workout_id: workout.id,
- workout_title: workout.title,
- level: workout.level,
- duration: workout.duration,
- })
- }
- }, [workout?.id])
-
- const isSaved = savedWorkouts.includes(workout?.id?.toString() ?? '')
- const toggleSave = () => {
- if (!workout) return
- haptics.selection()
- toggleSavedWorkout(workout.id.toString())
- }
-
- if (!workout) {
- return (
- <>
-
-
-
- {t('screens:workout.notFound')}
-
-
- >
- )
- }
-
- const isLocked = !canAccessWorkout(workout.id, isPremium)
- const exerciseCount = workout.exercises?.length || 1
- const repeatCount = Math.max(1, Math.floor((workout.rounds || exerciseCount) / exerciseCount))
-
- const handleStartWorkout = () => {
- if (isLocked) {
- haptics.buttonTap()
- track('paywall_triggered', { source: 'workout_detail', workout_id: workout.id })
- router.push('/paywall')
- return
- }
- haptics.phaseChange()
- router.push(`/player/${workout.id}`)
- }
-
- const ctaBg = isDark ? TEXT.PRIMARY : NAVY[900]
- const ctaText = isDark ? NAVY[900] : TEXT.PRIMARY
- const ctaLockedBg = isDark ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.08)'
- const ctaLockedText = colors.text.primary
-
- const equipmentText = workout.equipment.length > 0
- ? workout.equipment.join(' · ')
- : t('screens:workout.noEquipment', { defaultValue: 'No equipment needed' })
-
- return (
- <>
- (
-
- ),
- }}
- />
-
-
-
- {/* Thumbnail / Video Preview */}
- {rawWorkout?.thumbnailUrl ? (
-
-
-
- ) : rawWorkout?.videoUrl ? (
-
-
-
- ) : null}
-
- {/* Title */}
-
- {workout.title}
-
-
- {/* Trainer */}
- {trainer && (
-
- with {trainer.name}
-
- )}
-
- {/* Inline metadata */}
-
-
-
-
- {t('units.minUnit', { count: workout.duration })}
-
-
- ·
-
-
-
- {t('units.calUnit', { count: workout.calories })}
-
-
- ·
-
- {t(`levels.${(workout.level ?? 'Beginner').toLowerCase()}`)}
-
-
-
- {/* Equipment */}
-
- {equipmentText}
-
-
- {/* Separator */}
-
-
- {/* Timing Card */}
-
-
-
-
- {workout.prepTime}s
-
-
- {t('screens:workout.prep', { defaultValue: 'Prep' })}
-
-
-
-
-
- {workout.workTime}s
-
-
- {t('screens:workout.work', { defaultValue: 'Work' })}
-
-
-
-
-
- {workout.restTime}s
-
-
- {t('screens:workout.rest', { defaultValue: 'Rest' })}
-
-
-
-
-
- {workout.rounds}
-
-
- {t('screens:workout.rounds', { defaultValue: 'Rounds' })}
-
-
-
-
-
- {/* Exercises Card */}
-
- {t('screens:workout.exercises', { count: workout.rounds })}
-
-
-
- {workout.exercises.map((exercise, index) => (
-
-
-
- {index + 1}
-
-
- {exercise.name}
-
-
- {exercise.duration}s
-
-
- {index < workout.exercises.length - 1 && (
-
- )}
-
- ))}
-
-
- {repeatCount > 1 && (
-
-
-
- {t('screens:workout.repeatRounds', { count: repeatCount })}
-
-
- )}
-
- {/* Music */}
-
-
-
- {t('screens:workout.musicMix', { vibe: musicVibeLabel })}
-
-
-
-
- {/* CTA */}
-
-
-
-
- >
- )
-}
-
-// ─── Styles ──────────────────────────────────────────────────────────────────
-
-const styles = StyleSheet.create({
- container: { flex: 1, backgroundColor: NAVY[900] },
- heroSection: { padding: SPACING[5], alignItems: 'center' },
- sessionTitle: { ...TYPOGRAPHY.LARGE_TITLE, color: TEXT.PRIMARY, textAlign: 'center' },
- sessionDesc: { ...TYPOGRAPHY.BODY, color: TEXT.SECONDARY, textAlign: 'center', marginTop: SPACING[2], lineHeight: 22 },
- metaRow: { flexDirection: 'row', marginTop: SPACING[4], gap: SPACING[2], justifyContent: 'center' },
- metaText: { ...TYPOGRAPHY.SUBHEADLINE, color: TEXT.SECONDARY },
- focusRow: { flexDirection: 'row', flexWrap: 'wrap', gap: SPACING[2], marginTop: SPACING[3], justifyContent: 'center' },
- focusTag: { paddingHorizontal: 10, paddingVertical: 3, borderRadius: 12, borderWidth: 1 },
- focusTagText: { fontSize: 12, fontWeight: '600' },
- section: { paddingHorizontal: SPACING[5], marginTop: SPACING[6] },
- sectionTitle: { ...TYPOGRAPHY.HEADLINE, color: TEXT.PRIMARY, marginBottom: SPACING[3] },
- movementRow: { flexDirection: 'row', alignItems: 'center', gap: SPACING[2], marginBottom: SPACING[2] },
- movementDot: { fontSize: 8, color: TEXT.TERTIARY },
- movementName: { ...TYPOGRAPHY.BODY, color: TEXT.PRIMARY, flex: 1 },
- movementDuration: { ...TYPOGRAPHY.CAPTION_1, color: TEXT.TERTIARY },
- exercisePair: { gap: SPACING[3] },
- exerciseCard: { backgroundColor: NAVY[800], borderRadius: RADIUS.MD, padding: SPACING[3], borderLeftWidth: 3 },
- exerciseLabel: { ...TYPOGRAPHY.CAPTION_2, color: TEXT.TERTIARY, textTransform: 'uppercase', letterSpacing: 0.5, marginBottom: 4 },
- exerciseName: { ...TYPOGRAPHY.TITLE_3, color: TEXT.PRIMARY },
- exerciseTip: { ...TYPOGRAPHY.CAPTION_1, color: TEXT.SECONDARY, marginTop: SPACING[1], lineHeight: 18 },
- equipText: { ...TYPOGRAPHY.SUBHEADLINE, color: TEXT.SECONDARY, marginBottom: SPACING[1] },
- ctaContainer: { position: 'absolute', bottom: 0, left: 0, right: 0, paddingHorizontal: SPACING[5], paddingTop: SPACING[3], backgroundColor: DARK.SCRIM, borderTopWidth: 1, borderTopColor: BORDER_COLORS.DIM },
- ctaButton: { height: 52, borderRadius: RADIUS.MD, borderCurve: 'continuous', alignItems: 'center', justifyContent: 'center' },
- ctaText: { ...TYPOGRAPHY.BUTTON_MEDIUM, color: NAVY[900], letterSpacing: 0.5 },
-})
-
-const s = StyleSheet.create({
- container: {
- flex: 1,
- },
- centered: {
- alignItems: 'center',
- justifyContent: 'center',
- },
- scrollContent: {
- paddingHorizontal: LAYOUT.SCREEN_PADDING,
- paddingTop: SPACING[2],
- },
-
- // Media
- mediaContainer: {
- height: 200,
- borderRadius: RADIUS.LG,
- borderCurve: 'continuous',
- overflow: 'hidden',
- marginBottom: SPACING[4],
- },
- thumbnail: {
- width: '100%',
- height: '100%',
- },
-
- // Title
- title: {
- ...TYPOGRAPHY.TITLE_1,
- marginBottom: SPACING[2],
- },
-
- // Trainer
- trainerName: {
- ...TYPOGRAPHY.SUBHEADLINE,
- marginBottom: SPACING[3],
- },
-
- // Metadata
- metaRow: {
- flexDirection: 'row',
- alignItems: 'center',
- flexWrap: 'wrap',
- gap: SPACING[2],
- marginBottom: SPACING[2],
- },
- metaItem: {
- flexDirection: 'row',
- alignItems: 'center',
- gap: 4,
- },
- metaText: {
- ...TYPOGRAPHY.SUBHEADLINE,
- },
- metaDot: {
- ...TYPOGRAPHY.SUBHEADLINE,
- },
-
- // Equipment
- equipmentText: {
- ...TYPOGRAPHY.FOOTNOTE,
- marginBottom: SPACING[4],
- },
-
- // Separator
- separator: {
- height: StyleSheet.hairlineWidth,
- marginBottom: SPACING[4],
- },
-
- // Card
- card: {
- borderRadius: RADIUS.LG,
- borderCurve: 'continuous',
- overflow: 'hidden',
- marginBottom: SPACING[4],
- },
-
- // Timing
- timingRow: {
- flexDirection: 'row',
- paddingVertical: SPACING[4],
- },
- timingItem: {
- flex: 1,
- alignItems: 'center',
- gap: 2,
- },
- timingDivider: {
- width: StyleSheet.hairlineWidth,
- alignSelf: 'stretch',
- },
- timingValue: {
- ...TYPOGRAPHY.HEADLINE,
- fontVariant: ['tabular-nums'],
- },
- timingLabel: {
- ...TYPOGRAPHY.CAPTION_2,
- textTransform: 'uppercase' as const,
- letterSpacing: 0.5,
- },
-
- // Section
- sectionTitle: {
- ...TYPOGRAPHY.HEADLINE,
- marginBottom: SPACING[3],
- },
-
- // Exercise
- exerciseRow: {
- flexDirection: 'row',
- alignItems: 'center',
- paddingVertical: SPACING[3],
- paddingHorizontal: SPACING[4],
- },
- exerciseIndex: {
- ...TYPOGRAPHY.FOOTNOTE,
- fontVariant: ['tabular-nums'],
- width: 24,
- },
- exerciseName: {
- ...TYPOGRAPHY.BODY,
- flex: 1,
- },
- exerciseDuration: {
- ...TYPOGRAPHY.SUBHEADLINE,
- fontVariant: ['tabular-nums'],
- marginLeft: SPACING[3],
- },
- exerciseSep: {
- height: StyleSheet.hairlineWidth,
- marginLeft: SPACING[4] + 24,
- marginRight: SPACING[4],
- },
- repeatRow: {
- flexDirection: 'row',
- alignItems: 'center',
- gap: SPACING[2],
- marginTop: SPACING[2],
- paddingLeft: 24,
- },
- repeatText: {
- ...TYPOGRAPHY.FOOTNOTE,
- },
-
- // Music
- musicRow: {
- flexDirection: 'row',
- alignItems: 'center',
- gap: SPACING[2],
- marginTop: SPACING[5],
- },
- musicText: {
- ...TYPOGRAPHY.FOOTNOTE,
- },
-
- // Bottom bar
- bottomBar: {
- position: 'absolute',
- bottom: 0,
- left: 0,
- right: 0,
- paddingHorizontal: LAYOUT.SCREEN_PADDING,
- paddingTop: SPACING[3],
- },
- ctaButton: {
- height: 54,
- borderRadius: RADIUS.MD,
- borderCurve: 'continuous',
- alignItems: 'center',
- justifyContent: 'center',
- flexDirection: 'row',
- },
- ctaText: {
- ...TYPOGRAPHY.BUTTON_LARGE,
- },
-})
diff --git a/app/workout/body-zone/CLAUDE.md b/app/workout/body-zone/CLAUDE.md
deleted file mode 100644
index b82cf83..0000000
--- a/app/workout/body-zone/CLAUDE.md
+++ /dev/null
@@ -1,16 +0,0 @@
-
-# Recent Activity
-
-
-
-### Apr 17, 2026
-
-| ID | Time | T | Title | Read |
-|----|------|---|-------|------|
-| #6377 | 10:28 AM | 🔴 | Fixed duplicate ScrollView opening tag in body zone detail screen | ~223 |
-| #6374 | 10:26 AM | 🔄 | Removed header section from body zone detail screen | ~260 |
-| #6363 | 10:20 AM | 🔄 | Changed program navigation to exclude explicit tabata position | ~319 |
-| #6353 | 10:02 AM | 🔄 | Simplified difficulty pill styling in body-zone detail screen | ~281 |
-| #6352 | 10:01 AM | 🔄 | Removed program count badges from difficulty filter pills | ~319 |
-| #6351 | " | 🔵 | Discovered body zone detail page with difficulty level filtering | ~364 |
-
\ No newline at end of file
diff --git a/app/workout/body-zone/[id].tsx b/app/workout/body-zone/[id].tsx
deleted file mode 100644
index ea5e50b..0000000
--- a/app/workout/body-zone/[id].tsx
+++ /dev/null
@@ -1,296 +0,0 @@
-/**
- * Body Zone Detail Screen
- * Shows workout programs filtered by body zone with difficulty pills
- */
-
-import { useState, useMemo, useEffect, useCallback } from 'react'
-import { View, StyleSheet, ScrollView, Pressable } from 'react-native'
-import { useRouter, useLocalSearchParams } from 'expo-router'
-import { useSafeAreaInsets } from 'react-native-safe-area-context'
-import { Icon } from '@/src/shared/components/Icon'
-
-import { useTranslation } from 'react-i18next'
-import { useHaptics } from '@/src/shared/hooks'
-import { usePurchases } from '@/src/shared/hooks/usePurchases'
-import { StyledText } from '@/src/shared/components/StyledText'
-import { useThemeColors } from '@/src/shared/theme'
-import type { ThemeColors } from '@/src/shared/theme/types'
-import { BRAND, GREEN, TEXT, NAVY, BORDER_COLORS } from '@/src/shared/constants/colors'
-import { SPACING, LAYOUT } from '@/src/shared/constants/spacing'
-import { RADIUS } from '@/src/shared/constants/borderRadius'
-import { canAccessWorkoutProgram } from '@/src/shared/services/access'
-import { useWorkoutProgramStore } from '@/src/shared/stores/workoutProgramStore'
-import { fetchProgramsByBodyZone, buildWorkoutProgramId } from '@/src/shared/data/workoutPrograms'
-import type { WorkoutProgram, BodyZone, ProgramLevel } from '@/src/shared/types/workoutProgram'
-import { BODY_ZONE_META, LEVEL_META } from '@/src/shared/types/workoutProgram'
-
-const LEVELS: ProgramLevel[] = ['Beginner', 'Intermediate', 'Advanced']
-
-export default function BodyZoneDetailScreen() {
- const insets = useSafeAreaInsets()
- const router = useRouter()
- const haptics = useHaptics()
- const { t } = useTranslation('screens')
- const { id } = useLocalSearchParams<{ id: string }>()
- const colors = useThemeColors()
- const { isPremium } = usePurchases()
- const isProgramCompleted = useWorkoutProgramStore(s => s.isProgramCompleted)
-
- const bodyZone = (id ?? 'full-body') as BodyZone
- const meta = BODY_ZONE_META[bodyZone]
-
- const [programs, setPrograms] = useState([])
- const [selectedLevel, setSelectedLevel] = useState('Beginner')
-
- useEffect(() => {
- fetchProgramsByBodyZone(bodyZone).then((data) => {
- setPrograms(data)
- // Default to first level that has programs
- const firstAvailable = LEVELS.find(l => data.some(p => p.level === l))
- if (firstAvailable) setSelectedLevel(firstAvailable)
- })
- }, [bodyZone])
-
- const filteredPrograms = useMemo(
- () => programs.filter(p => p.level === selectedLevel),
- [programs, selectedLevel],
- )
-
- const handleProgramPress = (program: WorkoutProgram) => {
- haptics.buttonTap()
- const isLocked = !canAccessWorkoutProgram(program, isPremium)
- if (isLocked) {
- router.push('/paywall')
- return
- }
- router.push(`/workout/${buildWorkoutProgramId(program.id)}` as any)
- }
-
- const handleLevelPress = (level: ProgramLevel) => {
- haptics.buttonTap()
- setSelectedLevel(level)
- }
-
- const accentColor = meta.color
-
- const styles = useMemo(() => createStyles(colors, accentColor), [colors, accentColor])
-
- return (
-
-
- {/* Difficulty Pills */}
-
- {LEVELS.map((level) => {
- const levelMeta = LEVEL_META[level]
- const isActive = selectedLevel === level
-
- return (
- handleLevelPress(level)}
- style={[
- styles.pill,
- {
- backgroundColor: isActive ? accentColor + '20' : NAVY[800],
- borderColor: isActive ? accentColor : BORDER_COLORS.DIM,
- },
- ]}
- >
-
- {levelMeta.label}
-
-
- )
- })}
-
-
- {/* Program Count */}
-
- {filteredPrograms.length} programme{filteredPrograms.length !== 1 ? 's' : ''} {LEVEL_META[selectedLevel].label.toLowerCase()}
-
-
- {/* Program List */}
- {filteredPrograms.map((program) => (
- handleProgramPress(program)}
- isPremium={isPremium}
- isCompleted={isProgramCompleted(program.id)}
- />
- ))}
-
- {filteredPrograms.length === 0 && (
-
-
-
- Aucun programme disponible pour ce niveau
-
-
- )}
-
-
- )
-}
-
-// ═══════════════════════════════════════════════════════════════════════════
-// PROGRAM CARD (full-width)
-// ═══════════════════════════════════════════════════════════════════════════
-
-function ProgramCard({
- program,
- accentColor,
- onPress,
- isPremium,
- isCompleted,
-}: {
- program: WorkoutProgram
- accentColor: string
- onPress: () => void
- isPremium: boolean
- isCompleted: boolean
-}) {
- const { t } = useTranslation('screens')
- const colors = useThemeColors()
- const isLocked = !canAccessWorkoutProgram(program, isPremium)
- const levelMeta = LEVEL_META[program.level]
- const color = program.accentColor ?? accentColor
-
- return (
- [
- {
- borderRadius: RADIUS.XL,
- overflow: 'hidden',
- borderWidth: 1,
- borderCurve: 'continuous' as const,
- borderColor: colors.border.dim,
- backgroundColor: colors.surface.default.backgroundColor,
- marginBottom: SPACING[3],
- opacity: pressed ? 0.85 : 1,
- },
- ]}
- >
- {/* Accent line */}
-
-
-
- {/* Title row */}
-
-
- {program.title}
-
-
- {isCompleted ? (
-
-
-
- Complété
-
-
- ) : isLocked ? (
-
-
-
- {t('home.premiumBadge')}
-
-
- ) : (
-
-
- {t('home.freeBadge')}
-
-
- )}
-
-
- {/* Description */}
- {program.description ? (
-
- {program.description}
-
- ) : null}
-
- {/* Meta row */}
-
-
-
- {program.estimatedDuration} min
-
-
-
- {program.estimatedCalories} kcal
-
-
-
- {program.tabatas.length} tabatas
-
-
-
- {/* CTA */}
-
-
-
- {isLocked ? t('home.unlockPremium') : t('home.startProgram')}
-
-
-
-
- )
-}
-
-// ═══════════════════════════════════════════════════════════════════════════
-// STYLES
-// ═══════════════════════════════════════════════════════════════════════════
-
-const createStyles = (colors: ThemeColors, accentColor: string) =>
- StyleSheet.create({
- container: {
- flex: 1,
- backgroundColor: NAVY[900],
- },
- scrollView: {
- flex: 1,
- },
- scrollContent: {
- paddingHorizontal: LAYOUT.SCREEN_PADDING,
- },
-
- // Difficulty pills
- pillsRow: {
- flexDirection: 'row',
- gap: SPACING[2],
- marginBottom: SPACING[5],
- },
- pill: {
- flex: 1,
- alignItems: 'center',
- justifyContent: 'center',
- paddingVertical: SPACING[3],
- borderRadius: RADIUS.PILL,
- borderWidth: 1,
- borderCurve: 'continuous',
- },
-
- // Results
- resultCount: {
- marginBottom: SPACING[4],
- },
-
- // Empty state
- emptyState: {
- alignItems: 'center',
- justifyContent: 'center',
- paddingVertical: SPACING[10],
- },
- })
diff --git a/app/workout/category/CLAUDE.md b/app/workout/category/CLAUDE.md
deleted file mode 100644
index 6c41707..0000000
--- a/app/workout/category/CLAUDE.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-# Recent Activity
-
-
-
-### Apr 11, 2026
-
-| ID | Time | T | Title | Read |
-|----|------|---|-------|------|
-| #6114 | 7:39 PM | 🔵 | Category detail screen imports reviewed | ~298 |
-
\ No newline at end of file
diff --git a/app/workout/category/[id].tsx b/app/workout/category/[id].tsx
deleted file mode 100644
index 553e05f..0000000
--- a/app/workout/category/[id].tsx
+++ /dev/null
@@ -1,246 +0,0 @@
-/**
- * TabataFit Category Detail Screen
- * Filtered workout list for a specific category with level sub-filter
- */
-
-import { useState, useMemo } from 'react'
-import { View, StyleSheet, ScrollView, Pressable, Text as RNText } from 'react-native'
-import { useRouter, useLocalSearchParams } from 'expo-router'
-import { useSafeAreaInsets } from 'react-native-safe-area-context'
-import { Icon } from '@/src/shared/components/Icon'
-
-import { useTranslation } from 'react-i18next'
-
-import { useHaptics } from '@/src/shared/hooks'
-import { getWorkoutsByCategory, CATEGORIES } from '@/src/shared/data'
-import type { ProgramWorkout } from '@/src/shared/types/program'
-import { useTranslatedCategories, useTranslatedWorkouts } from '@/src/shared/data/useTranslatedData'
-import { StyledText } from '@/src/shared/components/StyledText'
-import type { WorkoutCategory, WorkoutLevel } from '@/src/shared/types'
-
-import { useThemeColors, BRAND } from '@/src/shared/theme'
-import type { ThemeColors } from '@/src/shared/theme/types'
-import { SPACING, LAYOUT } from '@/src/shared/constants/spacing'
-import { RADIUS } from '@/src/shared/constants/borderRadius'
-import { TEXT, GREEN } from '@/src/shared/constants/colors'
-import { TYPOGRAPHY } from '@/src/shared/constants/typography'
-
-const LEVEL_IDS: (WorkoutLevel | 'all')[] = ['all', 'Beginner', 'Intermediate', 'Advanced']
-
-export default function CategoryDetailScreen() {
- const insets = useSafeAreaInsets()
- const router = useRouter()
- const haptics = useHaptics()
- const { t } = useTranslation()
- const { id } = useLocalSearchParams<{ id: string }>()
-
- const colors = useThemeColors()
- const styles = useMemo(() => createStyles(colors), [colors])
-
- const [selectedLevelIndex, setSelectedLevelIndex] = useState(0)
-
- const translatedCategories = useTranslatedCategories()
-
- const levelLabels = [
- t('screens:category.allLevels'),
- t('levels.beginner'),
- t('levels.intermediate'),
- t('levels.advanced'),
- ]
-
- const selectedLevel = LEVEL_IDS[selectedLevelIndex]
- const category = translatedCategories.find(c => c.id === id)
- const categoryLabel = category?.label ?? id ?? 'Category'
-
- const allWorkouts = useMemo(
- () => (id && id !== 'all') ? getWorkoutsByCategory(id as WorkoutCategory) : [],
- [id]
- )
-
- const filteredWorkouts = useMemo(() => {
- if (selectedLevel === 'all') return allWorkouts
- return allWorkouts.filter((w: ProgramWorkout) => w.focus?.[0] === selectedLevel)
- }, [allWorkouts, selectedLevel])
-
- const translatedWorkouts = useTranslatedWorkouts(filteredWorkouts)
-
- const handleBack = () => {
- haptics.selection()
- router.back()
- }
-
- const handleWorkoutPress = (workoutId: string) => {
- haptics.buttonTap()
- router.push(`/workout/${workoutId}`)
- }
-
- return (
-
- {/* Header */}
-
-
-
-
- {categoryLabel}
-
-
-
- {/* Level Filter — segmented pills */}
-
-
- {levelLabels.map((label, idx) => (
- {
- haptics.selection()
- setSelectedLevelIndex(idx)
- }}
- >
-
- {label}
-
-
- ))}
-
-
-
-
- {t('plurals.workout', { count: translatedWorkouts.length })}
-
-
-
- {translatedWorkouts.map((workout: ProgramWorkout & { title: string }) => (
- handleWorkoutPress(workout.id)}
- >
-
-
-
-
- {workout.title}
-
- {workout.duration}min • {workout.focus?.[0] ?? 'Full Body'}
-
-
-
-
-
-
- ))}
-
- {translatedWorkouts.length === 0 && (
-
-
-
- No workouts found
-
-
- )}
-
-
- )
-}
-
-function createStyles(colors: ThemeColors) {
- return StyleSheet.create({
- container: {
- flex: 1,
- backgroundColor: colors.bg.base,
- },
- header: {
- flexDirection: 'row',
- alignItems: 'center',
- justifyContent: 'space-between',
- paddingHorizontal: LAYOUT.SCREEN_PADDING,
- paddingVertical: SPACING[3],
- },
- backButton: {
- width: 44,
- height: 44,
- alignItems: 'center',
- justifyContent: 'center',
- },
- filterContainer: {
- paddingHorizontal: LAYOUT.SCREEN_PADDING,
- marginBottom: SPACING[4],
- },
- segmentedRow: {
- flexDirection: 'row',
- gap: SPACING[2],
- },
- segment: {
- flex: 1,
- paddingVertical: SPACING[2],
- paddingHorizontal: SPACING[3],
- borderRadius: RADIUS.MD,
- backgroundColor: colors.bg.surface,
- borderWidth: 1,
- borderColor: colors.border.dim,
- alignItems: 'center',
- },
- segmentActive: {
- backgroundColor: GREEN.DIM,
- borderColor: GREEN.BORDER,
- },
- segmentText: {
- fontSize: 13,
- fontWeight: '500',
- color: TEXT.TERTIARY,
- },
- segmentTextActive: {
- color: BRAND.PRIMARY,
- fontWeight: '600',
- },
- scrollView: {
- flex: 1,
- },
- scrollContent: {
- paddingHorizontal: LAYOUT.SCREEN_PADDING,
- },
- workoutCard: {
- flexDirection: 'row',
- alignItems: 'center',
- paddingVertical: SPACING[3],
- paddingHorizontal: SPACING[4],
- backgroundColor: colors.bg.surface,
- borderRadius: RADIUS.LG,
- marginBottom: SPACING[2],
- gap: SPACING[3],
- },
- workoutAvatar: {
- width: 44,
- height: 44,
- borderRadius: RADIUS.FULL,
- alignItems: 'center',
- justifyContent: 'center',
- },
- workoutInitial: {
- ...TYPOGRAPHY.HEADING_2,
- color: colors.text.primary,
- },
- workoutInfo: {
- flex: 1,
- gap: 2,
- },
- workoutMeta: {
- alignItems: 'flex-end',
- gap: 4,
- },
- emptyState: {
- alignItems: 'center',
- justifyContent: 'center',
- paddingVertical: SPACING[12],
- },
- })
-}
diff --git a/blob_https___www.3daistudio.png b/blob_https___www.3daistudio.png
deleted file mode 100644
index 32df874..0000000
Binary files a/blob_https___www.3daistudio.png and /dev/null differ
diff --git a/src/admin/components/AdminAuthProvider.tsx b/src/admin/components/AdminAuthProvider.tsx
deleted file mode 100644
index 1f1f958..0000000
--- a/src/admin/components/AdminAuthProvider.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import { createContext, useContext, useState, useEffect, ReactNode } from 'react'
-import { adminService } from '../services/adminService'
-import { logger } from '@/src/shared/utils/logger'
-
-interface AdminAuthContextType {
- isAuthenticated: boolean
- isAdmin: boolean
- isLoading: boolean
- signIn: (email: string, password: string) => Promise
- signOut: () => Promise
-}
-
-const AdminAuthContext = createContext(undefined)
-
-export function AdminAuthProvider({ children }: { children: ReactNode }) {
- const [isAuthenticated, setIsAuthenticated] = useState(false)
- const [isAdmin, setIsAdmin] = useState(false)
- const [isLoading, setIsLoading] = useState(true)
-
- useEffect(() => {
- checkAuth()
- }, [])
-
- async function checkAuth() {
- try {
- const user = await adminService.getCurrentUser()
- if (user) {
- setIsAuthenticated(true)
- const adminStatus = await adminService.isAdmin()
- setIsAdmin(adminStatus)
- }
- } catch (error) {
- logger.error('Auth check failed:', error)
- } finally {
- setIsLoading(false)
- }
- }
-
- const signIn = async (email: string, password: string) => {
- await adminService.signIn(email, password)
- setIsAuthenticated(true)
- const adminStatus = await adminService.isAdmin()
- setIsAdmin(adminStatus)
- }
-
- const signOut = async () => {
- await adminService.signOut()
- setIsAuthenticated(false)
- setIsAdmin(false)
- }
-
- return (
-
- {children}
-
- )
-}
-
-export function useAdminAuth() {
- const context = useContext(AdminAuthContext)
- if (context === undefined) {
- throw new Error('useAdminAuth must be used within an AdminAuthProvider')
- }
- return context
-}
diff --git a/src/admin/services/adminService.ts b/src/admin/services/adminService.ts
deleted file mode 100644
index ceab319..0000000
--- a/src/admin/services/adminService.ts
+++ /dev/null
@@ -1,282 +0,0 @@
-import { supabase, isSupabaseConfigured } from '../../shared/supabase'
-import type { Database } from '../../shared/supabase/database.types'
-
-type WorkoutInsert = Database['public']['Tables']['workouts']['Insert']
-type WorkoutUpdate = Database['public']['Tables']['workouts']['Update']
-type TrainerInsert = Database['public']['Tables']['trainers']['Insert']
-type TrainerUpdate = Database['public']['Tables']['trainers']['Update']
-type CollectionInsert = Database['public']['Tables']['collections']['Insert']
-type CollectionWorkoutInsert = Database['public']['Tables']['collection_workouts']['Insert']
-
-export class AdminService {
- private checkConfiguration(): boolean {
- if (!isSupabaseConfigured()) {
- throw new Error('Supabase is not configured. Please set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY')
- }
- return true
- }
-
- // Workouts
- async createWorkout(workout: Omit): Promise {
- this.checkConfiguration()
-
- const { data, error } = await supabase
- .from('workouts')
- .insert(workout as any)
- .select('id')
- .single()
-
- if (error) {
- throw new Error(`Failed to create workout: ${error.message}`)
- }
-
- return (data as any).id
- }
-
- async updateWorkout(id: string, workout: WorkoutUpdate): Promise {
- this.checkConfiguration()
-
- const { error } = await (supabase
- .from('workouts') as any)
- .update({ ...workout, updated_at: new Date().toISOString() })
- .eq('id', id)
-
- if (error) {
- throw new Error(`Failed to update workout: ${error.message}`)
- }
- }
-
- async deleteWorkout(id: string): Promise {
- this.checkConfiguration()
-
- const { error } = await supabase
- .from('workouts')
- .delete()
- .eq('id', id)
-
- if (error) {
- throw new Error(`Failed to delete workout: ${error.message}`)
- }
- }
-
- // Trainers
- async createTrainer(trainer: Omit): Promise {
- this.checkConfiguration()
-
- const { data, error } = await supabase
- .from('trainers')
- .insert(trainer as any)
- .select('id')
- .single()
-
- if (error) {
- throw new Error(`Failed to create trainer: ${error.message}`)
- }
-
- return (data as any).id
- }
-
- async updateTrainer(id: string, trainer: TrainerUpdate): Promise {
- this.checkConfiguration()
-
- const { error } = await (supabase
- .from('trainers') as any)
- .update({ ...trainer, updated_at: new Date().toISOString() })
- .eq('id', id)
-
- if (error) {
- throw new Error(`Failed to update trainer: ${error.message}`)
- }
- }
-
- async deleteTrainer(id: string): Promise {
- this.checkConfiguration()
-
- const { error } = await supabase
- .from('trainers')
- .delete()
- .eq('id', id)
-
- if (error) {
- throw new Error(`Failed to delete trainer: ${error.message}`)
- }
- }
-
- // Collections
- async createCollection(
- collection: Omit,
- workoutIds: string[]
- ): Promise {
- this.checkConfiguration()
-
- const { data: collectionData, error: collectionError } = await supabase
- .from('collections')
- .insert(collection as any)
- .select('id')
- .single()
-
- if (collectionError) {
- throw new Error(`Failed to create collection: ${collectionError.message}`)
- }
-
- const collectionWorkouts: CollectionWorkoutInsert[] = workoutIds.map((workoutId, index) => ({
- collection_id: (collectionData as any).id,
- workout_id: workoutId,
- sort_order: index,
- }))
-
- const { error: linkError } = await supabase
- .from('collection_workouts')
- .insert(collectionWorkouts as any)
-
- if (linkError) {
- throw new Error(`Failed to link workouts to collection: ${linkError.message}`)
- }
-
- return (collectionData as any).id
- }
-
- async updateCollectionWorkouts(collectionId: string, workoutIds: string[]): Promise {
- this.checkConfiguration()
-
- const { error: deleteError } = await supabase
- .from('collection_workouts')
- .delete()
- .eq('collection_id', collectionId)
-
- if (deleteError) {
- throw new Error(`Failed to remove existing workouts: ${deleteError.message}`)
- }
-
- const collectionWorkouts: CollectionWorkoutInsert[] = workoutIds.map((workoutId, index) => ({
- collection_id: collectionId,
- workout_id: workoutId,
- sort_order: index,
- }))
-
- const { error: insertError } = await supabase
- .from('collection_workouts')
- .insert(collectionWorkouts as any)
-
- if (insertError) {
- throw new Error(`Failed to add new workouts: ${insertError.message}`)
- }
- }
-
- // Storage
- async uploadVideo(file: File, path: string): Promise {
- this.checkConfiguration()
-
- const { error: uploadError } = await supabase.storage
- .from('videos')
- .upload(path, file)
-
- if (uploadError) {
- throw new Error(`Failed to upload video: ${uploadError.message}`)
- }
-
- const { data: { publicUrl } } = supabase.storage
- .from('videos')
- .getPublicUrl(path)
-
- return publicUrl
- }
-
- async uploadThumbnail(file: File, path: string): Promise {
- this.checkConfiguration()
-
- const { error: uploadError } = await supabase.storage
- .from('thumbnails')
- .upload(path, file)
-
- if (uploadError) {
- throw new Error(`Failed to upload thumbnail: ${uploadError.message}`)
- }
-
- const { data: { publicUrl } } = supabase.storage
- .from('thumbnails')
- .getPublicUrl(path)
-
- return publicUrl
- }
-
- async uploadAvatar(file: File, path: string): Promise {
- this.checkConfiguration()
-
- const { error: uploadError } = await supabase.storage
- .from('avatars')
- .upload(path, file)
-
- if (uploadError) {
- throw new Error(`Failed to upload avatar: ${uploadError.message}`)
- }
-
- const { data: { publicUrl } } = supabase.storage
- .from('avatars')
- .getPublicUrl(path)
-
- return publicUrl
- }
-
- async deleteVideo(path: string): Promise {
- this.checkConfiguration()
-
- const { error } = await supabase.storage
- .from('videos')
- .remove([path])
-
- if (error) {
- throw new Error(`Failed to delete video: ${error.message}`)
- }
- }
-
- async deleteThumbnail(path: string): Promise {
- this.checkConfiguration()
-
- const { error } = await supabase.storage
- .from('thumbnails')
- .remove([path])
-
- if (error) {
- throw new Error(`Failed to delete thumbnail: ${error.message}`)
- }
- }
-
- // Admin authentication
- async signIn(email: string, password: string): Promise {
- this.checkConfiguration()
-
- const { error } = await supabase.auth.signInWithPassword({
- email,
- password,
- })
-
- if (error) {
- throw new Error(`Authentication failed: ${error.message}`)
- }
- }
-
- async signOut(): Promise {
- await supabase.auth.signOut()
- }
-
- async getCurrentUser() {
- const { data: { user } } = await supabase.auth.getUser()
- return user
- }
-
- async isAdmin(): Promise {
- const user = await this.getCurrentUser()
- if (!user) return false
-
- const { data, error } = await supabase
- .from('admin_users')
- .select('*')
- .eq('id', user.id)
- .single()
-
- return !error && !!data
- }
-}
-
-export const adminService = new AdminService()