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 <noreply@anthropic.com>
This commit is contained in:
72
src/shared/data/achievements.ts
Normal file
72
src/shared/data/achievements.ts
Normal file
@@ -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',
|
||||
},
|
||||
]
|
||||
83
src/shared/data/collections.ts
Normal file
83
src/shared/data/collections.ts
Normal file
@@ -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'
|
||||
117
src/shared/data/index.ts
Normal file
117
src/shared/data/index.ts
Normal file
@@ -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<string, string> = {
|
||||
'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<string, string> = {
|
||||
'morning-energizer': '#FFD60A',
|
||||
'no-equipment': '#30D158',
|
||||
'7-day-burn': '#FF3B30',
|
||||
'quick-intense': '#FF3B30',
|
||||
'core-focus': '#5AC8FA',
|
||||
'leg-day': '#BF5AF2',
|
||||
}
|
||||
44
src/shared/data/trainers.ts
Normal file
44
src/shared/data/trainers.ts
Normal file
@@ -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,
|
||||
},
|
||||
]
|
||||
1079
src/shared/data/workouts.ts
Normal file
1079
src/shared/data/workouts.ts
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user