From 981208ab7be51ca360d80e8e7e146a23631d8b19 Mon Sep 17 00:00:00 2001 From: echo Date: Thu, 12 Mar 2026 15:32:52 +0100 Subject: [PATCH] redesign checkpoint --- apps/admin/data/fitai.db | Bin 172032 -> 172032 bytes apps/mobile/src/app/(tabs)/attendance.tsx | 43 +- apps/mobile/src/app/(tabs)/goals.tsx | 35 +- apps/mobile/src/app/(tabs)/index.tsx | 436 ++++++++++++++---- .../mobile/src/app/(tabs)/recommendations.tsx | 36 +- .../src/components/GoalProgressCard.tsx | 331 +++++++------ 6 files changed, 583 insertions(+), 298 deletions(-) diff --git a/apps/admin/data/fitai.db b/apps/admin/data/fitai.db index 25a5e7b590c9598546fa19c75bc1ca59ed022794..d0afb89685d1167da2cb262f26e626b34eb4624a 100644 GIT binary patch delta 128 zcmZoTz}0YoYl1Xm+C&*=#->noNsdKy=I|CHfCX8 zUj}u421Z6f78VwH#>r*(;v_RSDkmrB=N9ComZYYn65L#P|9CwUC(HCZ g`HU*t - Attendance + 📍 Attendance - Track your gym visits + {activeCheckIn + ? "You're crushing it today! 💪" + : history.length === 0 + ? "Ready to start your fitness journey? 🚀" + : "Track your gym visits and build streaks! 🔥"} {/* Check In/Out Section */} {activeCheckIn ? ( - + - Currently Checked In + ✅ Currently Checked In ) : ( - - + + {/* Recent History */} - + {history.length === 0 ? ( - + - - - + 📍 - Your check-in history will appear here + Check in to start building your streak! 🔥 @@ -232,7 +226,11 @@ export default function AttendanceScreen() { : null; return ( - + - Fitness Goals + 🎯 Fitness Goals - Track your fitness journey progress + {activeGoals.length === 0 + ? "Ready to crush some goals? 💪" + : activeGoals.length === 1 + ? "You're on a mission! Keep it up! 🚀" + : `${activeGoals.length} goals in progress. Legend! ⭐`} 0 && ( - + {activeGoals.length} @@ -189,11 +193,11 @@ export default function GoalsScreen() { { color: colors.textTertiary, marginTop: 4 }, ]} > - Active + 🎯 Active - + {completedGoals.length} @@ -203,11 +207,11 @@ export default function GoalsScreen() { { color: colors.textTertiary, marginTop: 4 }, ]} > - Completed + {completedGoals.length >= 5 ? "🏆" : "✅"} Completed - + {avgProgress}% @@ -217,7 +221,7 @@ export default function GoalsScreen() { { color: colors.textTertiary, marginTop: 4 }, ]} > - Avg Progress + {avgProgress >= 75 ? "🔥" : "📊"} Progress @@ -245,7 +249,7 @@ export default function GoalsScreen() { { color: colors.textPrimary, marginLeft: 8 }, ]} > - Progress Analytics + 📈 Progress Analytics setIsModalVisible(true)} /> {activeGoals.length === 0 ? ( - + 🎯 - Tap "Add New" to create your first goal + Tap "Add New" to set your first goal! 💪 @@ -331,7 +331,7 @@ export default function GoalsScreen() { {completedGoals.length > 0 && ( {completedGoals.map((goal) => ( @@ -405,6 +405,7 @@ const styles = StyleSheet.create({ flex: 1, alignItems: "center", paddingVertical: 20, + borderRadius: 20, }, analyticsHeader: { flexDirection: "row", diff --git a/apps/mobile/src/app/(tabs)/index.tsx b/apps/mobile/src/app/(tabs)/index.tsx index 9dda6d7..8aa8a0b 100644 --- a/apps/mobile/src/app/(tabs)/index.tsx +++ b/apps/mobile/src/app/(tabs)/index.tsx @@ -5,9 +5,11 @@ import { ScrollView, RefreshControl, Image, + Animated, + TouchableOpacity, } from "react-native"; import { useUser } from "@clerk/clerk-expo"; -import { useState, useCallback, useEffect } from "react"; +import { useState, useCallback, useEffect, useRef } from "react"; import { useFocusEffect } from "@react-navigation/native"; import AsyncStorage from "@react-native-async-storage/async-storage"; import { Ionicons } from "@expo/vector-icons"; @@ -36,6 +38,11 @@ export default function HomeScreen() { const [calories, setCalories] = useState(0); const [waterIntake, setWaterIntake] = useState(0); + // Animation values + const streakPulse = useRef(new Animated.Value(1)).current; + const caloriesBounce = useRef(new Animated.Value(1)).current; + const waterBounce = useRef(new Animated.Value(1)).current; + // Refetch statistics when screen comes into focus useFocusEffect( useCallback(() => { @@ -51,9 +58,46 @@ export default function HomeScreen() { const getGreeting = () => { const hour = new Date().getHours(); - if (hour < 12) return "Good Morning"; - if (hour < 18) return "Good Afternoon"; - return "Good Evening"; + const greetings = { + morning: [ + "Rise and Shine", + "Good Morning", + "Morning Champion", + "Ready to Crush It", + ], + afternoon: [ + "Keep It Going", + "Good Afternoon", + "Stay Strong", + "You're Doing Great", + ], + evening: [ + "Evening Warrior", + "Good Evening", + "Almost There", + "Finish Strong", + ], + }; + + const randomGreeting = (arr: string[]) => + arr[Math.floor(Math.random() * arr.length)]; + + if (hour < 12) return randomGreeting(greetings.morning); + if (hour < 18) return randomGreeting(greetings.afternoon); + return randomGreeting(greetings.evening); + }; + + const getMotivationalEmoji = () => { + const emojis = ["💪", "🔥", "⚡", "🎯", "🚀", "✨"]; + return emojis[Math.floor(Math.random() * emojis.length)]; + }; + + const getStreakBadge = (streak: number) => { + if (streak >= 30) return "🏆"; + if (streak >= 14) return "🔥"; + if (streak >= 7) return "⭐"; + if (streak >= 3) return "🌟"; + return "💫"; }; const handleSaveMeal = (meal: { @@ -63,11 +107,39 @@ export default function HomeScreen() { }) => { setCalories((prev) => prev + meal.calories); setTrackMealModalVisible(false); + + // Bounce animation + Animated.sequence([ + Animated.timing(caloriesBounce, { + toValue: 1.2, + duration: 150, + useNativeDriver: true, + }), + Animated.timing(caloriesBounce, { + toValue: 1, + duration: 150, + useNativeDriver: true, + }), + ]).start(); }; const handleAddWater = (amount: number) => { setWaterIntake((prev) => prev + amount); setAddWaterModalVisible(false); + + // Bounce animation + Animated.sequence([ + Animated.timing(waterBounce, { + toValue: 1.2, + duration: 150, + useNativeDriver: true, + }), + Animated.timing(waterBounce, { + toValue: 1, + duration: 150, + useNativeDriver: true, + }), + ]).start(); }; const handleResetCalories = () => { @@ -130,6 +202,9 @@ export default function HomeScreen() { persistWater(); }, [waterIntake]); + const checkInsThisWeek = statistics?.attendance.checkInsThisWeek || 0; + const currentStreak = statistics?.attendance.currentStreak || 0; + // Check for midnight reset useEffect(() => { const checkAndResetIfNeeded = async () => { @@ -167,8 +242,29 @@ export default function HomeScreen() { return () => clearTimeout(midnightTimer); }, []); - const checkInsThisWeek = statistics?.attendance.checkInsThisWeek || 0; - const currentStreak = statistics?.attendance.currentStreak || 0; + // Streak pulse animation + useEffect(() => { + const pulse = Animated.loop( + Animated.sequence([ + Animated.timing(streakPulse, { + toValue: 1.1, + duration: 1000, + useNativeDriver: true, + }), + Animated.timing(streakPulse, { + toValue: 1, + duration: 1000, + useNativeDriver: true, + }), + ]), + ); + + if (currentStreak >= 3) { + pulse.start(); + } + + return () => pulse.stop(); + }, [currentStreak]); return ( @@ -184,20 +280,30 @@ export default function HomeScreen() { > {/* Header Section */} - - - {getGreeting()}, - + + {getGreeting()} {getMotivationalEmoji()} + + {user?.firstName || "Athlete"} + {currentStreak >= 3 && ( + + + {getStreakBadge(currentStreak)} {currentStreak} Day Streak! + + + )} - + {user?.imageUrl ? ( ) : ( @@ -210,193 +316,273 @@ export default function HomeScreen() { )} - + - {/* Daily Stats Card */} + {/* Daily Stats Card - More Playful */} - + {checkInsThisWeek} - This Week + Check-ins{"\n"}This Week - + - + {calories} - Kcal + Calories{"\n"}Tracked - + - + - + + {getStreakBadge(currentStreak)} + {currentStreak} - Day Streak + Day{"\n"}Streak - + - {/* Quick Actions */} + {/* Quick Actions - More Playful */} - + - { - console.log("Log workout tapped"); - }} - style={styles.actionCard} + console.log("Log workout tapped")} + activeOpacity={0.7} + style={[ + styles.actionCard, + { + backgroundColor: `${colors.primary}15`, + borderColor: `${colors.primary}30`, + borderWidth: 2, + }, + ]} > - + - Log Workout + 💪 Workout - + - setTrackMealModalVisible(true)} - style={styles.actionCard} + activeOpacity={0.7} + style={[ + styles.actionCard, + { + backgroundColor: `${colors.success}15`, + borderColor: `${colors.success}30`, + borderWidth: 2, + }, + ]} > - + - Track Meal + 🍽️ Track Meal - + - setAddWaterModalVisible(true)} - style={styles.actionCard} + activeOpacity={0.7} + style={[ + styles.actionCard, + { + backgroundColor: `${colors.info}15`, + borderColor: `${colors.info}30`, + borderWidth: 2, + }, + ]} > - - - + + + + + - Add Water + 💧 Add Water - + - setScanFoodModalVisible(true)} - style={styles.actionCard} + activeOpacity={0.7} + style={[ + styles.actionCard, + { + backgroundColor: `${colors.accent}15`, + borderColor: `${colors.accent}30`, + borderWidth: 2, + }, + ]} > - + - Scan Food + 📱 Scan Food - + - {/* Nutrition Progress */} + {/* Nutrition Progress - More Playful */} - - + = CALORIE_GOAL + ? "Goal smashed! 🎉" + : calories >= CALORIE_GOAL * 0.75 + ? "Almost there! Keep going 💪" + : "Let's fuel that body! 🔥" + } + /> + - + 🔥 {calories} / {CALORIE_GOAL} - {/* Hydration Progress */} + {/* Hydration Progress - More Playful */} - + - - - Hydration - + 💧 + + + Hydration + + + {waterIntake >= WATER_GOAL + ? "Perfectly hydrated! 🌊" + : "Stay hydrated, champ! 💪"} + + {waterIntake} / {WATER_GOAL} ml @@ -557,14 +762,33 @@ const styles = StyleSheet.create({ header: { flexDirection: "row", justifyContent: "space-between", - alignItems: "center", + alignItems: "flex-start", paddingHorizontal: 24, marginBottom: 24, }, + greetingContainer: { + flex: 1, + marginRight: 16, + }, + streakBadge: { + marginTop: 8, + paddingHorizontal: 12, + paddingVertical: 6, + backgroundColor: "rgba(136, 192, 208, 0.15)", + borderRadius: 999, + alignSelf: "flex-start", + }, + streakBadgeText: { + fontSize: 13, + fontWeight: "600", + color: "#88C0D0", + }, avatar: { width: 56, height: 56, borderRadius: 28, + borderWidth: 3, + borderColor: "#88C0D0", }, placeholderAvatar: { width: 56, @@ -572,15 +796,21 @@ const styles = StyleSheet.create({ borderRadius: 28, justifyContent: "center", alignItems: "center", + borderWidth: 3, + borderColor: "#88C0D0", }, section: { paddingHorizontal: 24, marginBottom: 24, }, + statsCard: { + borderRadius: 20, + }, statsRow: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", + paddingVertical: 8, }, statItem: { alignItems: "center", @@ -588,7 +818,10 @@ const styles = StyleSheet.create({ }, divider: { width: 1, - height: 60, + height: 70, + }, + emojiIcon: { + fontSize: 28, }, actionGrid: { flexDirection: "row", @@ -598,7 +831,8 @@ const styles = StyleSheet.create({ actionCard: { width: "48%", alignItems: "center", - paddingVertical: 20, + paddingVertical: 24, + borderRadius: 20, }, progressHeader: { flexDirection: "row", diff --git a/apps/mobile/src/app/(tabs)/recommendations.tsx b/apps/mobile/src/app/(tabs)/recommendations.tsx index 2594d2a..4ce68e2 100644 --- a/apps/mobile/src/app/(tabs)/recommendations.tsx +++ b/apps/mobile/src/app/(tabs)/recommendations.tsx @@ -132,7 +132,7 @@ export default function RecommendationsScreen() { - AI Recommendations + ✨ AI Recommendations - Personalized fitness & nutrition plans + {recommendations.length === 0 + ? "Let's create your perfect plan! 🚀" + : `${recommendations.length} plan${recommendations.length !== 1 ? "s" : ""} ready for you! 💪`} 0 ? `${recommendations.length} active plan${recommendations.length !== 1 ? "s" : ""}` @@ -194,19 +196,9 @@ export default function RecommendationsScreen() { /> {recommendations.length === 0 ? ( - + - - - + 🤖 Tap "Generate New Plan" to get personalized AI-powered fitness - and nutrition recommendations based on your profile and goals. + and nutrition recommendations! 🎯 @@ -258,7 +250,7 @@ function RecommendationCard({ recommendation }: RecommendationCardProps) { const [expanded, setExpanded] = useState(false); return ( - + {/* Header */} setExpanded(!expanded)} @@ -278,7 +270,7 @@ function RecommendationCard({ recommendation }: RecommendationCardProps) { - AI Fitness Plan + ✨ AI Fitness Plan {new Date(recommendation.generatedAt).toLocaleDateString()} @@ -320,7 +312,7 @@ function RecommendationCard({ recommendation }: RecommendationCardProps) { { color: colors.textPrimary, marginLeft: 8 }, ]} > - Activity Plan + 💪 Activity Plan - Diet Plan + 🍽️ Diet Plan { + if (isCompleted) { + Animated.sequence([ + Animated.spring(scaleAnim, { + toValue: 1.05, + friction: 3, + tension: 40, + useNativeDriver: true, + }), + Animated.spring(scaleAnim, { + toValue: 1, + friction: 3, + tension: 40, + useNativeDriver: true, + }), + ]).start(); + } + }, [isCompleted]); + + const handleComplete = () => { + if (onComplete) { + // Trigger celebration animation + Animated.sequence([ + Animated.spring(scaleAnim, { + toValue: 1.1, + friction: 3, + tension: 40, + useNativeDriver: true, + }), + Animated.spring(scaleAnim, { + toValue: 1, + friction: 3, + tension: 40, + useNativeDriver: true, + }), + ]).start(() => { + onComplete(); + }); + } + }; // Calculate days remaining const daysRemaining = goal.targetDate @@ -71,160 +121,169 @@ export function GoalProgressCard({ }; return ( - - - {/* Header */} - - - - - - - - + + + {/* Header */} + + + - {goal.title} - - {goal.description && ( + + + + - {goal.description} + {goal.title} + {goal.description && ( + + {goal.description} + + )} + + + + {/* Action Buttons */} + + {!isCompleted && onComplete && ( + + + + )} + {onDelete && ( + + + )} - {/* Action Buttons */} - - {!isCompleted && onComplete && ( - - - - )} - {onDelete && ( - - - - )} - - + {/* Progress Section */} + {goal.targetValue && ( + + + + {goal.currentValue || 0} / {goal.targetValue}{" "} + {goal.unit || ""} + + + {(progress * 100).toFixed(0)}% + + - {/* Progress Section */} - {goal.targetValue && ( - - - - {goal.currentValue || 0} / {goal.targetValue} {goal.unit || ""} - + + + )} + + {/* Footer */} + + {isCompleted ? ( + + ) : ( + + {goal.priority === "high" && ( + + )} + {goal.priority === "medium" && ( + + )} + {goal.priority === "low" && ( + + )} + + )} + + {daysRemaining !== null && !isCompleted && ( - {(progress * 100).toFixed(0)}% + {daysRemaining < 0 + ? `${Math.abs(daysRemaining)} days overdue` + : `${daysRemaining} days remaining`} - + )} - + {isCompleted && goal.completedDate && ( + + Completed {new Date(goal.completedDate).toLocaleDateString()} + + )} - )} - - {/* Footer */} - - {isCompleted ? ( - - ) : ( - - {goal.priority === "high" && ( - - )} - {goal.priority === "medium" && ( - - )} - {goal.priority === "low" && ( - - )} - - )} - - {daysRemaining !== null && !isCompleted && ( - - {daysRemaining < 0 - ? `${Math.abs(daysRemaining)} days overdue` - : `${daysRemaining} days remaining`} - - )} - - {isCompleted && goal.completedDate && ( - - Completed {new Date(goal.completedDate).toLocaleDateString()} - - )} - - - + + + ); }