diff --git a/apps/admin/data/fitai.db b/apps/admin/data/fitai.db index 17ac7ec..130e545 100644 Binary files a/apps/admin/data/fitai.db and b/apps/admin/data/fitai.db differ diff --git a/apps/mobile/src/app/(auth)/onboarding.tsx b/apps/mobile/src/app/(auth)/onboarding.tsx index 2c76b18..5f6c661 100644 --- a/apps/mobile/src/app/(auth)/onboarding.tsx +++ b/apps/mobile/src/app/(auth)/onboarding.tsx @@ -96,9 +96,13 @@ export default function OnboardingScreen() { const fitnessData = { userId: user.id, - height: parseFloat(fitnessProfile.height), - weight: parseFloat(fitnessProfile.weight), - age: parseInt(fitnessProfile.age), + height: fitnessProfile.height + ? parseFloat(fitnessProfile.height) + : undefined, + weight: fitnessProfile.weight + ? parseFloat(fitnessProfile.weight) + : undefined, + age: fitnessProfile.age ? parseInt(fitnessProfile.age, 10) : undefined, fitnessGoals: fitnessProfile.goals ? [fitnessProfile.goals] : [], medicalConditions: fitnessProfile.medicalConditions || undefined, allergies: fitnessProfile.dietaryRestrictions || undefined, diff --git a/apps/mobile/src/app/fitness-profile.tsx b/apps/mobile/src/app/fitness-profile.tsx index 59ef8cc..788a86d 100644 --- a/apps/mobile/src/app/fitness-profile.tsx +++ b/apps/mobile/src/app/fitness-profile.tsx @@ -13,8 +13,10 @@ import { import { useRouter, Stack } from "expo-router"; import { useAuth } from "@clerk/clerk-expo"; import { Ionicons } from "@expo/vector-icons"; -import { LinearGradient } from "expo-linear-gradient"; -import { theme } from "../styles/theme"; +import { useTheme } from "../contexts/ThemeContext"; +import { MinimalCard } from "../components/MinimalCard"; +import { MinimalButton } from "../components/MinimalButton"; +import { IconContainer } from "../components/IconContainer"; import { API_BASE_URL } from "../config/api"; interface FitnessProfileData { @@ -30,13 +32,13 @@ interface FitnessProfileData { } const GENDER_OPTIONS = [ - { label: "Male", value: "male", icon: "male" }, - { label: "Female", value: "female", icon: "female" }, - { label: "Other", value: "other", icon: "transgender" }, + { label: "Male", value: "male", icon: "male-outline" }, + { label: "Female", value: "female", icon: "female-outline" }, + { label: "Other", value: "other", icon: "transgender-outline" }, { label: "Prefer not to say", value: "prefer_not_to_say", - icon: "help-circle", + icon: "help-circle-outline", }, ]; @@ -45,31 +47,26 @@ const FITNESS_GOAL_OPTIONS = [ label: "Weight Loss", value: "weight_loss", icon: "trending-down", - color: theme.colors.danger, + color: "#FF3B3B", }, { label: "Muscle Gain", value: "muscle_gain", icon: "barbell", - color: theme.colors.primary, - }, - { - label: "Endurance", - value: "endurance", - icon: "bicycle", - color: theme.colors.success, + color: "#0066FF", }, + { label: "Endurance", value: "endurance", icon: "bicycle", color: "#00D26A" }, { label: "Flexibility", value: "flexibility", icon: "body", - color: theme.colors.purple, + color: "#7B2CBF", }, { label: "General Fitness", value: "general_fitness", icon: "fitness", - color: theme.colors.warning, + color: "#FFB800", }, ]; @@ -99,6 +96,7 @@ const ACTIVITY_LEVEL_OPTIONS = [ export default function FitnessProfileScreen() { const router = useRouter(); + const { colors, typography } = useTheme(); const { userId, getToken } = useAuth(); const [loading, setLoading] = useState(false); const [fetchingProfile, setFetchingProfile] = useState(true); @@ -124,7 +122,6 @@ export default function FitnessProfileScreen() { if (response.ok) { const data = await response.json(); if (data.profile) { - // Normalize old activity level values to new schema let activityLevel = data.profile.activityLevel || ""; if (activityLevel === "light") activityLevel = "lightly_active"; if (activityLevel === "moderate") activityLevel = "moderately_active"; @@ -159,8 +156,6 @@ export default function FitnessProfileScreen() { setLoading(true); const token = await getToken(); - // Prepare data with userId and convert fitnessGoal to fitnessGoals array - // Convert empty strings to undefined for optional enum fields const dataToSave = { userId: userId, height: profileData.height, @@ -205,8 +200,13 @@ export default function FitnessProfileScreen() { if (fetchingProfile) { return ( - - + + ); } @@ -214,18 +214,28 @@ export default function FitnessProfileScreen() { return ( <> - + {/* Header */} - + router.back()} > - Fitness Profile + + Fitness Profile + - + {/* Basic Information */} - Basic Information - - + + Basic Information + + + - Height (cm) - + + HEIGHT (CM) + + updateField( @@ -256,20 +288,35 @@ export default function FitnessProfileScreen() { } keyboardType="decimal-pad" placeholder="175" - placeholderTextColor={theme.colors.gray400} + placeholderTextColor={colors.textTertiary} /> - Weight (kg) - + + WEIGHT (KG) + + updateField( @@ -279,45 +326,75 @@ export default function FitnessProfileScreen() { } keyboardType="decimal-pad" placeholder="70" - placeholderTextColor={theme.colors.gray400} + placeholderTextColor={colors.textTertiary} /> - Age - + + AGE + + updateField("age", text ? parseInt(text, 10) : undefined) } keyboardType="number-pad" placeholder="25" - placeholderTextColor={theme.colors.gray400} + placeholderTextColor={colors.textTertiary} /> - + {/* Gender Selection */} - Gender - + + Gender + + {GENDER_OPTIONS.map((option) => ( updateField("gender", option.value)} > @@ -326,15 +403,22 @@ export default function FitnessProfileScreen() { size={24} color={ profileData.gender === option.value - ? theme.colors.primary - : theme.colors.gray400 + ? "#fff" + : colors.textTertiary } /> {option.label} @@ -346,8 +430,15 @@ export default function FitnessProfileScreen() { {/* Fitness Goal */} - Primary Fitness Goal - + + Primary Fitness Goal + + {FITNESS_GOAL_OPTIONS.map((option, index) => ( - {option.label} + + {option.label} + {profileData.fitnessGoal === option.value && ( )} {index < FITNESS_GOAL_OPTIONS.length - 1 && ( - + )} ))} - + {/* Activity Level */} - Activity Level - + + Activity Level + + {ACTIVITY_LEVEL_OPTIONS.map((option, index) => ( updateField("activityLevel", option.value)} > - {option.label} - + + {option.label} + + {option.description} @@ -403,90 +525,141 @@ export default function FitnessProfileScreen() { )} {index < ACTIVITY_LEVEL_OPTIONS.length - 1 && ( - + )} ))} - + {/* Health Information */} - - Health Information (Optional) + + Health Information - + - Medical Conditions + + MEDICAL CONDITIONS + updateField("medicalConditions", text) } placeholder="e.g., Asthma, diabetes..." - placeholderTextColor={theme.colors.gray400} + placeholderTextColor={colors.textTertiary} multiline numberOfLines={3} textAlignVertical="top" /> - Allergies + + ALLERGIES + updateField("allergies", text)} placeholder="e.g., Peanuts, latex..." - placeholderTextColor={theme.colors.gray400} + placeholderTextColor={colors.textTertiary} multiline numberOfLines={3} textAlignVertical="top" /> - Injuries + + INJURIES + updateField("injuries", text)} placeholder="e.g., Previous knee injury..." - placeholderTextColor={theme.colors.gray400} + placeholderTextColor={colors.textTertiary} multiline numberOfLines={3} textAlignVertical="top" /> - + + + {/* Save Button */} - - + - - {loading ? ( - - ) : ( - <> - - Save Profile - - )} - - + variant="primary" + size="xl" + fullWidth + loading={loading} + /> @@ -496,35 +669,27 @@ export default function FitnessProfileScreen() { const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: theme.colors.background, }, loadingContainer: { flex: 1, justifyContent: "center", alignItems: "center", - backgroundColor: theme.colors.background, }, header: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", - paddingTop: Platform.OS === "ios" ? 60 : 40, paddingBottom: 20, paddingHorizontal: 20, }, backButton: { width: 40, height: 40, - borderRadius: 20, + borderRadius: 12, backgroundColor: "rgba(255, 255, 255, 0.2)", justifyContent: "center", alignItems: "center", }, - headerTitle: { - fontSize: theme.typography.fontSize["2xl"], - fontWeight: theme.typography.fontWeight.bold, - color: "#fff", - }, scrollView: { flex: 1, }, @@ -533,118 +698,69 @@ const styles = StyleSheet.create({ paddingBottom: 100, }, section: { - marginBottom: 24, - }, - sectionTitle: { - fontSize: theme.typography.fontSize.lg, - fontWeight: theme.typography.fontWeight.bold, - color: theme.colors.gray900, - marginBottom: 12, + marginBottom: 28, }, card: { - backgroundColor: "#fff", - borderRadius: theme.borderRadius.xl, - padding: 16, - ...theme.shadows.subtle, - borderWidth: 1, - borderColor: theme.colors.gray100, + padding: 4, }, - row: { + inputRow: { flexDirection: "row", gap: 12, - marginBottom: 16, }, inputGroup: { flex: 1, - marginBottom: 16, - }, - label: { - fontSize: theme.typography.fontSize.sm, - fontWeight: theme.typography.fontWeight.semibold, - color: theme.colors.gray700, - marginBottom: 8, + marginBottom: 4, }, inputContainer: { flexDirection: "row", alignItems: "center", - backgroundColor: theme.colors.gray50, - borderRadius: theme.borderRadius.lg, - borderWidth: 1, - borderColor: theme.colors.gray200, - paddingHorizontal: 12, - gap: 8, + borderRadius: 14, + borderWidth: 1.5, + paddingHorizontal: 14, + gap: 10, }, input: { flex: 1, - paddingVertical: 12, - fontSize: theme.typography.fontSize.base, - color: theme.colors.gray900, + paddingVertical: 14, + fontSize: 16, + fontWeight: "600", }, textArea: { - backgroundColor: theme.colors.gray50, - borderRadius: theme.borderRadius.lg, - borderWidth: 1, - borderColor: theme.colors.gray200, - padding: 12, - fontSize: theme.typography.fontSize.base, - color: theme.colors.gray900, - minHeight: 80, + borderRadius: 14, + borderWidth: 1.5, + padding: 14, + fontSize: 16, + minHeight: 90, }, - optionsRow: { + genderRow: { flexDirection: "row", - gap: 12, + gap: 10, }, - optionCard: { + genderCard: { flex: 1, - backgroundColor: "#fff", - borderRadius: theme.borderRadius.lg, - padding: 16, - alignItems: "center", - gap: 8, + paddingVertical: 16, + paddingHorizontal: 8, + borderRadius: 14, borderWidth: 2, - borderColor: theme.colors.gray200, - ...theme.shadows.subtle, - }, - optionCardActive: { - borderColor: theme.colors.primary, - backgroundColor: `${theme.colors.primary}10`, - }, - optionLabel: { - fontSize: theme.typography.fontSize.sm, - fontWeight: theme.typography.fontWeight.medium, - color: theme.colors.gray600, - }, - optionLabelActive: { - color: theme.colors.primary, - fontWeight: theme.typography.fontWeight.bold, + alignItems: "center", }, listItem: { flexDirection: "row", alignItems: "center", - paddingVertical: 12, - gap: 12, + paddingVertical: 14, + paddingHorizontal: 12, + gap: 14, }, iconCircle: { - width: 40, - height: 40, - borderRadius: 20, + width: 44, + height: 44, + borderRadius: 12, justifyContent: "center", alignItems: "center", }, - listItemText: { - flex: 1, - fontSize: theme.typography.fontSize.base, - fontWeight: theme.typography.fontWeight.medium, - color: theme.colors.gray900, - }, - listItemDescription: { - fontSize: theme.typography.fontSize.xs, - color: theme.colors.gray500, - marginTop: 2, - }, divider: { height: 1, - backgroundColor: theme.colors.gray100, + marginLeft: 58, }, footer: { position: "absolute", @@ -652,29 +768,7 @@ const styles = StyleSheet.create({ left: 0, right: 0, padding: 20, - paddingBottom: Platform.OS === "ios" ? 40 : 20, - backgroundColor: "#fff", + paddingBottom: 34, borderTopWidth: 1, - borderTopColor: theme.colors.gray100, - ...theme.shadows.medium, - }, - saveButton: { - borderRadius: theme.borderRadius.lg, - overflow: "hidden", - }, - saveButtonGradient: { - flexDirection: "row", - alignItems: "center", - justifyContent: "center", - paddingVertical: 16, - gap: 8, - }, - saveButtonDisabled: { - opacity: 0.6, - }, - saveButtonText: { - fontSize: theme.typography.fontSize.base, - fontWeight: theme.typography.fontWeight.bold, - color: "#fff", }, });