diff --git a/app/(tabs)/settings/_index.tsx b/app/(tabs)/settings/_index.tsx index d12e21a..91fada9 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} + returnKeyType='send' + /> - - - - - - + + + + + ); }; - export default SettingsScreen; const styles = StyleSheet.create({ @@ -58,32 +191,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/index.tsx b/app/(tabs)/settings/index.tsx index 91fada9..d12e21a 100644 --- a/app/(tabs)/settings/index.tsx +++ b/app/(tabs)/settings/index.tsx @@ -1,114 +1,11 @@ -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 { 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 [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 ( - - - - ); - } + const router = useRouter(); return ( { } > - - - - {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} - returnKeyType='send' - /> + + router.push('/settings/profile')} + > + + + Profile Settings + Update profile information or sign out. - - - - - + + + + + + ); }; + export default SettingsScreen; const styles = StyleSheet.create({ @@ -191,51 +58,32 @@ const styles = StyleSheet.create({ lineHeight: 64, fontWeight: 'bold', }, - scrollContainer: { - flexGrow: 1, + section: { + marginVertical: 8, + borderRadius: 10, + overflow: 'hidden', }, - container: { - flex: 1, + settingItem: { + flexDirection: 'row', + alignItems: 'center', padding: 16, - alignItems: 'center', + backgroundColor: 'rgba(200, 200, 200, 0.1)', + marginBottom: 1, }, - loadingContainer: { + icon: { + marginRight: 16, + }, + settingContent: { + backgroundColor: 'transparent', flex: 1, - justifyContent: 'center', - alignItems: 'center', }, - formSection: { - marginBottom: 20, - }, - label: { - marginBottom: 8, - fontSize: 16, + settingTitle: { + fontSize: 17, fontWeight: '500', }, - 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, + settingSubtitle: { fontSize: 14, - opacity: 0.7, - } + opacity: 0.6, + marginTop: 4, + }, }); diff --git a/app/(tabs)/settings/profile.tsx b/app/(tabs)/settings/profile.tsx index 1e1ccab..f4e080f 100644 --- a/app/(tabs)/settings/profile.tsx +++ b/app/(tabs)/settings/profile.tsx @@ -1,10 +1,8 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { StyleSheet, Alert, ActivityIndicator, - SafeAreaView, - ScrollView, } from 'react-native'; import { supabase } from '@/lib/supabase'; import { ThemedView, ThemedText, ThemedTextButton, ThemedTextInput } from '@/components/theme'; @@ -109,60 +107,53 @@ const ProfileScreen = () => { } return ( - - - - - - - {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} - returnKeyType='done' - /> - - - - - - + + - + + {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} + returnKeyType='done' + /> + + + + + + ); }; export default ProfileScreen; diff --git a/components/auth/Auth.tsx b/components/auth/Auth.tsx index f198911..f581e72 100644 --- a/components/auth/Auth.tsx +++ b/components/auth/Auth.tsx @@ -81,6 +81,7 @@ const Auth = () => { setFullName(text)} value={full_name} @@ -90,6 +91,7 @@ const Auth = () => { setEmail(text)} value={email} @@ -99,6 +101,7 @@ const Auth = () => { setPassword(text)} value={password} diff --git a/components/default/ParallaxScrollView.tsx b/components/default/ParallaxScrollView.tsx index 8667e7e..2ee0f4f 100644 --- a/components/default/ParallaxScrollView.tsx +++ b/components/default/ParallaxScrollView.tsx @@ -55,6 +55,8 @@ const ParallaxScrollView = ({ scrollEventThrottle={16} scrollIndicatorInsets={{ bottom }} contentContainerStyle={{ paddingBottom: bottom }} + keyboardShouldPersistTaps='handled' + keyboardDismissMode='interactive' > = ({ color: Colors[scheme].text, backgroundColor: Colors[scheme].background, fontSize, + lineHeight: fontSize * 1.5, }, style, ]}