Files
tabatago/app/admin/index.tsx
Millian Lamiaux 2ad7ae3a34 feat: Apple Watch app + Paywall + Privacy Policy + rebranding
## Major Features
- Apple Watch companion app (6 phases complete)
  - WatchConnectivity iPhone ↔ Watch
  - HealthKit integration (HR, calories)
  - SwiftUI premium UI
  - 9 complication types
  - Always-On Display support

- Paywall screen with RevenueCat integration
- Privacy Policy screen
- App rebranding: tabatago → TabataFit
- Bundle ID: com.millianlmx.tabatafit

## Changes
- New: ios/TabataFit Watch App/ (complete Watch app)
- New: app/paywall.tsx (subscription UI)
- New: app/privacy.tsx (privacy policy)
- New: src/features/watch/ (Watch sync hooks)
- New: admin-web/ (admin dashboard)
- Updated: app.json, package.json (branding)
- Updated: profile.tsx (paywall + privacy links)
- Updated: i18n translations (EN/FR/DE/ES)
- New: app icon 1024x1024

## Watch App Files
- TabataFitWatchApp.swift (entry point)
- ContentView.swift (premium UI)
- HealthKitManager.swift (HR + calories)
- WatchSessionManager.swift (communication)
- Complications/ (WidgetKit)
- UserDefaults+Shared.swift (data sharing)
2026-03-11 09:43:53 +01:00

213 lines
5.5 KiB
TypeScript

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 {
workouts,
loading: workoutsLoading,
refetch: refetchWorkouts
} = useWorkouts()
const {
trainers,
loading: trainersLoading,
refetch: refetchTrainers
} = useTrainers()
const {
collections,
loading: 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 (
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.title}>Admin Dashboard</Text>
<TouchableOpacity onPress={handleLogout} style={styles.logoutButton}>
<Text style={styles.logoutText}>Logout</Text>
</TouchableOpacity>
</View>
<ScrollView
style={styles.content}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor="#FF6B35" />
}
>
<View style={styles.statsGrid}>
<View style={styles.statCard}>
<Text style={styles.statNumber}>{workouts.length}</Text>
<Text style={styles.statLabel}>Workouts</Text>
</View>
<View style={styles.statCard}>
<Text style={styles.statNumber}>{trainers.length}</Text>
<Text style={styles.statLabel}>Trainers</Text>
</View>
<View style={styles.statCard}>
<Text style={styles.statNumber}>{collections.length}</Text>
<Text style={styles.statLabel}>Collections</Text>
</View>
</View>
<Text style={styles.sectionTitle}>Quick Actions</Text>
<View style={styles.actionsGrid}>
<TouchableOpacity
style={styles.actionCard}
onPress={() => router.push('/admin/workouts')}
>
<Text style={styles.actionIcon}>💪</Text>
<Text style={styles.actionTitle}>Manage Workouts</Text>
<Text style={styles.actionDescription}>Add, edit, or delete workouts</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.actionCard}
onPress={() => router.push('/admin/trainers')}
>
<Text style={styles.actionIcon}>👥</Text>
<Text style={styles.actionTitle}>Manage Trainers</Text>
<Text style={styles.actionDescription}>Update trainer profiles</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.actionCard}
onPress={() => router.push('/admin/collections')}
>
<Text style={styles.actionIcon}>📁</Text>
<Text style={styles.actionTitle}>Manage Collections</Text>
<Text style={styles.actionDescription}>Organize workout collections</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.actionCard}
onPress={() => router.push('/admin/media')}
>
<Text style={styles.actionIcon}>🎬</Text>
<Text style={styles.actionTitle}>Media Library</Text>
<Text style={styles.actionDescription}>Upload videos and images</Text>
</TouchableOpacity>
</View>
</ScrollView>
</View>
)
}
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',
},
})