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)
This commit is contained in:
Millian Lamiaux
2026-03-11 09:43:53 +01:00
parent f80798069b
commit 2ad7ae3a34
86 changed files with 19648 additions and 365 deletions

183
supabase/seed.ts Normal file
View File

@@ -0,0 +1,183 @@
/**
* TabataFit Seed Script
*
* This script seeds your Supabase database with the initial data.
* Run this after setting up your Supabase project.
*
* Usage:
* 1. Set your Supabase credentials in .env
* 2. Run: npx ts-node supabase/seed.ts
*/
import { createClient } from '@supabase/supabase-js'
import { WORKOUTS } from '../src/shared/data/workouts'
import { TRAINERS } from '../src/shared/data/trainers'
import { COLLECTIONS, PROGRAMS } from '../src/shared/data/collections'
import { ACHIEVEMENTS } from '../src/shared/data/achievements'
const supabaseUrl = process.env.EXPO_PUBLIC_SUPABASE_URL
const supabaseKey = process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY
if (!supabaseUrl || !supabaseKey) {
console.error('Missing Supabase credentials. Please set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY')
process.exit(1)
}
const supabase = createClient(supabaseUrl, supabaseKey)
async function seedTrainers() {
console.log('Seeding trainers...')
const trainers = TRAINERS.map(t => ({
id: t.id,
name: t.name,
specialty: t.specialty,
color: t.color,
avatar_url: t.avatarUrl || null,
workout_count: t.workoutCount,
}))
const { error } = await supabase.from('trainers').upsert(trainers)
if (error) throw error
console.log(`✅ Seeded ${trainers.length} trainers`)
}
async function seedWorkouts() {
console.log('Seeding workouts...')
const workouts = WORKOUTS.map(w => ({
id: w.id,
title: w.title,
trainer_id: w.trainerId,
category: w.category,
level: w.level,
duration: w.duration,
calories: w.calories,
rounds: w.rounds,
prep_time: w.prepTime,
work_time: w.workTime,
rest_time: w.restTime,
equipment: w.equipment,
music_vibe: w.musicVibe,
exercises: w.exercises,
thumbnail_url: w.thumbnailUrl || null,
video_url: w.videoUrl || null,
is_featured: w.isFeatured || false,
}))
const { error } = await supabase.from('workouts').upsert(workouts)
if (error) throw error
console.log(`✅ Seeded ${workouts.length} workouts`)
}
async function seedCollections() {
console.log('Seeding collections...')
const collections = COLLECTIONS.map(c => ({
id: c.id,
title: c.title,
description: c.description,
icon: c.icon,
gradient: c.gradient || null,
}))
const { error } = await supabase.from('collections').upsert(collections)
if (error) throw error
// Seed collection-workout relationships
const collectionWorkouts = COLLECTIONS.flatMap(c =>
c.workoutIds.map((workoutId, index) => ({
collection_id: c.id,
workout_id: workoutId,
sort_order: index,
}))
)
const { error: linkError } = await supabase.from('collection_workouts').upsert(collectionWorkouts)
if (linkError) throw linkError
console.log(`✅ Seeded ${collections.length} collections with ${collectionWorkouts.length} workout links`)
}
async function seedPrograms() {
console.log('Seeding programs...')
const programs = PROGRAMS.map(p => ({
id: p.id,
title: p.title,
description: p.description,
weeks: p.weeks,
workouts_per_week: p.workoutsPerWeek,
level: p.level,
}))
const { error } = await supabase.from('programs').upsert(programs)
if (error) throw error
// Seed program-workout relationships
let programWorkouts: { program_id: string; workout_id: string; week_number: number; day_number: number }[] = []
PROGRAMS.forEach(program => {
let week = 1
let day = 1
program.workoutIds.forEach((workoutId, index) => {
programWorkouts.push({
program_id: program.id,
workout_id: workoutId,
week_number: week,
day_number: day,
})
day++
if (day > program.workoutsPerWeek) {
day = 1
week++
}
})
})
const { error: linkError } = await supabase.from('program_workouts').upsert(programWorkouts)
if (linkError) throw linkError
console.log(`✅ Seeded ${programs.length} programs with ${programWorkouts.length} workout links`)
}
async function seedAchievements() {
console.log('Seeding achievements...')
const achievements = ACHIEVEMENTS.map(a => ({
id: a.id,
title: a.title,
description: a.description,
icon: a.icon,
requirement: a.requirement,
type: a.type,
}))
const { error } = await supabase.from('achievements').upsert(achievements)
if (error) throw error
console.log(`✅ Seeded ${achievements.length} achievements`)
}
async function main() {
try {
console.log('🌱 Starting database seed...\n')
await seedTrainers()
await seedWorkouts()
await seedCollections()
await seedPrograms()
await seedAchievements()
console.log('\n✨ Database seeded successfully!')
console.log('\nNext steps:')
console.log('1. Set up storage buckets in Supabase Dashboard')
console.log('2. Upload video and thumbnail files')
console.log('3. Create an admin user in the admin_users table')
console.log('4. Access the admin dashboard at /admin')
} catch (error) {
console.error('\n❌ Seeding failed:', error)
process.exit(1)
}
}
main()