diff --git a/app/(tabs)/settings/_index.tsx b/app/(tabs)/settings/_index.tsx new file mode 100644 index 0000000..d12e21a --- /dev/null +++ b/app/(tabs)/settings/_index.tsx @@ -0,0 +1,89 @@ +import { StyleSheet, TouchableOpacity } from 'react-native'; +import { useRouter } from 'expo-router'; +import ParallaxScrollView from '@/components/default/ParallaxScrollView'; +import { ThemedText, ThemedTextButton, ThemedView } from '@/components/theme'; +import { IconSymbol } from '@/components/ui/IconSymbol'; + +const SettingsScreen = () => { + const router = useRouter(); + + return ( + + } + headerTitle={ + + Settings + + } + > + + router.push('/settings/profile')} + > + + + Profile Settings + Update profile information or sign out. + + + + + + + + + + ); +}; + +export default SettingsScreen; + +const styles = StyleSheet.create({ + headerImage: { + color: '#808080', + bottom: 6, + left: 38, + position: 'absolute', + }, + headerTitle: { + position: 'absolute', + bottom: 20, + left: 16, + right: 0, + textAlign: 'center', + fontSize: 48, + lineHeight: 64, + fontWeight: 'bold', + }, + section: { + marginVertical: 8, + borderRadius: 10, + overflow: 'hidden', + }, + settingItem: { + flexDirection: 'row', + alignItems: 'center', + padding: 16, + backgroundColor: 'rgba(200, 200, 200, 0.1)', + marginBottom: 1, + }, + icon: { + marginRight: 16, + }, + settingContent: { + backgroundColor: 'transparent', + flex: 1, + }, + settingTitle: { + fontSize: 17, + fontWeight: '500', + }, + settingSubtitle: { + fontSize: 14, + opacity: 0.6, + marginTop: 4, + }, +}); diff --git a/app/(tabs)/settings/index.tsx b/app/(tabs)/settings/index.tsx index d12e21a..b0e1a92 100644 --- a/app/(tabs)/settings/index.tsx +++ b/app/(tabs)/settings/index.tsx @@ -1,11 +1,114 @@ -import { StyleSheet, TouchableOpacity } from 'react-native'; -import { useRouter } from 'expo-router'; +import React, { useState, useEffect } from 'react'; +import { + StyleSheet, + Alert, + ActivityIndicator, + SafeAreaView, + ScrollView, +} from 'react-native'; +import { supabase } from '@/lib/supabase'; +import { ThemedView, ThemedText, ThemedTextButton, ThemedTextInput } from '@/components/theme'; +import ProfileAvatar from '@/components/auth/Profile_Avatar'; +import LogoutButton from '@/components/auth/Logout_Button'; +import { useFocusEffect } from '@react-navigation/native'; import ParallaxScrollView from '@/components/default/ParallaxScrollView'; -import { ThemedText, ThemedTextButton, ThemedView } from '@/components/theme'; import { IconSymbol } from '@/components/ui/IconSymbol'; const SettingsScreen = () => { - const router = useRouter(); + const [loading, setLoading] = useState(true); + const [updating, setUpdating] = useState(false); + const [profile, setProfile] = useState({ + full_name: '', + email: '', + avatar_url: null, + provider: '' + }); + + // Fetch profile when screen comes into focus + useFocusEffect( + React.useCallback(() => { + fetchUserProfile(); + }, []) + ); + + const fetchUserProfile = async () => { + setLoading(true); + try { + const { data: { user } } = await supabase.auth.getUser(); + + if (!user) { + throw new Error('Not authenticated'); + } + + const { data, error } = await supabase + .from('profiles') + .select('*') + .eq('id', user.id) + .single(); + + if (error) throw error; + + if (data) { + setProfile({ + full_name: data.full_name || '', + email: data.email || '', + avatar_url: data.avatar_url, + provider: data.provider || '' + }); + } + } catch (error) { + console.error('Error fetching profile:', error); + Alert.alert('Error', 'Failed to load profile information'); + } finally { + setLoading(false); + } + }; + + const updateProfile = async () => { + setUpdating(true); + try { + const { data: { user } } = await supabase.auth.getUser(); + + if (!user) throw new Error('Not authenticated'); + + // Validate input + if (!profile.full_name.trim()) { + Alert.alert('Error', 'Please enter your full name'); + return; + } + + const updates = { + id: user.id, + full_name: profile.full_name.trim(), + updated_at: new Date(), + }; + + const { error } = await supabase + .from('profiles') + .upsert(updates); + + if (error) throw error; + + Alert.alert('Success', 'Profile updated successfully!'); + } catch (error) { + Alert.alert('Error', error instanceof Error ? error.message : 'Failed to update profile'); + } finally { + setUpdating(false); + } + }; + + const handleAvatarUpload = () => { + // Refresh profile data after avatar upload + fetchUserProfile(); + }; + + if (loading) { + return ( + + + + ); + } return ( { } > - - router.push('/settings/profile')} - > - - - Profile Settings - Update profile information or sign out. + + + + {profile.provider && ( + + Signed in with {profile.provider.charAt(0).toUpperCase() + profile.provider.slice(1)} + + )} + + + Name + setProfile(prev => ({ ...prev, full_name: text }))} + placeholder="Enter your full name" + style={styles.input} + editable={!updating} + autoCapitalize='words' + textContentType='name' + maxLength={50} + onSubmitEditing={updateProfile} + /> - - - - - - + + + + + ); }; - export default SettingsScreen; const styles = StyleSheet.create({ @@ -58,32 +190,51 @@ const styles = StyleSheet.create({ lineHeight: 64, fontWeight: 'bold', }, - section: { - marginVertical: 8, - borderRadius: 10, - overflow: 'hidden', + scrollContainer: { + flexGrow: 1, }, - settingItem: { - flexDirection: 'row', - alignItems: 'center', - padding: 16, - backgroundColor: 'rgba(200, 200, 200, 0.1)', - marginBottom: 1, - }, - icon: { - marginRight: 16, - }, - settingContent: { - backgroundColor: 'transparent', + container: { flex: 1, + padding: 16, + alignItems: 'center', }, - settingTitle: { - fontSize: 17, + loadingContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + formSection: { + marginBottom: 20, + }, + label: { + marginBottom: 8, + fontSize: 16, fontWeight: '500', }, - settingSubtitle: { - fontSize: 14, - opacity: 0.6, - marginTop: 4, + input: { + fontSize: 16, + paddingVertical: 12, + paddingHorizontal: 10, + borderRadius: 8, + marginBottom: 20, + width: '100%', }, + disabledInput: { + opacity: 0.7, + }, + saveButton: { + borderRadius: 8, + alignItems: 'center', + marginBottom: 10, + }, + logoutButton: { + marginTop: 30, + borderRadius: 8, + alignItems: 'center', + }, + providerText: { + marginBottom: 20, + fontSize: 14, + opacity: 0.7, + } }); diff --git a/app/(tabs)/settings/profile.tsx b/app/(tabs)/settings/profile.tsx index 8c25eb7..1e1ccab 100644 --- a/app/(tabs)/settings/profile.tsx +++ b/app/(tabs)/settings/profile.tsx @@ -1,5 +1,11 @@ import React, { useState, useEffect } from 'react'; -import { StyleSheet, Alert, ActivityIndicator, ScrollView } from 'react-native'; +import { + StyleSheet, + Alert, + ActivityIndicator, + SafeAreaView, + ScrollView, +} from 'react-native'; import { supabase } from '@/lib/supabase'; import { ThemedView, ThemedText, ThemedTextButton, ThemedTextInput } from '@/components/theme'; import ProfileAvatar from '@/components/auth/Profile_Avatar'; @@ -103,7 +109,10 @@ const ProfileScreen = () => { } return ( - + { Signed in with {profile.provider.charAt(0).toUpperCase() + profile.provider.slice(1)} )} - - - Name - setProfile(prev => ({ ...prev, full_name: text }))} - placeholder="Enter your full name" - style={styles.input} - editable={!updating} - /> - + + + Name + setProfile(prev => ({ ...prev, full_name: text }))} + placeholder="Enter your full name" + style={styles.input} + editable={!updating} + autoCapitalize='words' + textContentType='name' + maxLength={50} + returnKeyType='done' + /> + +