451 lines
12 KiB
TypeScript
451 lines
12 KiB
TypeScript
import React, { useState } from "react";
|
|
import {
|
|
View,
|
|
Text,
|
|
TextInput,
|
|
TouchableOpacity,
|
|
StyleSheet,
|
|
Alert,
|
|
ScrollView,
|
|
} from "react-native";
|
|
import { useRouter } from "expo-router";
|
|
import { useAuth } from "@clerk/clerk-expo";
|
|
import * as SecureStore from "expo-secure-store";
|
|
import { profileApi } from "../api/profile";
|
|
import { getErrorMessage } from "../utils/error-helpers";
|
|
|
|
interface FitnessProfile {
|
|
height: string;
|
|
weight: string;
|
|
age: string;
|
|
gender: "male" | "female" | "other";
|
|
activityLevel: "sedentary" | "light" | "moderate" | "active" | "very_active";
|
|
fitnessGoals: string[];
|
|
exerciseHabits: string;
|
|
dietHabits: string;
|
|
medicalConditions: string;
|
|
}
|
|
|
|
export default function WelcomeScreen() {
|
|
const [profile, setProfile] = useState<FitnessProfile>({
|
|
height: "",
|
|
weight: "",
|
|
age: "",
|
|
gender: "male",
|
|
activityLevel: "moderate",
|
|
fitnessGoals: [],
|
|
exerciseHabits: "",
|
|
dietHabits: "",
|
|
medicalConditions: "",
|
|
});
|
|
const [loading, setLoading] = useState(false);
|
|
const router = useRouter();
|
|
const { getToken } = useAuth();
|
|
|
|
const fitnessGoalsOptions = [
|
|
"Weight Loss",
|
|
"Muscle Gain",
|
|
"Improve Endurance",
|
|
"Better Flexibility",
|
|
"General Fitness",
|
|
"Strength Training",
|
|
"Cardio Health",
|
|
];
|
|
|
|
const activityLevels: Array<{
|
|
value: FitnessProfile["activityLevel"];
|
|
label: string;
|
|
}> = [
|
|
{ value: "sedentary", label: "Sedentary (little or no exercise)" },
|
|
{ value: "light", label: "Light (1-3 days/week)" },
|
|
{ value: "moderate", label: "Moderate (3-5 days/week)" },
|
|
{ value: "active", label: "Active (6-7 days/week)" },
|
|
{ value: "very_active", label: "Very Active (twice per day)" },
|
|
];
|
|
|
|
const toggleGoal = (goal: string) => {
|
|
setProfile((prev) => ({
|
|
...prev,
|
|
fitnessGoals: prev.fitnessGoals.includes(goal)
|
|
? prev.fitnessGoals.filter((g) => g !== goal)
|
|
: [...prev.fitnessGoals, goal],
|
|
}));
|
|
};
|
|
|
|
const handleSubmit = async () => {
|
|
if (!profile.height || !profile.weight || !profile.age) {
|
|
Alert.alert("Error", "Please fill in all required fields");
|
|
return;
|
|
}
|
|
|
|
setLoading(true);
|
|
try {
|
|
const token = await getToken();
|
|
if (!token) {
|
|
throw new Error("Authentication required");
|
|
}
|
|
|
|
const user = await SecureStore.getItemAsync("user");
|
|
if (!user) {
|
|
throw new Error("No user found");
|
|
}
|
|
|
|
const userData = JSON.parse(user);
|
|
|
|
await profileApi.createFitnessProfile(
|
|
{
|
|
userId: userData.id,
|
|
...profile,
|
|
},
|
|
token,
|
|
);
|
|
|
|
Alert.alert("Success", "Profile completed successfully!", [
|
|
{ text: "OK", onPress: () => router.replace("/(tabs)") },
|
|
]);
|
|
} catch (error: unknown) {
|
|
console.log("Profile save error:", error);
|
|
Alert.alert(
|
|
"Error",
|
|
getErrorMessage(error, "Failed to save profile. Please try again."),
|
|
);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<ScrollView style={styles.container}>
|
|
<View style={styles.content}>
|
|
<Text style={styles.title}>Welcome to FitAI!</Text>
|
|
<Text style={styles.subtitle}>Let's set up your fitness profile</Text>
|
|
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>Basic Information</Text>
|
|
|
|
<View style={styles.row}>
|
|
<View style={[styles.inputContainer, { flex: 1, marginRight: 8 }]}>
|
|
<Text style={styles.label}>Height (cm)</Text>
|
|
<TextInput
|
|
style={styles.input}
|
|
value={profile.height}
|
|
onChangeText={(text) =>
|
|
setProfile({ ...profile, height: text })
|
|
}
|
|
keyboardType="numeric"
|
|
placeholder="170"
|
|
/>
|
|
</View>
|
|
|
|
<View style={[styles.inputContainer, { flex: 1, marginLeft: 8 }]}>
|
|
<Text style={styles.label}>Weight (kg)</Text>
|
|
<TextInput
|
|
style={styles.input}
|
|
value={profile.weight}
|
|
onChangeText={(text) =>
|
|
setProfile({ ...profile, weight: text })
|
|
}
|
|
keyboardType="numeric"
|
|
placeholder="70"
|
|
/>
|
|
</View>
|
|
</View>
|
|
|
|
<View style={styles.row}>
|
|
<View style={[styles.inputContainer, { flex: 1, marginRight: 8 }]}>
|
|
<Text style={styles.label}>Age</Text>
|
|
<TextInput
|
|
style={styles.input}
|
|
value={profile.age}
|
|
onChangeText={(text) => setProfile({ ...profile, age: text })}
|
|
keyboardType="numeric"
|
|
placeholder="25"
|
|
/>
|
|
</View>
|
|
|
|
<View style={[styles.inputContainer, { flex: 1, marginLeft: 8 }]}>
|
|
<Text style={styles.label}>Gender</Text>
|
|
<View style={styles.genderRow}>
|
|
{(["male", "female", "other"] as const).map((gender) => (
|
|
<TouchableOpacity
|
|
key={gender}
|
|
style={[
|
|
styles.genderButton,
|
|
profile.gender === gender && styles.genderButtonSelected,
|
|
]}
|
|
onPress={() => setProfile({ ...profile, gender })}
|
|
>
|
|
<Text
|
|
style={[
|
|
styles.genderButtonText,
|
|
profile.gender === gender &&
|
|
styles.genderButtonTextSelected,
|
|
]}
|
|
>
|
|
{gender.charAt(0).toUpperCase() + gender.slice(1)}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
))}
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>Activity Level</Text>
|
|
{activityLevels.map((level) => (
|
|
<TouchableOpacity
|
|
key={level.value}
|
|
style={[
|
|
styles.activityOption,
|
|
profile.activityLevel === level.value &&
|
|
styles.activityOptionSelected,
|
|
]}
|
|
onPress={() =>
|
|
setProfile({ ...profile, activityLevel: level.value })
|
|
}
|
|
>
|
|
<Text
|
|
style={[
|
|
styles.activityText,
|
|
profile.activityLevel === level.value &&
|
|
styles.activityTextSelected,
|
|
]}
|
|
>
|
|
{level.label}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
))}
|
|
</View>
|
|
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>Fitness Goals</Text>
|
|
<Text style={styles.sectionSubtitle}>Select all that apply</Text>
|
|
<View style={styles.goalsContainer}>
|
|
{fitnessGoalsOptions.map((goal) => (
|
|
<TouchableOpacity
|
|
key={goal}
|
|
style={[
|
|
styles.goalButton,
|
|
profile.fitnessGoals.includes(goal) &&
|
|
styles.goalButtonSelected,
|
|
]}
|
|
onPress={() => toggleGoal(goal)}
|
|
>
|
|
<Text
|
|
style={[
|
|
styles.goalButtonText,
|
|
profile.fitnessGoals.includes(goal) &&
|
|
styles.goalButtonTextSelected,
|
|
]}
|
|
>
|
|
{goal}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
))}
|
|
</View>
|
|
</View>
|
|
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>Exercise Habits</Text>
|
|
<TextInput
|
|
style={[styles.input, styles.textArea]}
|
|
value={profile.exerciseHabits}
|
|
onChangeText={(text) =>
|
|
setProfile({ ...profile, exerciseHabits: text })
|
|
}
|
|
placeholder="Describe your current exercise routine..."
|
|
multiline
|
|
numberOfLines={3}
|
|
/>
|
|
</View>
|
|
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>Diet Habits</Text>
|
|
<TextInput
|
|
style={[styles.input, styles.textArea]}
|
|
value={profile.dietHabits}
|
|
onChangeText={(text) =>
|
|
setProfile({ ...profile, dietHabits: text })
|
|
}
|
|
placeholder="Describe your current eating habits..."
|
|
multiline
|
|
numberOfLines={3}
|
|
/>
|
|
</View>
|
|
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>Medical Conditions (Optional)</Text>
|
|
<TextInput
|
|
style={[styles.input, styles.textArea]}
|
|
value={profile.medicalConditions}
|
|
onChangeText={(text) =>
|
|
setProfile({ ...profile, medicalConditions: text })
|
|
}
|
|
placeholder="Any medical conditions we should know about..."
|
|
multiline
|
|
numberOfLines={3}
|
|
/>
|
|
</View>
|
|
|
|
<TouchableOpacity
|
|
style={[styles.button, loading && styles.buttonDisabled]}
|
|
onPress={handleSubmit}
|
|
disabled={loading}
|
|
>
|
|
<Text style={styles.buttonText}>
|
|
{loading ? "Saving..." : "Complete Profile"}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
</ScrollView>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: "#f5f5f5",
|
|
},
|
|
content: {
|
|
padding: 20,
|
|
},
|
|
title: {
|
|
fontSize: 28,
|
|
fontWeight: "bold",
|
|
marginBottom: 8,
|
|
color: "#333",
|
|
textAlign: "center",
|
|
},
|
|
subtitle: {
|
|
fontSize: 16,
|
|
color: "#666",
|
|
marginBottom: 32,
|
|
textAlign: "center",
|
|
},
|
|
section: {
|
|
marginBottom: 24,
|
|
},
|
|
sectionTitle: {
|
|
fontSize: 18,
|
|
fontWeight: "600",
|
|
marginBottom: 12,
|
|
color: "#333",
|
|
},
|
|
sectionSubtitle: {
|
|
fontSize: 14,
|
|
color: "#666",
|
|
marginBottom: 12,
|
|
},
|
|
row: {
|
|
flexDirection: "row",
|
|
marginBottom: 16,
|
|
},
|
|
inputContainer: {
|
|
marginBottom: 16,
|
|
},
|
|
label: {
|
|
fontSize: 14,
|
|
fontWeight: "500",
|
|
marginBottom: 6,
|
|
color: "#333",
|
|
},
|
|
input: {
|
|
backgroundColor: "white",
|
|
paddingHorizontal: 16,
|
|
paddingVertical: 12,
|
|
borderRadius: 8,
|
|
borderWidth: 1,
|
|
borderColor: "#ddd",
|
|
fontSize: 16,
|
|
},
|
|
textArea: {
|
|
height: 80,
|
|
textAlignVertical: "top",
|
|
},
|
|
genderRow: {
|
|
flexDirection: "row",
|
|
},
|
|
genderButton: {
|
|
flex: 1,
|
|
paddingVertical: 12,
|
|
paddingHorizontal: 8,
|
|
borderWidth: 1,
|
|
borderColor: "#ddd",
|
|
borderRadius: 8,
|
|
alignItems: "center",
|
|
marginRight: 4,
|
|
},
|
|
genderButtonSelected: {
|
|
backgroundColor: "#3b82f6",
|
|
borderColor: "#3b82f6",
|
|
},
|
|
genderButtonText: {
|
|
fontSize: 12,
|
|
color: "#666",
|
|
},
|
|
genderButtonTextSelected: {
|
|
color: "white",
|
|
},
|
|
activityOption: {
|
|
paddingVertical: 12,
|
|
paddingHorizontal: 16,
|
|
borderWidth: 1,
|
|
borderColor: "#ddd",
|
|
borderRadius: 8,
|
|
marginBottom: 8,
|
|
backgroundColor: "white",
|
|
},
|
|
activityOptionSelected: {
|
|
backgroundColor: "#3b82f6",
|
|
borderColor: "#3b82f6",
|
|
},
|
|
activityText: {
|
|
fontSize: 14,
|
|
color: "#333",
|
|
},
|
|
activityTextSelected: {
|
|
color: "white",
|
|
},
|
|
goalsContainer: {
|
|
flexDirection: "row",
|
|
flexWrap: "wrap",
|
|
marginHorizontal: -4,
|
|
},
|
|
goalButton: {
|
|
backgroundColor: "white",
|
|
borderWidth: 1,
|
|
borderColor: "#ddd",
|
|
borderRadius: 20,
|
|
paddingHorizontal: 12,
|
|
paddingVertical: 8,
|
|
margin: 4,
|
|
},
|
|
goalButtonSelected: {
|
|
backgroundColor: "#3b82f6",
|
|
borderColor: "#3b82f6",
|
|
},
|
|
goalButtonText: {
|
|
fontSize: 12,
|
|
color: "#666",
|
|
},
|
|
goalButtonTextSelected: {
|
|
color: "white",
|
|
},
|
|
button: {
|
|
backgroundColor: "#3b82f6",
|
|
paddingVertical: 16,
|
|
borderRadius: 8,
|
|
alignItems: "center",
|
|
marginTop: 16,
|
|
},
|
|
buttonDisabled: {
|
|
backgroundColor: "#9ca3af",
|
|
},
|
|
buttonText: {
|
|
color: "white",
|
|
fontSize: 16,
|
|
fontWeight: "600",
|
|
},
|
|
});
|