fitaiProto/apps/mobile/src/app/welcome.tsx
echo e287e55f1f welcome page
with profile form
2025-11-07 21:16:03 +01:00

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',
},
})