From 3aa2c9a181a0780f1e0fdad6f64f5a3aa8cb30d4 Mon Sep 17 00:00:00 2001 From: bender Date: Wed, 11 Mar 2026 19:32:10 +0000 Subject: [PATCH 1/7] Add src/app/login/page.tsx --- src/app/login/page.tsx | 235 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 src/app/login/page.tsx diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx new file mode 100644 index 0000000..848bacc --- /dev/null +++ b/src/app/login/page.tsx @@ -0,0 +1,235 @@ +"use client"; + +import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider"; +import NavbarStyleCentered from '@/components/navbar/NavbarStyleCentered/NavbarStyleCentered'; +import { useState } from 'react'; +import { Mail, Lock, Eye, EyeOff, ArrowRight } from 'lucide-react'; + +export default function LoginPage() { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [showPassword, setShowPassword] = useState(false); + const [errors, setErrors] = useState<{ email?: string; password?: string }>({}); + const [isSubmitting, setIsSubmitting] = useState(false); + + const validateForm = () => { + const newErrors: { email?: string; password?: string } = {}; + + if (!email) { + newErrors.email = 'Email is required'; + } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { + newErrors.email = 'Please enter a valid email'; + } + + if (!password) { + newErrors.password = 'Password is required'; + } else if (password.length < 6) { + newErrors.password = 'Password must be at least 6 characters'; + } + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!validateForm()) { + return; + } + + setIsSubmitting(true); + try { + // Simulated API call + await new Promise(resolve => setTimeout(resolve, 1000)); + console.log('Login attempt:', { email, password }); + // In a real app, you would redirect to dashboard or handle authentication + } finally { + setIsSubmitting(false); + } + }; + + return ( + + + +
+
+
+ {/* Header */} +
+

+ Welcome Back +

+

+ Sign in to your FitFlow Pro account +

+
+ + {/* Form */} +
+ {/* Email Field */} +
+ +
+ + setEmail(e.target.value)} + placeholder="you@example.com" + className="w-full pl-10 pr-4 py-2.5 rounded-lg border transition-all" + style={{ + backgroundColor: 'var(--color-background)', + borderColor: errors.email ? '#ef4444' : 'var(--color-primary-cta)', + color: 'var(--color-foreground)', + }} + /> +
+ {errors.email && ( +

+ {errors.email} +

+ )} +
+ + {/* Password Field */} +
+ +
+ + setPassword(e.target.value)} + placeholder="••••••••" + className="w-full pl-10 pr-10 py-2.5 rounded-lg border transition-all" + style={{ + backgroundColor: 'var(--color-background)', + borderColor: errors.password ? '#ef4444' : 'var(--color-primary-cta)', + color: 'var(--color-foreground)', + }} + /> + +
+ {errors.password && ( +

+ {errors.password} +

+ )} +
+ + {/* Remember & Forgot */} +
+ + + Forgot password? + +
+ + {/* Submit Button */} + +
+ + {/* Divider */} +
+
+ OR +
+
+ + {/* Social Login */} +
+ + +
+ + {/* Sign Up Link */} +

+ Don't have an account?{' '} + + Sign up + +

+
+
+
+ + ); +} \ No newline at end of file From aad94e432434e15d72b8adf4bb64cdf43c3f46a8 Mon Sep 17 00:00:00 2001 From: bender Date: Wed, 11 Mar 2026 19:32:10 +0000 Subject: [PATCH 2/7] Update src/app/page.tsx --- src/app/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index df0ae68..2470f00 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -380,4 +380,4 @@ export default function LandingPage() {
); -} \ No newline at end of file +} From f7e85fec9989f75ab17d9f0be7b023eea9739938 Mon Sep 17 00:00:00 2001 From: bender Date: Wed, 11 Mar 2026 19:32:10 +0000 Subject: [PATCH 3/7] Add src/app/signup/page.tsx --- src/app/signup/page.tsx | 404 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 404 insertions(+) create mode 100644 src/app/signup/page.tsx diff --git a/src/app/signup/page.tsx b/src/app/signup/page.tsx new file mode 100644 index 0000000..70df99a --- /dev/null +++ b/src/app/signup/page.tsx @@ -0,0 +1,404 @@ +"use client"; + +import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider"; +import NavbarStyleCentered from '@/components/navbar/NavbarStyleCentered/NavbarStyleCentered'; +import { useState } from 'react'; +import { Mail, Lock, User, Eye, EyeOff, ArrowRight, CheckCircle } from 'lucide-react'; + +export default function SignupPage() { + const [formData, setFormData] = useState({ + fullName: '', + email: '', + password: '', + confirmPassword: '' + }); + const [showPassword, setShowPassword] = useState(false); + const [showConfirmPassword, setShowConfirmPassword] = useState(false); + const [errors, setErrors] = useState>({}); + const [isSubmitting, setIsSubmitting] = useState(false); + const [passwordStrength, setPasswordStrength] = useState(0); + const [agreedToTerms, setAgreedToTerms] = useState(false); + + const calculatePasswordStrength = (pwd: string) => { + let strength = 0; + if (pwd.length >= 8) strength++; + if (pwd.match(/[a-z]/) && pwd.match(/[A-Z]/)) strength++; + if (pwd.match(/[0-9]/)) strength++; + if (pwd.match(/[^a-zA-Z0-9]/)) strength++; + setPasswordStrength(strength); + }; + + const validateForm = () => { + const newErrors: Record = {}; + + if (!formData.fullName.trim()) { + newErrors.fullName = 'Full name is required'; + } + + if (!formData.email) { + newErrors.email = 'Email is required'; + } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) { + newErrors.email = 'Please enter a valid email'; + } + + if (!formData.password) { + newErrors.password = 'Password is required'; + } else if (formData.password.length < 8) { + newErrors.password = 'Password must be at least 8 characters'; + } else if (passwordStrength < 2) { + newErrors.password = 'Password is too weak'; + } + + if (!formData.confirmPassword) { + newErrors.confirmPassword = 'Please confirm your password'; + } else if (formData.password !== formData.confirmPassword) { + newErrors.confirmPassword = 'Passwords do not match'; + } + + if (!agreedToTerms) { + newErrors.terms = 'You must agree to the terms'; + } + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + + const handleChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setFormData(prev => ({ ...prev, [name]: value })); + + if (name === 'password') { + calculatePasswordStrength(value); + } + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!validateForm()) { + return; + } + + setIsSubmitting(true); + try { + // Simulated API call + await new Promise(resolve => setTimeout(resolve, 1000)); + console.log('Signup attempt:', { ...formData, confirmPassword: undefined }); + // In a real app, you would create account and redirect + } finally { + setIsSubmitting(false); + } + }; + + const getPasswordStrengthColor = () => { + if (passwordStrength <= 1) return '#ef4444'; + if (passwordStrength <= 2) return '#eab308'; + if (passwordStrength <= 3) return '#f97316'; + return '#22c55e'; + }; + + const getPasswordStrengthText = () => { + if (!formData.password) return ''; + if (passwordStrength <= 1) return 'Weak'; + if (passwordStrength <= 2) return 'Fair'; + if (passwordStrength <= 3) return 'Good'; + return 'Strong'; + }; + + return ( + + + +
+
+
+ {/* Header */} +
+

+ Create Account +

+

+ Join 150k+ athletes transforming their bodies +

+
+ + {/* Form */} +
+ {/* Full Name Field */} +
+ +
+ + +
+ {errors.fullName && ( +

+ {errors.fullName} +

+ )} +
+ + {/* Email Field */} +
+ +
+ + +
+ {errors.email && ( +

+ {errors.email} +

+ )} +
+ + {/* Password Field */} +
+ +
+ + + +
+ + {/* Password Strength Indicator */} + {formData.password && ( +
+
+
+
+ + {getPasswordStrengthText()} + +
+ )} + + {errors.password && ( +

+ {errors.password} +

+ )} +
+ + {/* Confirm Password Field */} +
+ +
+ + + +
+ {errors.confirmPassword && ( +

+ {errors.confirmPassword} +

+ )} +
+ + {/* Terms Agreement */} +
+
+ + ); +} \ No newline at end of file From 3fba97bf227d97b3a45c5b33387ac5da3a418684 Mon Sep 17 00:00:00 2001 From: bender Date: Wed, 11 Mar 2026 19:32:11 +0000 Subject: [PATCH 4/7] Add src/components/workout/WorkoutMetricsDisplay.tsx --- .../workout/WorkoutMetricsDisplay.tsx | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 src/components/workout/WorkoutMetricsDisplay.tsx diff --git a/src/components/workout/WorkoutMetricsDisplay.tsx b/src/components/workout/WorkoutMetricsDisplay.tsx new file mode 100644 index 0000000..3cfcda8 --- /dev/null +++ b/src/components/workout/WorkoutMetricsDisplay.tsx @@ -0,0 +1,86 @@ +'use client'; + +import { useWorkoutData } from '@/hooks/useWorkoutData'; +import { UserMetrics } from '@/lib/storage/workoutStorage'; + +interface WorkoutMetricsDisplayProps { + className?: string; + showRefresh?: boolean; + onRefresh?: () => void; +} + +export const WorkoutMetricsDisplay: React.FC = ({ + className = '', + showRefresh = true, + onRefresh +}) => { + const { metrics, refreshMetrics, isLoading } = useWorkoutData(); + + const handleRefresh = () => { + refreshMetrics(); + onRefresh?.(); + }; + + if (isLoading || !metrics) { + return
Loading metrics...
; + } + + const formatMetric = (value: number, unit: string) => { + return `${value.toLocaleString()}${unit}`; + }; + + return ( +
+
+
+

Total Steps

+

{formatMetric(metrics.totalSteps, ' steps')}

+
+
+

Total Distance

+

{formatMetric(metrics.totalDistance, ' km')}

+
+
+

Total Calories

+

{formatMetric(metrics.totalCalories, ' kcal')}

+
+
+

Total Volume

+

{formatMetric(metrics.totalVolume, ' kg')}

+
+
+

Workout Streak

+

{metrics.workoutStreak} days

+
+ {metrics.lastWorkoutDate && ( +
+

Last Workout

+

{new Date(metrics.lastWorkoutDate).toLocaleDateString()}

+
+ )} +
+ + {Object.keys(metrics.personalRecords).length > 0 && ( +
+

Personal Records

+
    + {Object.entries(metrics.personalRecords).map(([exercise, weight]) => ( +
  • + {exercise} + {weight} kg +
  • + ))} +
+
+ )} + + {showRefresh && ( + + )} +
+ ); +}; + +export default WorkoutMetricsDisplay; From 8924fed85c39e9f0c066729a3ceea43a32ebd8ae Mon Sep 17 00:00:00 2001 From: bender Date: Wed, 11 Mar 2026 19:32:11 +0000 Subject: [PATCH 5/7] Add src/components/workout/WorkoutSaver.tsx --- src/components/workout/WorkoutSaver.tsx | 129 ++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 src/components/workout/WorkoutSaver.tsx diff --git a/src/components/workout/WorkoutSaver.tsx b/src/components/workout/WorkoutSaver.tsx new file mode 100644 index 0000000..9a90c3e --- /dev/null +++ b/src/components/workout/WorkoutSaver.tsx @@ -0,0 +1,129 @@ +'use client'; + +import { useState } from 'react'; +import { useWorkoutData } from '@/hooks/useWorkoutData'; +import { WorkoutSession, ExerciseLog } from '@/lib/storage/workoutStorage'; + +interface WorkoutSaverProps { + onSave?: (session: WorkoutSession) => void; + onError?: (error: string) => void; +} + +export const WorkoutSaver: React.FC = ({ onSave, onError }) => { + const { addSession } = useWorkoutData(); + const [isSaving, setIsSaving] = useState(false); + + const saveCardioWorkout = async (data: { + distance: number; + duration: number; + pace: string; + calories: number; + steps?: number; + }) => { + setIsSaving(true); + try { + const session: Omit = { + date: new Date().toISOString(), + type: 'cardio', + distance: data.distance, + duration: data.duration, + pace: data.pace, + calories: data.calories, + steps: data.steps + }; + const success = addSession(session); + if (success && onSave) { + onSave(session as WorkoutSession); + } else if (!success && onError) { + onError('Failed to save cardio workout'); + } + } catch (error) { + if (onError) onError('Error saving cardio workout'); + console.error(error); + } finally { + setIsSaving(false); + } + }; + + const saveTrainingWorkout = async (data: { + exercises: ExerciseLog[]; + duration: number; + calories?: number; + }) => { + setIsSaving(true); + try { + const session: Omit = { + date: new Date().toISOString(), + type: 'training', + exercises: data.exercises, + duration: data.duration, + calories: data.calories + }; + const success = addSession(session); + if (success && onSave) { + onSave(session as WorkoutSession); + } else if (!success && onError) { + onError('Failed to save training workout'); + } + } catch (error) { + if (onError) onError('Error saving training workout'); + console.error(error); + } finally { + setIsSaving(false); + } + }; + + const saveNutritionLog = async (data: { + meals: Array<{ + name: string; + calories: number; + protein: number; + carbs: number; + fats: number; + }>; + }) => { + setIsSaving(true); + try { + const meals = data.meals.map((meal, index) => ({ + id: `meal-${index}-${Date.now()}`, + name: meal.name, + calories: meal.calories, + protein: meal.protein, + carbs: meal.carbs, + fats: meal.fats, + timestamp: new Date().toISOString() + })); + + const totalCalories = data.meals.reduce((sum, meal) => sum + meal.calories, 0); + const totalProtein = data.meals.reduce((sum, meal) => sum + meal.protein, 0); + + const session: Omit = { + date: new Date().toISOString(), + type: 'nutrition', + meals, + calories: totalCalories, + notes: `Total Protein: ${totalProtein}g` + }; + const success = addSession(session); + if (success && onSave) { + onSave(session as WorkoutSession); + } else if (!success && onError) { + onError('Failed to save nutrition log'); + } + } catch (error) { + if (onError) onError('Error saving nutrition log'); + console.error(error); + } finally { + setIsSaving(false); + } + }; + + return { + saveCardioWorkout, + saveTrainingWorkout, + saveNutritionLog, + isSaving + }; +}; + +export default WorkoutSaver; From 4ba888b3a1460b87b0cc29f2f9803afa7ae8d38f Mon Sep 17 00:00:00 2001 From: bender Date: Wed, 11 Mar 2026 19:32:12 +0000 Subject: [PATCH 6/7] Add src/hooks/useWorkoutData.ts --- src/hooks/useWorkoutData.ts | 162 ++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 src/hooks/useWorkoutData.ts diff --git a/src/hooks/useWorkoutData.ts b/src/hooks/useWorkoutData.ts new file mode 100644 index 0000000..d95824b --- /dev/null +++ b/src/hooks/useWorkoutData.ts @@ -0,0 +1,162 @@ +'use client'; + +import { useState, useCallback, useEffect } from 'react'; +import { + WorkoutSession, + UserMetrics, + saveWorkoutSession, + getWorkoutSessions, + updateWorkoutSession, + deleteWorkoutSession, + getWorkoutsByType, + getWorkoutsByDateRange, + saveUserMetrics, + getUserMetrics, + updateUserMetrics, + calculateMetricsFromSessions +} from '@/lib/storage/workoutStorage'; + +export const useWorkoutData = () => { + const [sessions, setSessions] = useState([]); + const [metrics, setMetrics] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + // Load initial data + useEffect(() => { + try { + setIsLoading(true); + const loadedSessions = getWorkoutSessions(); + const loadedMetrics = getUserMetrics(); + setSessions(loadedSessions); + setMetrics(loadedMetrics); + setError(null); + } catch (err) { + setError('Failed to load workout data'); + console.error(err); + } finally { + setIsLoading(false); + } + }, []); + + const addSession = useCallback((session: Omit) => { + try { + const newSession: WorkoutSession = { + ...session, + id: Date.now().toString() + }; + const success = saveWorkoutSession(newSession); + if (success) { + setSessions(prev => [...prev, newSession]); + // Recalculate metrics + const updatedMetrics = calculateMetricsFromSessions(); + setMetrics(updatedMetrics); + saveUserMetrics(updatedMetrics); + } + return success; + } catch (err) { + setError('Failed to add session'); + console.error(err); + return false; + } + }, []); + + const updateSession = useCallback((id: string, updates: Partial) => { + try { + const success = updateWorkoutSession(id, updates); + if (success) { + setSessions(prev => + prev.map(s => s.id === id ? { ...s, ...updates } : s) + ); + // Recalculate metrics + const updatedMetrics = calculateMetricsFromSessions(); + setMetrics(updatedMetrics); + saveUserMetrics(updatedMetrics); + } + return success; + } catch (err) { + setError('Failed to update session'); + console.error(err); + return false; + } + }, []); + + const removeSession = useCallback((id: string) => { + try { + const success = deleteWorkoutSession(id); + if (success) { + setSessions(prev => prev.filter(s => s.id !== id)); + // Recalculate metrics + const updatedMetrics = calculateMetricsFromSessions(); + setMetrics(updatedMetrics); + saveUserMetrics(updatedMetrics); + } + return success; + } catch (err) { + setError('Failed to delete session'); + console.error(err); + return false; + } + }, []); + + const getSessionsByType = useCallback((type: 'cardio' | 'training' | 'nutrition') => { + try { + return getWorkoutsByType(type); + } catch (err) { + setError('Failed to filter sessions'); + console.error(err); + return []; + } + }, []); + + const getSessionsByDate = useCallback((startDate: string, endDate: string) => { + try { + return getWorkoutsByDateRange(startDate, endDate); + } catch (err) { + setError('Failed to filter sessions by date'); + console.error(err); + return []; + } + }, []); + + const updateMetrics = useCallback((updates: Partial) => { + try { + const success = updateUserMetrics(updates); + if (success) { + setMetrics(prev => prev ? { ...prev, ...updates } : null); + } + return success; + } catch (err) { + setError('Failed to update metrics'); + console.error(err); + return false; + } + }, []); + + const refreshMetrics = useCallback(() => { + try { + const recalculated = calculateMetricsFromSessions(); + setMetrics(recalculated); + saveUserMetrics(recalculated); + return recalculated; + } catch (err) { + setError('Failed to refresh metrics'); + console.error(err); + return null; + } + }, []); + + return { + sessions, + metrics, + isLoading, + error, + addSession, + updateSession, + removeSession, + getSessionsByType, + getSessionsByDate, + updateMetrics, + refreshMetrics + }; +}; From 8f7f54d74f85f40584a978457c2a57205096e0df Mon Sep 17 00:00:00 2001 From: bender Date: Wed, 11 Mar 2026 19:32:12 +0000 Subject: [PATCH 7/7] Add src/lib/storage/workoutStorage.ts --- src/lib/storage/workoutStorage.ts | 285 ++++++++++++++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 src/lib/storage/workoutStorage.ts diff --git a/src/lib/storage/workoutStorage.ts b/src/lib/storage/workoutStorage.ts new file mode 100644 index 0000000..4286eee --- /dev/null +++ b/src/lib/storage/workoutStorage.ts @@ -0,0 +1,285 @@ +// User data persistence layer for workouts and metrics + +export interface WorkoutSession { + id: string; + date: string; + type: 'cardio' | 'training' | 'nutrition'; + duration?: number; + distance?: number; + calories?: number; + pace?: string; + steps?: number; + exercises?: ExerciseLog[]; + meals?: MealLog[]; + notes?: string; +} + +export interface ExerciseLog { + id: string; + name: string; + muscleGroup: string; + sets: SetLog[]; + totalVolume?: number; +} + +export interface SetLog { + reps: number; + weight: number; + restTime?: number; +} + +export interface MealLog { + id: string; + name: string; + calories: number; + protein: number; + carbs: number; + fats: number; + timestamp: string; +} + +export interface UserMetrics { + totalSteps: number; + totalDistance: number; + totalCalories: number; + totalVolume: number; + workoutStreak: number; + lastWorkoutDate?: string; + personalRecords: Record; +} + +const STORAGE_KEY = 'fitflow_workouts'; +const METRICS_KEY = 'fitflow_metrics'; + +// Workout Session Management +export const saveWorkoutSession = (session: WorkoutSession): boolean => { + try { + const existing = getWorkoutSessions(); + const updated = [...existing, { ...session, id: session.id || Date.now().toString() }]; + localStorage.setItem(STORAGE_KEY, JSON.stringify(updated)); + return true; + } catch (error) { + console.error('Error saving workout session:', error); + return false; + } +}; + +export const getWorkoutSessions = (): WorkoutSession[] => { + try { + const data = localStorage.getItem(STORAGE_KEY); + return data ? JSON.parse(data) : []; + } catch (error) { + console.error('Error retrieving workout sessions:', error); + return []; + } +}; + +export const getWorkoutById = (id: string): WorkoutSession | null => { + try { + const sessions = getWorkoutSessions(); + return sessions.find(s => s.id === id) || null; + } catch (error) { + console.error('Error retrieving workout by id:', error); + return null; + } +}; + +export const updateWorkoutSession = (id: string, updates: Partial): boolean => { + try { + const sessions = getWorkoutSessions(); + const index = sessions.findIndex(s => s.id === id); + if (index === -1) return false; + sessions[index] = { ...sessions[index], ...updates, id }; + localStorage.setItem(STORAGE_KEY, JSON.stringify(sessions)); + return true; + } catch (error) { + console.error('Error updating workout session:', error); + return false; + } +}; + +export const deleteWorkoutSession = (id: string): boolean => { + try { + const sessions = getWorkoutSessions(); + const filtered = sessions.filter(s => s.id !== id); + localStorage.setItem(STORAGE_KEY, JSON.stringify(filtered)); + return true; + } catch (error) { + console.error('Error deleting workout session:', error); + return false; + } +}; + +export const getWorkoutsByType = (type: 'cardio' | 'training' | 'nutrition'): WorkoutSession[] => { + try { + const sessions = getWorkoutSessions(); + return sessions.filter(s => s.type === type); + } catch (error) { + console.error('Error filtering workouts by type:', error); + return []; + } +}; + +export const getWorkoutsByDateRange = (startDate: string, endDate: string): WorkoutSession[] => { + try { + const sessions = getWorkoutSessions(); + return sessions.filter(s => { + const sessionDate = new Date(s.date); + return sessionDate >= new Date(startDate) && sessionDate <= new Date(endDate); + }); + } catch (error) { + console.error('Error filtering workouts by date range:', error); + return []; + } +}; + +// Metrics Management +export const saveUserMetrics = (metrics: UserMetrics): boolean => { + try { + localStorage.setItem(METRICS_KEY, JSON.stringify(metrics)); + return true; + } catch (error) { + console.error('Error saving user metrics:', error); + return false; + } +}; + +export const getUserMetrics = (): UserMetrics => { + try { + const data = localStorage.getItem(METRICS_KEY); + return data ? JSON.parse(data) : getDefaultMetrics(); + } catch (error) { + console.error('Error retrieving user metrics:', error); + return getDefaultMetrics(); + } +}; + +export const updateUserMetrics = (updates: Partial): boolean => { + try { + const current = getUserMetrics(); + const updated = { ...current, ...updates }; + return saveUserMetrics(updated); + } catch (error) { + console.error('Error updating user metrics:', error); + return false; + } +}; + +export const calculateMetricsFromSessions = (): UserMetrics => { + try { + const sessions = getWorkoutSessions(); + let totalSteps = 0; + let totalDistance = 0; + let totalCalories = 0; + let totalVolume = 0; + const personalRecords: Record = {}; + + sessions.forEach(session => { + if (session.steps) totalSteps += session.steps; + if (session.distance) totalDistance += session.distance; + if (session.calories) totalCalories += session.calories; + if (session.exercises) { + session.exercises.forEach(ex => { + ex.sets.forEach(set => { + totalVolume += set.weight * set.reps; + const key = ex.name; + if (!personalRecords[key] || set.weight > personalRecords[key]) { + personalRecords[key] = set.weight; + } + }); + }); + } + }); + + const metrics: UserMetrics = { + totalSteps, + totalDistance, + totalCalories, + totalVolume, + workoutStreak: calculateWorkoutStreak(sessions), + lastWorkoutDate: sessions.length > 0 ? sessions[sessions.length - 1].date : undefined, + personalRecords + }; + + return metrics; + } catch (error) { + console.error('Error calculating metrics:', error); + return getDefaultMetrics(); + } +}; + +export const calculateWorkoutStreak = (sessions: WorkoutSession[]): number => { + if (sessions.length === 0) return 0; + + const sortedSessions = [...sessions].sort((a, b) => + new Date(b.date).getTime() - new Date(a.date).getTime() + ); + + let streak = 0; + let currentDate = new Date(); + currentDate.setHours(0, 0, 0, 0); + + for (const session of sortedSessions) { + const sessionDate = new Date(session.date); + sessionDate.setHours(0, 0, 0, 0); + + const dayDiff = Math.floor((currentDate.getTime() - sessionDate.getTime()) / (1000 * 60 * 60 * 24)); + + if (dayDiff === streak) { + streak++; + } else { + break; + } + } + + return streak; +}; + +const getDefaultMetrics = (): UserMetrics => ({ + totalSteps: 0, + totalDistance: 0, + totalCalories: 0, + totalVolume: 0, + workoutStreak: 0, + personalRecords: {} +}); + +// Bulk operations +export const exportWorkoutData = (): string => { + try { + const sessions = getWorkoutSessions(); + const metrics = getUserMetrics(); + const data = { sessions, metrics, exportDate: new Date().toISOString() }; + return JSON.stringify(data, null, 2); + } catch (error) { + console.error('Error exporting data:', error); + return ''; + } +}; + +export const importWorkoutData = (jsonData: string): boolean => { + try { + const data = JSON.parse(jsonData); + if (data.sessions) { + localStorage.setItem(STORAGE_KEY, JSON.stringify(data.sessions)); + } + if (data.metrics) { + localStorage.setItem(METRICS_KEY, JSON.stringify(data.metrics)); + } + return true; + } catch (error) { + console.error('Error importing data:', error); + return false; + } +}; + +export const clearAllData = (): boolean => { + try { + localStorage.removeItem(STORAGE_KEY); + localStorage.removeItem(METRICS_KEY); + return true; + } catch (error) { + console.error('Error clearing data:', error); + return false; + } +};