322 lines
7.7 KiB
JavaScript
322 lines
7.7 KiB
JavaScript
import React, { useEffect, useState } from 'react';
|
|
import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator, Image, TextInput, ScrollView, Alert } from 'react-native';
|
|
import { Stack } from 'expo-router';
|
|
import apiService from '../../lib/api/apiService';
|
|
import Colors from '../../constants';
|
|
|
|
export default function ProfileScreen() {
|
|
const [profile, setProfile] = useState(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState(null);
|
|
const [isEditing, setIsEditing] = useState(false);
|
|
const [formData, setFormData] = useState({
|
|
name: '',
|
|
email: '',
|
|
bio: '',
|
|
});
|
|
|
|
useEffect(() => {
|
|
loadProfile();
|
|
}, []);
|
|
|
|
const loadProfile = async () => {
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
const userData = await apiService.getUserProfile();
|
|
setProfile(userData);
|
|
setFormData({
|
|
name: userData.name || '',
|
|
email: userData.email || '',
|
|
bio: userData.bio || '',
|
|
});
|
|
} catch (err) {
|
|
setError('Failed to load profile data. Please try again.');
|
|
console.error(err);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleInputChange = (field, value) => {
|
|
setFormData({
|
|
...formData,
|
|
[field]: value
|
|
});
|
|
};
|
|
|
|
const handleSave = async () => {
|
|
try {
|
|
setLoading(true);
|
|
await apiService.updateUserProfile(formData);
|
|
setProfile({
|
|
...profile,
|
|
...formData
|
|
});
|
|
setIsEditing(false);
|
|
Alert.alert('Success', 'Profile updated successfully!');
|
|
} catch (err) {
|
|
Alert.alert('Error', 'Failed to update profile. Please try again.');
|
|
console.error(err);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleCancel = () => {
|
|
setFormData({
|
|
name: profile.name || '',
|
|
email: profile.email || '',
|
|
bio: profile.bio || '',
|
|
});
|
|
setIsEditing(false);
|
|
};
|
|
|
|
if (loading && !profile) {
|
|
return (
|
|
<View style={styles.centeredContainer}>
|
|
<ActivityIndicator size="large" color={Colors.primary} />
|
|
</View>
|
|
);
|
|
}
|
|
|
|
if (error && !profile) {
|
|
return (
|
|
<View style={styles.centeredContainer}>
|
|
<Text style={styles.errorText}>{error}</Text>
|
|
<TouchableOpacity style={styles.retryButton} onPress={loadProfile}>
|
|
<Text style={styles.retryButtonText}>Retry</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<ScrollView style={styles.container}>
|
|
<Stack.Screen
|
|
options={{
|
|
title: 'Profile',
|
|
headerShown: true,
|
|
headerRight: () => (
|
|
<TouchableOpacity
|
|
onPress={isEditing ? handleSave : () => setIsEditing(true)}
|
|
disabled={loading}
|
|
>
|
|
<Text style={styles.headerButton}>
|
|
{isEditing ? 'Save' : 'Edit'}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
),
|
|
}}
|
|
/>
|
|
|
|
{loading && (
|
|
<ActivityIndicator
|
|
size="small"
|
|
color={Colors.primary}
|
|
style={styles.loadingIndicator}
|
|
/>
|
|
)}
|
|
|
|
<View style={styles.profileHeader}>
|
|
<Image
|
|
source={{ uri: profile?.avatar || 'https://via.placeholder.com/150' }}
|
|
style={styles.avatar}
|
|
/>
|
|
<View style={styles.profileInfo}>
|
|
{isEditing ? (
|
|
<TextInput
|
|
style={styles.nameInput}
|
|
value={formData.name}
|
|
onChangeText={(text) => handleInputChange('name', text)}
|
|
placeholder="Name"
|
|
/>
|
|
) : (
|
|
<Text style={styles.name}>{profile?.name}</Text>
|
|
)}
|
|
</View>
|
|
</View>
|
|
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>Email</Text>
|
|
{isEditing ? (
|
|
<TextInput
|
|
style={styles.input}
|
|
value={formData.email}
|
|
onChangeText={(text) => handleInputChange('email', text)}
|
|
keyboardType="email-address"
|
|
/>
|
|
) : (
|
|
<Text style={styles.sectionContent}>{profile?.email}</Text>
|
|
)}
|
|
</View>
|
|
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>Bio</Text>
|
|
{isEditing ? (
|
|
<TextInput
|
|
style={[styles.input, styles.bioInput]}
|
|
value={formData.bio}
|
|
onChangeText={(text) => handleInputChange('bio', text)}
|
|
multiline
|
|
numberOfLines={4}
|
|
/>
|
|
) : (
|
|
<Text style={styles.sectionContent}>{profile?.bio || 'No bio provided'}</Text>
|
|
)}
|
|
</View>
|
|
|
|
{isEditing && (
|
|
<TouchableOpacity
|
|
style={styles.cancelButton}
|
|
onPress={handleCancel}
|
|
>
|
|
<Text style={styles.cancelButtonText}>Cancel</Text>
|
|
</TouchableOpacity>
|
|
)}
|
|
|
|
<View style={styles.statsSection}>
|
|
<Text style={styles.sectionTitle}>Your Stats</Text>
|
|
<View style={styles.statsContainer}>
|
|
<View style={styles.statBox}>
|
|
<Text style={styles.statNumber}>{profile?.stats?.eventsCreated || 0}</Text>
|
|
<Text style={styles.statLabel}>Events Created</Text>
|
|
</View>
|
|
<View style={styles.statBox}>
|
|
<Text style={styles.statNumber}>{profile?.stats?.eventsAttended || 0}</Text>
|
|
<Text style={styles.statLabel}>Events Attended</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</ScrollView>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: Colors.background,
|
|
},
|
|
centeredContainer: {
|
|
flex: 1,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
padding: 20,
|
|
},
|
|
loadingIndicator: {
|
|
marginTop: 10,
|
|
},
|
|
headerButton: {
|
|
color: Colors.primary,
|
|
fontSize: 16,
|
|
fontWeight: 'bold',
|
|
marginRight: 15,
|
|
},
|
|
profileHeader: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
padding: 20,
|
|
backgroundColor: Colors.card,
|
|
borderBottomWidth: 1,
|
|
borderBottomColor: Colors.border,
|
|
},
|
|
avatar: {
|
|
width: 80,
|
|
height: 80,
|
|
borderRadius: 40,
|
|
marginRight: 20,
|
|
},
|
|
profileInfo: {
|
|
flex: 1,
|
|
},
|
|
name: {
|
|
fontSize: 22,
|
|
fontWeight: 'bold',
|
|
},
|
|
nameInput: {
|
|
fontSize: 22,
|
|
fontWeight: 'bold',
|
|
padding: 5,
|
|
borderBottomWidth: 1,
|
|
borderBottomColor: Colors.primary,
|
|
},
|
|
section: {
|
|
padding: 20,
|
|
backgroundColor: Colors.card,
|
|
marginBottom: 10,
|
|
},
|
|
sectionTitle: {
|
|
fontSize: 16,
|
|
color: 'gray',
|
|
marginBottom: 8,
|
|
},
|
|
sectionContent: {
|
|
fontSize: 16,
|
|
},
|
|
input: {
|
|
padding: 10,
|
|
borderWidth: 1,
|
|
borderColor: Colors.border,
|
|
borderRadius: 5,
|
|
fontSize: 16,
|
|
},
|
|
bioInput: {
|
|
height: 100,
|
|
textAlignVertical: 'top',
|
|
},
|
|
cancelButton: {
|
|
backgroundColor: Colors.background,
|
|
padding: 15,
|
|
alignItems: 'center',
|
|
borderWidth: 1,
|
|
borderColor: Colors.error,
|
|
marginHorizontal: 20,
|
|
borderRadius: 5,
|
|
marginBottom: 20,
|
|
},
|
|
cancelButtonText: {
|
|
color: Colors.error,
|
|
fontWeight: 'bold',
|
|
},
|
|
errorText: {
|
|
color: Colors.error,
|
|
textAlign: 'center',
|
|
marginBottom: 20,
|
|
},
|
|
retryButton: {
|
|
backgroundColor: Colors.primary,
|
|
paddingVertical: 10,
|
|
paddingHorizontal: 20,
|
|
borderRadius: 8,
|
|
},
|
|
retryButtonText: {
|
|
color: 'white',
|
|
fontWeight: 'bold',
|
|
},
|
|
statsSection: {
|
|
padding: 20,
|
|
backgroundColor: Colors.card,
|
|
marginBottom: 30,
|
|
},
|
|
statsContainer: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-around',
|
|
marginTop: 10,
|
|
},
|
|
statBox: {
|
|
alignItems: 'center',
|
|
padding: 15,
|
|
backgroundColor: Colors.background,
|
|
borderRadius: 10,
|
|
width: '45%',
|
|
},
|
|
statNumber: {
|
|
fontSize: 24,
|
|
fontWeight: 'bold',
|
|
color: Colors.primary,
|
|
},
|
|
statLabel: {
|
|
marginTop: 5,
|
|
color: 'gray',
|
|
},
|
|
});
|