From 5477ecb852a03121cb72324d46480054c198d752 Mon Sep 17 00:00:00 2001 From: Millian Lamiaux Date: Fri, 20 Feb 2026 13:23:32 +0100 Subject: [PATCH] feat: data layer with types, 50 workouts, and Zustand stores Types: Workout, Exercise, Trainer, UserProfile, UserSettings, WorkoutResult, Achievement, Collection, Program. Mock data: 50 workouts across 5 categories (full-body, core, upper-body, lower-body, cardio), 4 durations (4/8/12/20 min), 3 levels, 5 trainers. Plus 6 collections, 3 programs, and 8 achievements. Zustand stores with AsyncStorage persistence: - userStore: profile, settings (haptics, sound, voice, reminders) - activityStore: workout history, streak tracking - playerStore: ephemeral timer state (not persisted) Helper lookups: getWorkoutById, getWorkoutsByCategory, getTrainerById, getPopularWorkouts, getCollectionWorkouts, etc. Co-Authored-By: Claude Opus 4.6 --- src/shared/data/achievements.ts | 72 ++ src/shared/data/collections.ts | 83 +++ src/shared/data/index.ts | 117 +++ src/shared/data/trainers.ts | 44 ++ src/shared/data/workouts.ts | 1079 ++++++++++++++++++++++++++++ src/shared/stores/CLAUDE.md | 13 + src/shared/stores/activityStore.ts | 109 +++ src/shared/stores/index.ts | 7 + src/shared/stores/playerStore.ts | 75 ++ src/shared/stores/userStore.ts | 57 ++ src/shared/types/activity.ts | 32 + src/shared/types/index.ts | 8 + src/shared/types/trainer.ts | 12 + src/shared/types/user.ts | 29 + src/shared/types/workout.ts | 62 ++ 15 files changed, 1799 insertions(+) create mode 100644 src/shared/data/achievements.ts create mode 100644 src/shared/data/collections.ts create mode 100644 src/shared/data/index.ts create mode 100644 src/shared/data/trainers.ts create mode 100644 src/shared/data/workouts.ts create mode 100644 src/shared/stores/CLAUDE.md create mode 100644 src/shared/stores/activityStore.ts create mode 100644 src/shared/stores/index.ts create mode 100644 src/shared/stores/playerStore.ts create mode 100644 src/shared/stores/userStore.ts create mode 100644 src/shared/types/activity.ts create mode 100644 src/shared/types/index.ts create mode 100644 src/shared/types/trainer.ts create mode 100644 src/shared/types/user.ts create mode 100644 src/shared/types/workout.ts diff --git a/src/shared/data/achievements.ts b/src/shared/data/achievements.ts new file mode 100644 index 0000000..a4f2a2e --- /dev/null +++ b/src/shared/data/achievements.ts @@ -0,0 +1,72 @@ +/** + * TabataFit Achievement Definitions + */ + +import { Achievement } from '../types' + +export const ACHIEVEMENTS: Achievement[] = [ + { + id: 'first-burn', + title: 'First Burn', + description: 'Complete your first workout', + icon: 'flame', + requirement: 1, + type: 'workouts', + }, + { + id: 'week-warrior', + title: 'Week Warrior', + description: '7 day streak', + icon: 'calendar', + requirement: 7, + type: 'streak', + }, + { + id: 'century-club', + title: 'Century Club', + description: 'Burn 100 calories total', + icon: 'flame', + requirement: 100, + type: 'calories', + }, + { + id: 'iron-will', + title: 'Iron Will', + description: 'Complete 10 workouts', + icon: 'trophy', + requirement: 10, + type: 'workouts', + }, + { + id: 'tabata-master', + title: 'Tabata Master', + description: 'Complete 50 workouts', + icon: 'star', + requirement: 50, + type: 'workouts', + }, + { + id: 'marathon-burner', + title: 'Marathon Burner', + description: 'Exercise for 100 minutes total', + icon: 'time', + requirement: 100, + type: 'minutes', + }, + { + id: 'unstoppable', + title: 'Unstoppable', + description: '30 day streak', + icon: 'rocket', + requirement: 30, + type: 'streak', + }, + { + id: 'calorie-crusher', + title: 'Calorie Crusher', + description: 'Burn 1000 calories total', + icon: 'flame', + requirement: 1000, + type: 'calories', + }, +] diff --git a/src/shared/data/collections.ts b/src/shared/data/collections.ts new file mode 100644 index 0000000..924ea5c --- /dev/null +++ b/src/shared/data/collections.ts @@ -0,0 +1,83 @@ +/** + * TabataFit Collections & Programs + */ + +import { Collection, Program } from '../types' + +export const COLLECTIONS: Collection[] = [ + { + id: 'morning-energizer', + title: 'Morning Energizer', + description: 'Start your day right', + icon: '🌅', + workoutIds: ['4', '6', '43', '47', '10'], + }, + { + id: 'no-equipment', + title: 'No Equipment', + description: 'Workout anywhere', + icon: '💪', + workoutIds: ['1', '4', '6', '11', '13', '16', '17', '19', '23', '26', '31', '38', '42', '43', '45'], + }, + { + id: '7-day-burn', + title: '7-Day Burn Challenge', + description: 'Transform in one week', + icon: '🔥', + workoutIds: ['1', '11', '31', '42', '6', '17', '23'], + gradient: ['#FF6B35', '#FF3B30'], + }, + { + id: 'quick-intense', + title: 'Quick & Intense', + description: 'Max effort in 4 minutes', + icon: '⚡', + workoutIds: ['1', '11', '23', '35', '38', '42', '6', '17'], + }, + { + id: 'core-focus', + title: 'Core Focus', + description: 'Build a solid foundation', + icon: '🎯', + workoutIds: ['11', '12', '13', '14', '16', '17'], + }, + { + id: 'leg-day', + title: 'Leg Day', + description: 'Never skip leg day', + icon: '🦵', + workoutIds: ['31', '32', '33', '34', '35', '36', '37'], + }, +] + +export const PROGRAMS: Program[] = [ + { + id: 'beginner-journey', + title: 'Beginner Journey', + description: 'Your first steps into Tabata fitness', + weeks: 2, + workoutsPerWeek: 3, + level: 'Beginner', + workoutIds: ['1', '13', '31', '43', '6', '16'], + }, + { + id: 'strength-builder', + title: 'Strength Builder', + description: 'Progressive strength development', + weeks: 4, + workoutsPerWeek: 4, + level: 'Intermediate', + workoutIds: ['2', '21', '32', '9', '14', '25', '35', '29', '7', '23', '39', '44', '11', '42', '8', '27'], + }, + { + id: 'fat-burn-protocol', + title: 'Fat Burn Protocol', + description: 'Maximum calorie burn program', + weeks: 6, + workoutsPerWeek: 5, + level: 'Advanced', + workoutIds: ['3', '41', '12', '34', '46', '5', '18', '27', '36', '50', '8', '15', '24', '38', '44', '20', '30', '40', '46', '50', '3', '41', '5', '8', '12', '15', '18', '24', '27', '34'], + }, +] + +export const FEATURED_COLLECTION_ID = '7-day-burn' diff --git a/src/shared/data/index.ts b/src/shared/data/index.ts new file mode 100644 index 0000000..76bf63b --- /dev/null +++ b/src/shared/data/index.ts @@ -0,0 +1,117 @@ +/** + * TabataFit Data Layer + * Single source of truth + helper lookups + */ + +import { WORKOUTS } from './workouts' +import { TRAINERS } from './trainers' +import { COLLECTIONS, PROGRAMS, FEATURED_COLLECTION_ID } from './collections' +import { ACHIEVEMENTS } from './achievements' +import type { WorkoutCategory, WorkoutLevel, WorkoutDuration } from '../types' + +// Re-export raw data +export { WORKOUTS, TRAINERS, COLLECTIONS, PROGRAMS, ACHIEVEMENTS, FEATURED_COLLECTION_ID } + +// ═══════════════════════════════════════════════════════════════════════════ +// WORKOUT LOOKUPS +// ═══════════════════════════════════════════════════════════════════════════ + +export function getWorkoutById(id: string) { + return WORKOUTS.find(w => w.id === id) +} + +export function getWorkoutsByCategory(category: WorkoutCategory) { + return WORKOUTS.filter(w => w.category === category) +} + +export function getWorkoutsByTrainer(trainerId: string) { + return WORKOUTS.filter(w => w.trainerId === trainerId) +} + +export function getWorkoutsByLevel(level: WorkoutLevel) { + return WORKOUTS.filter(w => w.level === level) +} + +export function getWorkoutsByDuration(duration: WorkoutDuration) { + return WORKOUTS.filter(w => w.duration === duration) +} + +export function getFeaturedWorkouts() { + return WORKOUTS.filter(w => w.isFeatured) +} + +export function getPopularWorkouts(count = 8) { + // Simulate popularity — pick a diverse spread + return WORKOUTS.filter(w => ['1', '11', '21', '31', '41', '42', '2', '32'].includes(w.id)).slice(0, count) +} + +// ═══════════════════════════════════════════════════════════════════════════ +// TRAINER LOOKUPS +// ═══════════════════════════════════════════════════════════════════════════ + +export function getTrainerById(id: string) { + return TRAINERS.find(t => t.id === id) +} + +export function getTrainerByName(name: string) { + return TRAINERS.find(t => t.name.toLowerCase() === name.toLowerCase()) +} + +// ═══════════════════════════════════════════════════════════════════════════ +// COLLECTION LOOKUPS +// ═══════════════════════════════════════════════════════════════════════════ + +export function getCollectionById(id: string) { + return COLLECTIONS.find(c => c.id === id) +} + +export function getCollectionWorkouts(collectionId: string) { + const collection = getCollectionById(collectionId) + if (!collection) return [] + return collection.workoutIds.map(id => getWorkoutById(id)).filter(Boolean) +} + +export function getFeaturedCollection() { + return getCollectionById(FEATURED_COLLECTION_ID) +} + +// ═══════════════════════════════════════════════════════════════════════════ +// PROGRAM LOOKUPS +// ═══════════════════════════════════════════════════════════════════════════ + +export function getProgramById(id: string) { + return PROGRAMS.find(p => p.id === id) +} + +// ═══════════════════════════════════════════════════════════════════════════ +// CATEGORY METADATA +// ═══════════════════════════════════════════════════════════════════════════ + +export const CATEGORIES: { id: WorkoutCategory | 'all'; label: string }[] = [ + { id: 'all', label: 'All' }, + { id: 'full-body', label: 'Full Body' }, + { id: 'core', label: 'Core' }, + { id: 'upper-body', label: 'Upper Body' }, + { id: 'lower-body', label: 'Lower Body' }, + { id: 'cardio', label: 'Cardio' }, +] + +// SF Symbol icon map for collections +export const COLLECTION_ICONS: Record = { + 'morning-energizer': 'sunrise.fill', + 'no-equipment': 'figure.strengthtraining.traditional', + '7-day-burn': 'flame.fill', + 'quick-intense': 'bolt.fill', + 'core-focus': 'target', + 'leg-day': 'figure.walk', +} + +// Collection color map +export const COLLECTION_COLORS: Record = { + 'morning-energizer': '#FFD60A', + 'no-equipment': '#30D158', + '7-day-burn': '#FF3B30', + 'quick-intense': '#FF3B30', + 'core-focus': '#5AC8FA', + 'leg-day': '#BF5AF2', +} diff --git a/src/shared/data/trainers.ts b/src/shared/data/trainers.ts new file mode 100644 index 0000000..b8aee82 --- /dev/null +++ b/src/shared/data/trainers.ts @@ -0,0 +1,44 @@ +/** + * TabataFit Trainer Data + * 5 trainers per PRD + */ + +import { Trainer } from '../types' + +export const TRAINERS: Trainer[] = [ + { + id: 'emma', + name: 'Emma', + specialty: 'Full Body', + color: '#FF6B35', + workoutCount: 15, + }, + { + id: 'jake', + name: 'Jake', + specialty: 'Strength', + color: '#FFD60A', + workoutCount: 12, + }, + { + id: 'mia', + name: 'Mia', + specialty: 'Core', + color: '#30D158', + workoutCount: 10, + }, + { + id: 'alex', + name: 'Alex', + specialty: 'Cardio', + color: '#5AC8FA', + workoutCount: 8, + }, + { + id: 'sofia', + name: 'Sofia', + specialty: 'Recovery', + color: '#BF5AF2', + workoutCount: 5, + }, +] diff --git a/src/shared/data/workouts.ts b/src/shared/data/workouts.ts new file mode 100644 index 0000000..8381bae --- /dev/null +++ b/src/shared/data/workouts.ts @@ -0,0 +1,1079 @@ +/** + * TabataFit Workout Data + * 50 workouts across 5 categories, 4 durations, 3 levels, 5 trainers + */ + +import { Workout } from '../types' + +export const WORKOUTS: Workout[] = [ + // ═══════════════════════════════════════════════════════════════════════════ + // FULL BODY (10 workouts) + // ═══════════════════════════════════════════════════════════════════════════ + { + id: '1', + title: 'Full Body Ignite', + trainerId: 'emma', + category: 'full-body', + level: 'Beginner', + duration: 4, + calories: 45, + rounds: 8, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['No equipment required', 'Yoga mat optional'], + musicVibe: 'electronic', + exercises: [ + { name: 'Jumping Jacks', duration: 20 }, + { name: 'Squats', duration: 20 }, + { name: 'Push-ups', duration: 20 }, + { name: 'High Knees', duration: 20 }, + ], + isFeatured: true, + }, + { + id: '2', + title: 'Total Body Blast', + trainerId: 'jake', + category: 'full-body', + level: 'Intermediate', + duration: 8, + calories: 90, + rounds: 16, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Dumbbells optional'], + musicVibe: 'hip-hop', + exercises: [ + { name: 'Burpees', duration: 20 }, + { name: 'Lunges', duration: 20 }, + { name: 'Push-up to Row', duration: 20 }, + { name: 'Mountain Climbers', duration: 20 }, + ], + }, + { + id: '3', + title: 'Power Surge', + trainerId: 'alex', + category: 'full-body', + level: 'Advanced', + duration: 12, + calories: 140, + rounds: 24, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Kettlebell recommended'], + musicVibe: 'rock', + exercises: [ + { name: 'Thrusters', duration: 20 }, + { name: 'Box Jumps', duration: 20 }, + { name: 'Renegade Rows', duration: 20 }, + { name: 'Tuck Jumps', duration: 20 }, + ], + }, + { + id: '4', + title: 'Morning Wake-Up', + trainerId: 'emma', + category: 'full-body', + level: 'Beginner', + duration: 4, + calories: 40, + rounds: 8, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['No equipment required'], + musicVibe: 'pop', + exercises: [ + { name: 'Arm Circles', duration: 20 }, + { name: 'Bodyweight Squats', duration: 20 }, + { name: 'Knee Push-ups', duration: 20 }, + { name: 'Marching in Place', duration: 20 }, + ], + }, + { + id: '5', + title: 'Endurance Builder', + trainerId: 'alex', + category: 'full-body', + level: 'Advanced', + duration: 20, + calories: 220, + rounds: 40, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Dumbbells', 'Yoga mat'], + musicVibe: 'electronic', + exercises: [ + { name: 'Devil Press', duration: 20 }, + { name: 'Squat Jumps', duration: 20 }, + { name: 'Push-up Burpees', duration: 20 }, + { name: 'Plank Jacks', duration: 20 }, + ], + }, + { + id: '6', + title: 'Quick Burn', + trainerId: 'emma', + category: 'full-body', + level: 'Beginner', + duration: 4, + calories: 42, + rounds: 8, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['No equipment required'], + musicVibe: 'pop', + exercises: [ + { name: 'Star Jumps', duration: 20 }, + { name: 'Wall Sit', duration: 20 }, + { name: 'Tricep Dips', duration: 20 }, + { name: 'Butt Kicks', duration: 20 }, + ], + }, + { + id: '7', + title: 'Functional Flow', + trainerId: 'sofia', + category: 'full-body', + level: 'Intermediate', + duration: 8, + calories: 85, + rounds: 16, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Resistance band optional'], + musicVibe: 'chill', + exercises: [ + { name: 'Inchworms', duration: 20 }, + { name: 'Curtsy Lunges', duration: 20 }, + { name: 'Bear Crawls', duration: 20 }, + { name: 'Squat Pulses', duration: 20 }, + ], + }, + { + id: '8', + title: 'Athletic Power', + trainerId: 'jake', + category: 'full-body', + level: 'Advanced', + duration: 12, + calories: 145, + rounds: 24, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Medicine ball'], + musicVibe: 'rock', + exercises: [ + { name: 'Clean & Press', duration: 20 }, + { name: 'Lateral Bounds', duration: 20 }, + { name: 'Slam Ball', duration: 20 }, + { name: 'Sprawls', duration: 20 }, + ], + }, + { + id: '9', + title: 'Sweat Session', + trainerId: 'mia', + category: 'full-body', + level: 'Intermediate', + duration: 8, + calories: 88, + rounds: 16, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['No equipment required'], + musicVibe: 'electronic', + exercises: [ + { name: 'Jumping Lunges', duration: 20 }, + { name: 'Push-up to T', duration: 20 }, + { name: 'Speed Skaters', duration: 20 }, + { name: 'Plank Shoulder Taps', duration: 20 }, + ], + }, + { + id: '10', + title: 'Total Tone', + trainerId: 'sofia', + category: 'full-body', + level: 'Beginner', + duration: 4, + calories: 38, + rounds: 8, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['No equipment required'], + musicVibe: 'pop', + exercises: [ + { name: 'Side Lunges', duration: 20 }, + { name: 'Diamond Push-ups', duration: 20 }, + { name: 'Glute Bridges', duration: 20 }, + { name: 'Toe Touches', duration: 20 }, + ], + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // CORE (10 workouts) + // ═══════════════════════════════════════════════════════════════════════════ + { + id: '11', + title: 'Core Crusher', + trainerId: 'mia', + category: 'core', + level: 'Intermediate', + duration: 4, + calories: 38, + rounds: 8, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Yoga mat'], + musicVibe: 'electronic', + exercises: [ + { name: 'Crunches', duration: 20 }, + { name: 'Russian Twists', duration: 20 }, + { name: 'Leg Raises', duration: 20 }, + { name: 'Plank Hold', duration: 20 }, + ], + }, + { + id: '12', + title: 'Ab Shredder', + trainerId: 'mia', + category: 'core', + level: 'Advanced', + duration: 8, + calories: 75, + rounds: 16, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Yoga mat'], + musicVibe: 'hip-hop', + exercises: [ + { name: 'V-Ups', duration: 20 }, + { name: 'Bicycle Crunches', duration: 20 }, + { name: 'Dragon Flags', duration: 20 }, + { name: 'Flutter Kicks', duration: 20 }, + ], + }, + { + id: '13', + title: 'Core Foundations', + trainerId: 'emma', + category: 'core', + level: 'Beginner', + duration: 4, + calories: 32, + rounds: 8, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Yoga mat'], + musicVibe: 'chill', + exercises: [ + { name: 'Dead Bug', duration: 20 }, + { name: 'Bird Dog', duration: 20 }, + { name: 'Side Plank (L)', duration: 20 }, + { name: 'Side Plank (R)', duration: 20 }, + ], + }, + { + id: '14', + title: 'Oblique Inferno', + trainerId: 'jake', + category: 'core', + level: 'Intermediate', + duration: 8, + calories: 72, + rounds: 16, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Yoga mat', 'Dumbbell optional'], + musicVibe: 'rock', + exercises: [ + { name: 'Woodchoppers', duration: 20 }, + { name: 'Side Crunches', duration: 20 }, + { name: 'Windshield Wipers', duration: 20 }, + { name: 'Plank Hip Dips', duration: 20 }, + ], + }, + { + id: '15', + title: 'Core Endurance', + trainerId: 'mia', + category: 'core', + level: 'Advanced', + duration: 12, + calories: 110, + rounds: 24, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Yoga mat', 'Medicine ball'], + musicVibe: 'electronic', + exercises: [ + { name: 'Hollow Body Hold', duration: 20 }, + { name: 'Turkish Get-ups', duration: 20 }, + { name: 'Ab Rollouts', duration: 20 }, + { name: 'Pallof Press', duration: 20 }, + ], + }, + { + id: '16', + title: 'Gentle Core', + trainerId: 'sofia', + category: 'core', + level: 'Beginner', + duration: 4, + calories: 28, + rounds: 8, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Yoga mat'], + musicVibe: 'chill', + exercises: [ + { name: 'Pelvic Tilts', duration: 20 }, + { name: 'Modified Crunches', duration: 20 }, + { name: 'Supine Leg March', duration: 20 }, + { name: 'Cat-Cow', duration: 20 }, + ], + }, + { + id: '17', + title: 'Core Power', + trainerId: 'alex', + category: 'core', + level: 'Intermediate', + duration: 4, + calories: 40, + rounds: 8, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['No equipment required'], + musicVibe: 'hip-hop', + exercises: [ + { name: 'Mountain Climbers', duration: 20 }, + { name: 'Plank to Push-up', duration: 20 }, + { name: 'Reverse Crunches', duration: 20 }, + { name: 'Bear Hold', duration: 20 }, + ], + }, + { + id: '18', + title: '360 Core', + trainerId: 'jake', + category: 'core', + level: 'Advanced', + duration: 8, + calories: 78, + rounds: 16, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Yoga mat'], + musicVibe: 'electronic', + exercises: [ + { name: 'L-Sits', duration: 20 }, + { name: 'Dragon Flag Negatives', duration: 20 }, + { name: 'Hanging Knee Raises', duration: 20 }, + { name: 'Plank Walkouts', duration: 20 }, + ], + }, + { + id: '19', + title: 'Core Stability', + trainerId: 'sofia', + category: 'core', + level: 'Beginner', + duration: 4, + calories: 30, + rounds: 8, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Yoga mat'], + musicVibe: 'chill', + exercises: [ + { name: 'Forearm Plank', duration: 20 }, + { name: 'Slow Bicycle', duration: 20 }, + { name: 'Glute Bridge March', duration: 20 }, + { name: 'Prone Cobra', duration: 20 }, + ], + }, + { + id: '20', + title: 'Core Marathon', + trainerId: 'mia', + category: 'core', + level: 'Advanced', + duration: 20, + calories: 180, + rounds: 40, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Yoga mat', 'Stability ball'], + musicVibe: 'electronic', + exercises: [ + { name: 'Stir the Pot', duration: 20 }, + { name: 'Ab Wheel Rollout', duration: 20 }, + { name: 'Hanging Leg Raises', duration: 20 }, + { name: 'Dragon Flags', duration: 20 }, + ], + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // UPPER BODY (10 workouts) + // ═══════════════════════════════════════════════════════════════════════════ + { + id: '21', + title: 'Upper Body Blitz', + trainerId: 'jake', + category: 'upper-body', + level: 'Intermediate', + duration: 8, + calories: 82, + rounds: 16, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Dumbbells'], + musicVibe: 'rock', + exercises: [ + { name: 'Push-ups', duration: 20 }, + { name: 'Dumbbell Rows', duration: 20 }, + { name: 'Shoulder Press', duration: 20 }, + { name: 'Bicep Curls', duration: 20 }, + ], + }, + { + id: '22', + title: 'Arm Sculptor', + trainerId: 'jake', + category: 'upper-body', + level: 'Beginner', + duration: 4, + calories: 35, + rounds: 8, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Light dumbbells'], + musicVibe: 'pop', + exercises: [ + { name: 'Bicep Curls', duration: 20 }, + { name: 'Tricep Extensions', duration: 20 }, + { name: 'Lateral Raises', duration: 20 }, + { name: 'Front Raises', duration: 20 }, + ], + }, + { + id: '23', + title: 'Push-Up Mastery', + trainerId: 'emma', + category: 'upper-body', + level: 'Intermediate', + duration: 4, + calories: 42, + rounds: 8, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['No equipment required'], + musicVibe: 'electronic', + exercises: [ + { name: 'Wide Push-ups', duration: 20 }, + { name: 'Diamond Push-ups', duration: 20 }, + { name: 'Decline Push-ups', duration: 20 }, + { name: 'Explosive Push-ups', duration: 20 }, + ], + }, + { + id: '24', + title: 'Shoulder Shredder', + trainerId: 'jake', + category: 'upper-body', + level: 'Advanced', + duration: 8, + calories: 88, + rounds: 16, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Dumbbells', 'Resistance band'], + musicVibe: 'rock', + exercises: [ + { name: 'Arnold Press', duration: 20 }, + { name: 'Upright Rows', duration: 20 }, + { name: 'Face Pulls', duration: 20 }, + { name: 'Handstand Hold', duration: 20 }, + ], + }, + { + id: '25', + title: 'Chest & Back', + trainerId: 'alex', + category: 'upper-body', + level: 'Intermediate', + duration: 12, + calories: 120, + rounds: 24, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Dumbbells', 'Pull-up bar'], + musicVibe: 'hip-hop', + exercises: [ + { name: 'Chest Press', duration: 20 }, + { name: 'Bent Over Rows', duration: 20 }, + { name: 'Chest Flyes', duration: 20 }, + { name: 'Pull-ups', duration: 20 }, + ], + }, + { + id: '26', + title: 'Bodyweight Upper', + trainerId: 'emma', + category: 'upper-body', + level: 'Beginner', + duration: 4, + calories: 34, + rounds: 8, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['No equipment required'], + musicVibe: 'pop', + exercises: [ + { name: 'Knee Push-ups', duration: 20 }, + { name: 'Arm Circles', duration: 20 }, + { name: 'Tricep Dips (Chair)', duration: 20 }, + { name: 'Plank Shoulder Taps', duration: 20 }, + ], + }, + { + id: '27', + title: 'Strength Tabata', + trainerId: 'jake', + category: 'upper-body', + level: 'Advanced', + duration: 12, + calories: 130, + rounds: 24, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Dumbbells', 'Kettlebell'], + musicVibe: 'rock', + exercises: [ + { name: 'Clean & Jerk', duration: 20 }, + { name: 'Renegade Rows', duration: 20 }, + { name: 'Turkish Get-up', duration: 20 }, + { name: 'Overhead Press', duration: 20 }, + ], + }, + { + id: '28', + title: 'Tone & Define', + trainerId: 'sofia', + category: 'upper-body', + level: 'Beginner', + duration: 4, + calories: 32, + rounds: 8, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Light dumbbells'], + musicVibe: 'chill', + exercises: [ + { name: 'Hammer Curls', duration: 20 }, + { name: 'Overhead Tricep', duration: 20 }, + { name: 'Lateral Raises', duration: 20 }, + { name: 'Reverse Flyes', duration: 20 }, + ], + }, + { + id: '29', + title: 'Power Arms', + trainerId: 'alex', + category: 'upper-body', + level: 'Intermediate', + duration: 8, + calories: 80, + rounds: 16, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Dumbbells'], + musicVibe: 'hip-hop', + exercises: [ + { name: 'Concentration Curls', duration: 20 }, + { name: 'Skull Crushers', duration: 20 }, + { name: 'Zottman Curls', duration: 20 }, + { name: 'Dips', duration: 20 }, + ], + }, + { + id: '30', + title: 'Upper Endurance', + trainerId: 'jake', + category: 'upper-body', + level: 'Advanced', + duration: 20, + calories: 200, + rounds: 40, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Full gym setup'], + musicVibe: 'rock', + exercises: [ + { name: 'Barbell Press', duration: 20 }, + { name: 'Pull-ups', duration: 20 }, + { name: 'Dumbbell Snatch', duration: 20 }, + { name: 'Plyo Push-ups', duration: 20 }, + ], + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // LOWER BODY (10 workouts) + // ═══════════════════════════════════════════════════════════════════════════ + { + id: '31', + title: 'Lower Body Burn', + trainerId: 'emma', + category: 'lower-body', + level: 'Beginner', + duration: 4, + calories: 44, + rounds: 8, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['No equipment required'], + musicVibe: 'pop', + exercises: [ + { name: 'Squats', duration: 20 }, + { name: 'Lunges', duration: 20 }, + { name: 'Glute Bridges', duration: 20 }, + { name: 'Calf Raises', duration: 20 }, + ], + }, + { + id: '32', + title: 'Leg Day Tabata', + trainerId: 'jake', + category: 'lower-body', + level: 'Intermediate', + duration: 8, + calories: 92, + rounds: 16, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Dumbbells optional'], + musicVibe: 'hip-hop', + exercises: [ + { name: 'Jump Squats', duration: 20 }, + { name: 'Walking Lunges', duration: 20 }, + { name: 'Step-ups', duration: 20 }, + { name: 'Wall Sit', duration: 20 }, + ], + }, + { + id: '33', + title: 'Glute Activator', + trainerId: 'mia', + category: 'lower-body', + level: 'Beginner', + duration: 4, + calories: 36, + rounds: 8, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Resistance band optional'], + musicVibe: 'pop', + exercises: [ + { name: 'Glute Bridges', duration: 20 }, + { name: 'Donkey Kicks', duration: 20 }, + { name: 'Fire Hydrants', duration: 20 }, + { name: 'Clamshells', duration: 20 }, + ], + }, + { + id: '34', + title: 'Explosive Legs', + trainerId: 'alex', + category: 'lower-body', + level: 'Advanced', + duration: 8, + calories: 98, + rounds: 16, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Box or step'], + musicVibe: 'electronic', + exercises: [ + { name: 'Box Jumps', duration: 20 }, + { name: 'Pistol Squats', duration: 20 }, + { name: 'Jumping Lunges', duration: 20 }, + { name: 'Broad Jumps', duration: 20 }, + ], + }, + { + id: '35', + title: 'Squat Challenge', + trainerId: 'jake', + category: 'lower-body', + level: 'Intermediate', + duration: 4, + calories: 46, + rounds: 8, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['No equipment required'], + musicVibe: 'rock', + exercises: [ + { name: 'Goblet Squats', duration: 20 }, + { name: 'Sumo Squats', duration: 20 }, + { name: 'Pulse Squats', duration: 20 }, + { name: 'Squat Jumps', duration: 20 }, + ], + }, + { + id: '36', + title: 'Lower Power', + trainerId: 'jake', + category: 'lower-body', + level: 'Advanced', + duration: 12, + calories: 150, + rounds: 24, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Barbell', 'Dumbbells'], + musicVibe: 'rock', + exercises: [ + { name: 'Front Squats', duration: 20 }, + { name: 'Romanian Deadlifts', duration: 20 }, + { name: 'Split Squats', duration: 20 }, + { name: 'Power Cleans', duration: 20 }, + ], + }, + { + id: '37', + title: 'Knee-Friendly Legs', + trainerId: 'sofia', + category: 'lower-body', + level: 'Beginner', + duration: 4, + calories: 34, + rounds: 8, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Chair for balance'], + musicVibe: 'chill', + exercises: [ + { name: 'Glute Bridges', duration: 20 }, + { name: 'Standing Kickbacks', duration: 20 }, + { name: 'Side Leg Raises', duration: 20 }, + { name: 'Calf Raises', duration: 20 }, + ], + }, + { + id: '38', + title: 'Sprint Tabata', + trainerId: 'alex', + category: 'lower-body', + level: 'Advanced', + duration: 4, + calories: 52, + rounds: 8, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['No equipment required'], + musicVibe: 'electronic', + exercises: [ + { name: 'High Knees Sprint', duration: 20 }, + { name: 'Lateral Shuffles', duration: 20 }, + { name: 'Tuck Jumps', duration: 20 }, + { name: 'Butt Kick Sprint', duration: 20 }, + ], + }, + { + id: '39', + title: 'Legs & Glutes', + trainerId: 'mia', + category: 'lower-body', + level: 'Intermediate', + duration: 12, + calories: 125, + rounds: 24, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Resistance band', 'Dumbbells'], + musicVibe: 'pop', + exercises: [ + { name: 'Banded Squats', duration: 20 }, + { name: 'Hip Thrusts', duration: 20 }, + { name: 'Lateral Band Walks', duration: 20 }, + { name: 'Step-back Lunges', duration: 20 }, + ], + }, + { + id: '40', + title: 'Leg Day Marathon', + trainerId: 'jake', + category: 'lower-body', + level: 'Advanced', + duration: 20, + calories: 210, + rounds: 40, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Full gym setup'], + musicVibe: 'rock', + exercises: [ + { name: 'Back Squats', duration: 20 }, + { name: 'Leg Press Jumps', duration: 20 }, + { name: 'Walking Lunges', duration: 20 }, + { name: 'Box Jump Overs', duration: 20 }, + ], + }, + + // ═══════════════════════════════════════════════════════════════════════════ + // CARDIO (10 workouts) + // ═══════════════════════════════════════════════════════════════════════════ + { + id: '41', + title: 'HIIT Extreme', + trainerId: 'alex', + category: 'cardio', + level: 'Advanced', + duration: 8, + calories: 95, + rounds: 16, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['No equipment required'], + musicVibe: 'electronic', + exercises: [ + { name: 'Burpees', duration: 20 }, + { name: 'Mountain Climbers', duration: 20 }, + { name: 'Jump Squats', duration: 20 }, + { name: 'High Knees', duration: 20 }, + ], + }, + { + id: '42', + title: 'Cardio Blast', + trainerId: 'alex', + category: 'cardio', + level: 'Intermediate', + duration: 4, + calories: 48, + rounds: 8, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['No equipment required'], + musicVibe: 'electronic', + exercises: [ + { name: 'Jumping Jacks', duration: 20 }, + { name: 'Speed Skaters', duration: 20 }, + { name: 'Butt Kicks', duration: 20 }, + { name: 'Star Jumps', duration: 20 }, + ], + }, + { + id: '43', + title: 'Dance Cardio', + trainerId: 'emma', + category: 'cardio', + level: 'Beginner', + duration: 4, + calories: 40, + rounds: 8, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['No equipment required'], + musicVibe: 'pop', + exercises: [ + { name: 'Grapevine', duration: 20 }, + { name: 'Step Touch', duration: 20 }, + { name: 'Cha-Cha Slide', duration: 20 }, + { name: 'Jumping Jacks', duration: 20 }, + ], + }, + { + id: '44', + title: 'Fat Burn Express', + trainerId: 'alex', + category: 'cardio', + level: 'Intermediate', + duration: 8, + calories: 88, + rounds: 16, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Jump rope optional'], + musicVibe: 'hip-hop', + exercises: [ + { name: 'Jump Rope', duration: 20 }, + { name: 'Burpees', duration: 20 }, + { name: 'Mountain Climbers', duration: 20 }, + { name: 'Tuck Jumps', duration: 20 }, + ], + }, + { + id: '45', + title: 'Low Impact Cardio', + trainerId: 'sofia', + category: 'cardio', + level: 'Beginner', + duration: 4, + calories: 30, + rounds: 8, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['No equipment required'], + musicVibe: 'chill', + exercises: [ + { name: 'Marching in Place', duration: 20 }, + { name: 'Step Touches', duration: 20 }, + { name: 'Boxing Punches', duration: 20 }, + { name: 'Side Steps', duration: 20 }, + ], + }, + { + id: '46', + title: 'Cardio Inferno', + trainerId: 'alex', + category: 'cardio', + level: 'Advanced', + duration: 12, + calories: 155, + rounds: 24, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['No equipment required'], + musicVibe: 'electronic', + exercises: [ + { name: 'Burpee Tuck Jumps', duration: 20 }, + { name: 'Sprint in Place', duration: 20 }, + { name: 'Plyo Lunges', duration: 20 }, + { name: 'Lateral Bounds', duration: 20 }, + ], + }, + { + id: '47', + title: 'Sunrise Flow', + trainerId: 'sofia', + category: 'cardio', + level: 'Beginner', + duration: 4, + calories: 28, + rounds: 8, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['No equipment required'], + musicVibe: 'chill', + exercises: [ + { name: 'Sun Salutation', duration: 20 }, + { name: 'Light Jog', duration: 20 }, + { name: 'Arm Swings', duration: 20 }, + { name: 'Gentle Twists', duration: 20 }, + ], + }, + { + id: '48', + title: 'Power Hour', + trainerId: 'alex', + category: 'cardio', + level: 'Intermediate', + duration: 12, + calories: 130, + rounds: 24, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['No equipment required'], + musicVibe: 'hip-hop', + exercises: [ + { name: 'Burpees', duration: 20 }, + { name: 'Speed Skaters', duration: 20 }, + { name: 'High Knees', duration: 20 }, + { name: 'Plank Jacks', duration: 20 }, + ], + }, + { + id: '49', + title: 'Deep Stretch', + trainerId: 'sofia', + category: 'cardio', + level: 'Beginner', + duration: 8, + calories: 35, + rounds: 16, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Yoga mat'], + musicVibe: 'chill', + exercises: [ + { name: 'Forward Fold', duration: 20 }, + { name: 'Pigeon Pose', duration: 20 }, + { name: 'Spinal Twist', duration: 20 }, + { name: 'Butterfly Stretch', duration: 20 }, + ], + }, + { + id: '50', + title: 'Cardio Marathon', + trainerId: 'alex', + category: 'cardio', + level: 'Advanced', + duration: 20, + calories: 240, + rounds: 40, + prepTime: 10, + workTime: 20, + restTime: 10, + equipment: ['Jump rope', 'Box or step'], + musicVibe: 'electronic', + exercises: [ + { name: 'Double Unders', duration: 20 }, + { name: 'Box Jump Burpees', duration: 20 }, + { name: 'Sprint Intervals', duration: 20 }, + { name: 'Lateral Bounds', duration: 20 }, + ], + }, +] diff --git a/src/shared/stores/CLAUDE.md b/src/shared/stores/CLAUDE.md new file mode 100644 index 0000000..525750a --- /dev/null +++ b/src/shared/stores/CLAUDE.md @@ -0,0 +1,13 @@ + +# Recent Activity + + + +### Feb 20, 2026 + +| ID | Time | T | Title | Read | +|----|------|---|-------|------| +| #5177 | 11:54 AM | ✅ | Exported getWeeklyActivity from stores index | ~158 | +| #5171 | 11:53 AM | 🔄 | Refactored activityStore to remove computed helpers from state | ~256 | +| #5170 | " | 🔵 | Activity store implementation with persistence | ~220 | + \ No newline at end of file diff --git a/src/shared/stores/activityStore.ts b/src/shared/stores/activityStore.ts new file mode 100644 index 0000000..7cf156e --- /dev/null +++ b/src/shared/stores/activityStore.ts @@ -0,0 +1,109 @@ +/** + * TabataFit Activity Store + * Workout history, streak, stats — persisted via AsyncStorage + */ + +import { create } from 'zustand' +import { persist, createJSONStorage } from 'zustand/middleware' +import AsyncStorage from '@react-native-async-storage/async-storage' +import type { WorkoutResult, DayActivity } from '../types' + +interface ActivityState { + history: WorkoutResult[] + streak: { current: number; longest: number } + + // Actions + addWorkoutResult: (result: WorkoutResult) => void +} + +function getDateString(timestamp: number) { + return new Date(timestamp).toISOString().split('T')[0] +} + +function calculateStreak(history: WorkoutResult[]): { current: number; longest: number } { + if (history.length === 0) return { current: 0, longest: 0 } + + const uniqueDays = new Set(history.map(r => getDateString(r.completedAt))) + const sortedDays = Array.from(uniqueDays).sort().reverse() + + const today = getDateString(Date.now()) + const yesterday = getDateString(Date.now() - 86400000) + const hasRecentActivity = sortedDays[0] === today || sortedDays[0] === yesterday + + if (!hasRecentActivity) return { current: 0, longest: calculateLongest(sortedDays) } + + let current = 1 + for (let i = 0; i < sortedDays.length - 1; i++) { + const diff = new Date(sortedDays[i]).getTime() - new Date(sortedDays[i + 1]).getTime() + if (diff <= 86400000) { + current++ + } else { + break + } + } + + return { current, longest: Math.max(current, calculateLongest(sortedDays)) } +} + +function calculateLongest(sortedDays: string[]): number { + if (sortedDays.length === 0) return 0 + let longest = 1 + let streak = 1 + for (let i = 0; i < sortedDays.length - 1; i++) { + const diff = new Date(sortedDays[i]).getTime() - new Date(sortedDays[i + 1]).getTime() + if (diff <= 86400000) { + streak++ + longest = Math.max(longest, streak) + } else { + streak = 1 + } + } + return longest +} + +export const useActivityStore = create()( + persist( + (set) => ({ + history: [], + streak: { current: 0, longest: 0 }, + + addWorkoutResult: (result) => + set((state) => { + const newHistory = [result, ...state.history] + const newStreak = calculateStreak(newHistory) + return { + history: newHistory, + streak: newStreak, + } + }), + }), + { + name: 'tabatafit-activity', + storage: createJSONStorage(() => AsyncStorage), + } + ) +) + +// Standalone helper functions (use outside selectors) +export function getWeeklyActivity(history: WorkoutResult[]): DayActivity[] { + const days: DayActivity[] = [] + const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] + const today = new Date() + const startOfWeek = new Date(today) + startOfWeek.setDate(today.getDate() - today.getDay()) + + for (let i = 0; i < 7; i++) { + const date = new Date(startOfWeek) + date.setDate(startOfWeek.getDate() + i) + const dateStr = getDateString(date.getTime()) + const workoutsOnDay = history.filter( + r => getDateString(r.completedAt) === dateStr + ) + days.push({ + date: dayNames[i], + completed: workoutsOnDay.length > 0, + workoutCount: workoutsOnDay.length, + }) + } + return days +} diff --git a/src/shared/stores/index.ts b/src/shared/stores/index.ts new file mode 100644 index 0000000..e800ec5 --- /dev/null +++ b/src/shared/stores/index.ts @@ -0,0 +1,7 @@ +/** + * TabataFit Stores + */ + +export { useUserStore } from './userStore' +export { useActivityStore, getWeeklyActivity } from './activityStore' +export { usePlayerStore } from './playerStore' diff --git a/src/shared/stores/playerStore.ts b/src/shared/stores/playerStore.ts new file mode 100644 index 0000000..c81dfe2 --- /dev/null +++ b/src/shared/stores/playerStore.ts @@ -0,0 +1,75 @@ +/** + * TabataFit Player Store + * Current workout state — ephemeral (not persisted) + */ + +import { create } from 'zustand' +import type { Workout } from '../types' + +type TimerPhase = 'PREP' | 'WORK' | 'REST' | 'COMPLETE' + +interface PlayerState { + workout: Workout | null + phase: TimerPhase + timeRemaining: number + currentRound: number + isPaused: boolean + isRunning: boolean + calories: number + startedAt: number | null + + // Actions + loadWorkout: (workout: Workout) => void + setPhase: (phase: TimerPhase) => void + setTimeRemaining: (time: number) => void + setCurrentRound: (round: number) => void + setPaused: (paused: boolean) => void + setRunning: (running: boolean) => void + addCalories: (amount: number) => void + reset: () => void +} + +export const usePlayerStore = create((set) => ({ + workout: null, + phase: 'PREP', + timeRemaining: 10, + currentRound: 1, + isPaused: false, + isRunning: false, + calories: 0, + startedAt: null, + + loadWorkout: (workout) => + set({ + workout, + phase: 'PREP', + timeRemaining: workout.prepTime, + currentRound: 1, + isPaused: false, + isRunning: false, + calories: 0, + startedAt: null, + }), + + setPhase: (phase) => set({ phase }), + setTimeRemaining: (time) => set({ timeRemaining: time }), + setCurrentRound: (round) => set({ currentRound: round }), + setPaused: (paused) => set({ isPaused: paused }), + setRunning: (running) => + set((state) => ({ + isRunning: running, + startedAt: running && !state.startedAt ? Date.now() : state.startedAt, + })), + addCalories: (amount) => set((state) => ({ calories: state.calories + amount })), + reset: () => + set({ + workout: null, + phase: 'PREP', + timeRemaining: 10, + currentRound: 1, + isPaused: false, + isRunning: false, + calories: 0, + startedAt: null, + }), +})) diff --git a/src/shared/stores/userStore.ts b/src/shared/stores/userStore.ts new file mode 100644 index 0000000..090d5c0 --- /dev/null +++ b/src/shared/stores/userStore.ts @@ -0,0 +1,57 @@ +/** + * TabataFit User Store + * Profile, settings, subscription — persisted via AsyncStorage + */ + +import { create } from 'zustand' +import { persist, createJSONStorage } from 'zustand/middleware' +import AsyncStorage from '@react-native-async-storage/async-storage' +import type { UserProfile, UserSettings, SubscriptionPlan } from '../types' + +interface UserState { + profile: UserProfile + settings: UserSettings + // Actions + updateProfile: (updates: Partial) => void + updateSettings: (updates: Partial) => void + setSubscription: (plan: SubscriptionPlan) => void +} + +export const useUserStore = create()( + persist( + (set) => ({ + profile: { + name: 'Alex', + email: 'alex@example.com', + joinDate: 'January 2026', + subscription: 'premium-monthly', + }, + settings: { + haptics: true, + soundEffects: true, + voiceCoaching: true, + reminders: false, + reminderTime: '09:00', + }, + + updateProfile: (updates) => + set((state) => ({ + profile: { ...state.profile, ...updates }, + })), + + updateSettings: (updates) => + set((state) => ({ + settings: { ...state.settings, ...updates }, + })), + + setSubscription: (plan) => + set((state) => ({ + profile: { ...state.profile, subscription: plan }, + })), + }), + { + name: 'tabatafit-user', + storage: createJSONStorage(() => AsyncStorage), + } + ) +) diff --git a/src/shared/types/activity.ts b/src/shared/types/activity.ts new file mode 100644 index 0000000..1187ee5 --- /dev/null +++ b/src/shared/types/activity.ts @@ -0,0 +1,32 @@ +/** + * TabataFit Activity Types + */ + +export interface WorkoutResult { + id: string + workoutId: string + completedAt: number + calories: number + durationMinutes: number + rounds: number + /** 0..1 */ + completionRate: number +} + +export interface DayActivity { + date: string + completed: boolean + workoutCount: number +} + +export interface WeeklyStats { + days: DayActivity[] + totalWorkouts: number + totalMinutes: number + totalCalories: number +} + +export interface Streak { + current: number + longest: number +} diff --git a/src/shared/types/index.ts b/src/shared/types/index.ts new file mode 100644 index 0000000..9be5ab3 --- /dev/null +++ b/src/shared/types/index.ts @@ -0,0 +1,8 @@ +/** + * TabataFit Shared Types + */ + +export * from './workout' +export * from './trainer' +export * from './user' +export * from './activity' diff --git a/src/shared/types/trainer.ts b/src/shared/types/trainer.ts new file mode 100644 index 0000000..37d9e22 --- /dev/null +++ b/src/shared/types/trainer.ts @@ -0,0 +1,12 @@ +/** + * TabataFit Trainer Types + */ + +export interface Trainer { + id: string + name: string + specialty: string + color: string + avatarUrl?: string + workoutCount: number +} diff --git a/src/shared/types/user.ts b/src/shared/types/user.ts new file mode 100644 index 0000000..1ae8f48 --- /dev/null +++ b/src/shared/types/user.ts @@ -0,0 +1,29 @@ +/** + * TabataFit User Types + */ + +export type SubscriptionPlan = 'free' | 'premium-monthly' | 'premium-yearly' + +export interface UserSettings { + haptics: boolean + soundEffects: boolean + voiceCoaching: boolean + reminders: boolean + reminderTime: string +} + +export interface UserProfile { + name: string + email: string + joinDate: string + subscription: SubscriptionPlan +} + +export interface Achievement { + id: string + title: string + description: string + icon: string + requirement: number + type: 'workouts' | 'streak' | 'minutes' | 'calories' +} diff --git a/src/shared/types/workout.ts b/src/shared/types/workout.ts new file mode 100644 index 0000000..c5132f1 --- /dev/null +++ b/src/shared/types/workout.ts @@ -0,0 +1,62 @@ +/** + * TabataFit Workout Types + */ + +export type WorkoutCategory = 'full-body' | 'core' | 'upper-body' | 'lower-body' | 'cardio' + +export type WorkoutLevel = 'Beginner' | 'Intermediate' | 'Advanced' + +export type WorkoutDuration = 4 | 8 | 12 | 20 + +export type MusicVibe = 'electronic' | 'hip-hop' | 'pop' | 'rock' | 'chill' + +export interface Exercise { + name: string + /** Work duration in seconds */ + duration: number +} + +export interface Workout { + id: string + title: string + trainerId: string + category: WorkoutCategory + level: WorkoutLevel + /** Duration in minutes */ + duration: WorkoutDuration + /** Estimated calories burned */ + calories: number + exercises: Exercise[] + /** Total rounds (work+rest cycles) */ + rounds: number + /** Prep time in seconds */ + prepTime: number + /** Work interval in seconds */ + workTime: number + /** Rest interval in seconds */ + restTime: number + equipment: string[] + musicVibe: MusicVibe + thumbnailUrl?: string + videoUrl?: string + isFeatured?: boolean +} + +export interface Collection { + id: string + title: string + description: string + icon: string + workoutIds: string[] + gradient?: [string, string] +} + +export interface Program { + id: string + title: string + description: string + weeks: number + workoutsPerWeek: number + level: WorkoutLevel + workoutIds: string[] +}