Added sentry support
This commit is contained in:
@ -5,20 +5,16 @@ import StatusList from '@/components/status/StatusList';
|
||||
|
||||
const HomeScreen = () => {
|
||||
return (
|
||||
<ParallaxScrollView
|
||||
headerImage={
|
||||
<Image source={require('@/assets/images/tech_tracker_logo.png')} style={styles.techTrackerLogo} />
|
||||
}
|
||||
headerTitle={
|
||||
<ThemedText type='title' style={styles.headerTitle}>
|
||||
Tech Tracker
|
||||
</ThemedText>
|
||||
}
|
||||
>
|
||||
<ThemedView style={styles.titleContainer}>
|
||||
<StatusList />
|
||||
</ThemedView>
|
||||
</ParallaxScrollView>
|
||||
<StatusList
|
||||
headerTitle={
|
||||
<ThemedText style={styles.headerTitle}>
|
||||
Tech Tracker
|
||||
</ThemedText>
|
||||
}
|
||||
headerImage={
|
||||
<Image source={require('@/assets/images/tech_tracker_logo.png')} style={styles.techTrackerLogo} />
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default HomeScreen;
|
||||
|
@ -1,241 +0,0 @@
|
||||
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 { 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 (
|
||||
<ThemedView style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" />
|
||||
</ThemedView>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ParallaxScrollView
|
||||
headerImage={
|
||||
<IconSymbol size={80} color='#808080' name='gear.circle' style={styles.headerImage} />
|
||||
}
|
||||
headerTitle={
|
||||
<ThemedText type='title' style={styles.headerTitle}>
|
||||
Settings
|
||||
</ThemedText>
|
||||
}
|
||||
>
|
||||
<ThemedView style={styles.container}>
|
||||
<ProfileAvatar
|
||||
url={profile.avatar_url}
|
||||
size={120}
|
||||
onUpload={handleAvatarUpload}
|
||||
disabled={updating}
|
||||
/>
|
||||
|
||||
{profile.provider && (
|
||||
<ThemedText style={styles.providerText}>
|
||||
Signed in with {profile.provider.charAt(0).toUpperCase() + profile.provider.slice(1)}
|
||||
</ThemedText>
|
||||
)}
|
||||
<SafeAreaView style={styles.formSection}>
|
||||
<ThemedView style={styles.formSection}>
|
||||
<ThemedText type='title' style={styles.label}>Name</ThemedText>
|
||||
<ThemedTextInput
|
||||
value={profile.full_name}
|
||||
onChangeText={(text) => 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'
|
||||
/>
|
||||
</ThemedView>
|
||||
</SafeAreaView>
|
||||
|
||||
<ThemedTextButton
|
||||
text={updating ? 'Saving...' : 'Save Changes'}
|
||||
onPress={updateProfile}
|
||||
disabled={updating || !profile.full_name.trim()}
|
||||
fontSize={18}
|
||||
fontWeight='semibold'
|
||||
width='90%'
|
||||
style={styles.saveButton}
|
||||
/>
|
||||
|
||||
<LogoutButton
|
||||
fontSize={18}
|
||||
fontWeight='semibold'
|
||||
width='90%'
|
||||
style={styles.logoutButton}
|
||||
/>
|
||||
</ThemedView>
|
||||
</ParallaxScrollView>
|
||||
);
|
||||
};
|
||||
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',
|
||||
},
|
||||
scrollContainer: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
padding: 16,
|
||||
alignItems: 'center',
|
||||
},
|
||||
loadingContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
formSection: {
|
||||
marginBottom: 20,
|
||||
},
|
||||
label: {
|
||||
marginBottom: 8,
|
||||
fontSize: 16,
|
||||
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,
|
||||
fontSize: 14,
|
||||
opacity: 0.7,
|
||||
}
|
||||
});
|
@ -135,6 +135,7 @@ const ProfileScreen = () => {
|
||||
placeholder="Enter your full name"
|
||||
style={styles.input}
|
||||
fontSize={20}
|
||||
height={55}
|
||||
editable={!updating}
|
||||
autoCapitalize='words'
|
||||
textContentType='name'
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
|
||||
import { useFonts } from 'expo-font';
|
||||
import { Stack } from 'expo-router';
|
||||
import { Stack, useNavigationContainerRef } from 'expo-router';
|
||||
import * as SplashScreen from 'expo-splash-screen';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
import { useEffect, useState } from 'react';
|
||||
@ -11,6 +11,22 @@ import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import { supabase } from '@/lib/supabase';
|
||||
import Auth from '@/components/auth/Auth';
|
||||
import PushNotificationManager from '@/services/PushNotificationManager';
|
||||
import * as Sentry from '@sentry/react-native';
|
||||
import { isRunningInExpoGo } from 'expo';
|
||||
|
||||
const navigationIntegration = Sentry.reactNavigationIntegration({
|
||||
enableTimeToInitialDisplay: !isRunningInExpoGo(),
|
||||
});
|
||||
|
||||
Sentry.init({
|
||||
dsn: process.env.EXPO_PUBLIC_SENTRY_DSN,
|
||||
debug: true,
|
||||
tracesSampleRate: 1.0,
|
||||
integrations: [
|
||||
navigationIntegration,
|
||||
],
|
||||
enableNativeFramesTracking: !isRunningInExpoGo(),
|
||||
});
|
||||
|
||||
// Prevent the splash screen from auto-hiding before asset loading is complete.
|
||||
SplashScreen.preventAutoHideAsync();
|
||||
@ -18,6 +34,7 @@ SplashScreen.preventAutoHideAsync();
|
||||
const RootLayout = () => {
|
||||
const scheme = useColorScheme() ?? 'dark';
|
||||
const [session, setSession] = useState<Session | null>(null);
|
||||
const ref = useNavigationContainerRef();
|
||||
|
||||
const [loaded] = useFonts({
|
||||
SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
|
||||
@ -39,6 +56,12 @@ const RootLayout = () => {
|
||||
}
|
||||
}, [loaded]);
|
||||
|
||||
useEffect(() => {
|
||||
if (ref?.current) {
|
||||
navigationIntegration.registerNavigationContainer(ref);
|
||||
}
|
||||
}, [ref])
|
||||
|
||||
if (!loaded) {
|
||||
return null;
|
||||
}
|
||||
@ -62,4 +85,4 @@ const RootLayout = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default RootLayout;
|
||||
export default Sentry.wrap(RootLayout);
|
||||
|
Reference in New Issue
Block a user