fitaiProto/apps/mobile/src/app/welcome.tsx
2026-03-10 04:14:03 +01:00

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",
},
});