Files
tabatago/app/admin/trainers.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

195 lines
4.7 KiB
TypeScript

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 { trainers, loading, refetch } = useTrainers()
const [deletingId, setDeletingId] = useState<string | null>(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 (
<View style={[styles.container, styles.centered]}>
<ActivityIndicator size="large" color="#FF6B35" />
</View>
)
}
return (
<View style={styles.container}>
<View style={styles.header}>
<TouchableOpacity onPress={() => router.back()} style={styles.backButton}>
<Text style={styles.backText}> Back</Text>
</TouchableOpacity>
<Text style={styles.title}>Trainers</Text>
<TouchableOpacity style={styles.addButton}>
<Text style={styles.addButtonText}>+ Add</Text>
</TouchableOpacity>
</View>
<ScrollView style={styles.content}>
{trainers.map((trainer) => (
<View key={trainer.id} style={styles.trainerCard}>
<View style={[styles.colorIndicator, { backgroundColor: trainer.color }]} />
<View style={styles.trainerInfo}>
<Text style={styles.trainerName}>{trainer.name}</Text>
<Text style={styles.trainerMeta}>
{trainer.specialty} {trainer.workoutCount} workouts
</Text>
</View>
<View style={styles.actions}>
<TouchableOpacity style={styles.editButton}>
<Text style={styles.editText}>Edit</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.deleteButton, deletingId === trainer.id && styles.disabledButton]}
onPress={() => handleDelete(trainer)}
disabled={deletingId === trainer.id}
>
<Text style={styles.deleteText}>
{deletingId === trainer.id ? '...' : 'Delete'}
</Text>
</TouchableOpacity>
</View>
</View>
))}
</ScrollView>
</View>
)
}
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',
},
})