import React, { useState, useEffect } from "react"; import { View, Text, TextInput, TouchableOpacity, StyleSheet, ScrollView, Alert, ActivityIndicator, } from "react-native"; import { useUser, useAuth } from "@clerk/clerk-expo"; import { useRouter } from "expo-router"; import { fitnessProfileApi } from "@/api/fitnessProfile"; import { gymsApi, type Gym } from "@/api/gyms"; import { useTheme } from "../../contexts/ThemeContext"; import { Input } from "../../components/Input"; import { MinimalButton } from "../../components/MinimalButton"; import { MinimalCard } from "../../components/MinimalCard"; import log from "../../utils/logger"; export default function OnboardingScreen() { const { user } = useUser(); const { getToken } = useAuth(); const router = useRouter(); const { colors, typography } = useTheme(); const [isSubmitting, setIsSubmitting] = useState(false); const [gyms, setGyms] = useState([]); const [gymsLoading, setGymsLoading] = useState(false); const [selectedGymId, setSelectedGymId] = useState(null); useEffect(() => { const loadGyms = async () => { try { setGymsLoading(true); const token = await getToken(); const data = await gymsApi.getGyms(token); setGyms(data); } catch (e) { log.error("Failed to fetch gyms", e); } finally { setGymsLoading(false); } }; loadGyms(); }, []); const [fitnessProfile, setFitnessProfile] = useState({ height: "", weight: "", age: "", goals: "", fitnessLevel: "beginner", medicalConditions: "", dietaryRestrictions: "", preferredWorkoutTime: "morning", workoutFrequency: "3", }); const handleSubmit = async () => { if (!user?.id) return; try { setIsSubmitting(true); // Validate required fields if ( !fitnessProfile.height || !fitnessProfile.weight || !fitnessProfile.age ) { Alert.alert("Error", "Please enter your height, weight, and age"); return; } const token = await getToken(); if (!token) { throw new Error("Authentication token not available"); } // If gym was selected or cleared, patch user's gym selection first // selectedGymId: string gym id, or null to proceed without gym try { await gymsApi.updateUserGym(selectedGymId, token); } catch (e) { log.warn("Failed to update gym selection", { gymId: selectedGymId }); } const fitnessData = { userId: user.id, 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, }; await fitnessProfileApi.createFitnessProfile(fitnessData, token); router.replace("/(tabs)"); } catch (error) { log.error("Failed to create fitness profile", error); Alert.alert( "Error", "Failed to create fitness profile. Please try again.", ); } finally { setIsSubmitting(false); } }; const handleLevelSelect = (level: string) => { setFitnessProfile({ ...fitnessProfile, fitnessLevel: level }); }; const handleTimeSelect = (time: string) => { setFitnessProfile({ ...fitnessProfile, preferredWorkoutTime: time }); }; // Calculate progress based on filled fields const calculateProgress = () => { const fields = [ fitnessProfile.height, fitnessProfile.weight, fitnessProfile.age, fitnessProfile.goals, fitnessProfile.medicalConditions || "n/a", // Optional fields count as filled fitnessProfile.dietaryRestrictions || "n/a", ]; const filledFields = fields.filter( (field) => field && field.trim() !== "", ).length; return Math.round((filledFields / fields.length) * 100); }; const progress = calculateProgress(); return ( Set Up Your Fitness Profile Help us personalize your fitness journey {/* Progress indicator */} {progress}% Complete setFitnessProfile({ ...fitnessProfile, height: value }) } keyboardType="numeric" placeholder="Enter height in cm" /> setFitnessProfile({ ...fitnessProfile, weight: value }) } keyboardType="numeric" placeholder="Enter weight in kg" /> setFitnessProfile({ ...fitnessProfile, age: value }) } keyboardType="numeric" placeholder="Enter your age" /> setFitnessProfile({ ...fitnessProfile, goals: value }) } multiline numberOfLines={3} placeholder="What are your fitness goals?" /> Fitness Level {["beginner", "intermediate", "advanced"].map((level) => ( handleLevelSelect(level)} > {level} ))} setFitnessProfile({ ...fitnessProfile, medicalConditions: value }) } multiline numberOfLines={3} placeholder="Any medical conditions we should know about?" /> setFitnessProfile({ ...fitnessProfile, dietaryRestrictions: value, }) } multiline numberOfLines={3} placeholder="Any dietary restrictions?" /> Preferred Workout Time {["morning", "afternoon", "evening"].map((time) => ( handleTimeSelect(time)} > {time} ))} setFitnessProfile({ ...fitnessProfile, workoutFrequency: value }) } keyboardType="numeric" placeholder="Number of workouts per week" /> Select a Gym {gymsLoading ? ( ) : ( setSelectedGymId(null)} > Proceed without gym {gyms.map((gym) => ( setSelectedGymId(gym.id)} > {gym.name} ))} )} ); } const styles = StyleSheet.create({ container: { flex: 1, }, title: { textAlign: "center", marginTop: 40, marginBottom: 8, }, subtitle: { textAlign: "center", marginBottom: 32, }, form: { padding: 20, }, label: { marginBottom: 8, }, textArea: { minHeight: 80, textAlignVertical: "top", marginBottom: 16, }, buttonGroup: { flexDirection: "row", flexWrap: "wrap", gap: 8, marginBottom: 16, }, segmentButton: { minWidth: 100, padding: 10, borderRadius: 10, borderWidth: 1, alignItems: "center", }, progressContainer: { paddingHorizontal: 20, marginBottom: 16, }, progressBarBackground: { height: 8, borderRadius: 4, overflow: "hidden", marginBottom: 8, }, progressBarFill: { height: "100%", borderRadius: 4, }, progressText: { textAlign: "center" }, });