408 lines
11 KiB
TypeScript
408 lines
11 KiB
TypeScript
import React, { useState } from 'react'
|
|
import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert, ScrollView } from 'react-native'
|
|
import { useRouter } from 'expo-router'
|
|
import axios from 'axios'
|
|
import * as SecureStore from 'expo-secure-store'
|
|
import { API_BASE_URL, API_ENDPOINTS } from '../config/api'
|
|
|
|
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 fitnessGoalsOptions = [
|
|
'Weight Loss',
|
|
'Muscle Gain',
|
|
'Improve Endurance',
|
|
'Better Flexibility',
|
|
'General Fitness',
|
|
'Strength Training',
|
|
'Cardio Health'
|
|
]
|
|
|
|
const activityLevels = [
|
|
{ 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 user = await SecureStore.getItemAsync('user')
|
|
if (!user) {
|
|
throw new Error('No user found')
|
|
}
|
|
|
|
const userData = JSON.parse(user)
|
|
|
|
const response = await axios.post(
|
|
`${API_BASE_URL}${API_ENDPOINTS.PROFILE.FITNESS}`,
|
|
{
|
|
userId: userData.id,
|
|
...profile
|
|
}
|
|
)
|
|
|
|
if (response.status === 201) {
|
|
Alert.alert('Success', 'Profile completed successfully!', [
|
|
{ text: 'OK', onPress: () => router.replace('/(tabs)') }
|
|
])
|
|
}
|
|
} catch (error: any) {
|
|
console.log('Profile save error:', error)
|
|
Alert.alert('Error', error.response?.data?.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 as any })}
|
|
>
|
|
<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',
|
|
},
|
|
}) |