diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index 823a1ae..f23ad94 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -1,77 +1,20 @@ -import React, { useEffect, useState } from "react"; -import { StyleSheet, Alert, Image, TouchableOpacity } from "react-native"; -import { ThemedText } from "@/components/ThemedText"; +import React, { useState } from "react"; +import { StyleSheet } from "react-native"; import { ThemedView } from "@/components/ThemedView"; -import { getUserData } from "@/components/services/securestorage/UserData"; -import { Colors } from '@/constants/Colors'; -import { useColorScheme } from '@/hooks/useColorScheme'; -import * as ImagePicker from 'expo-image-picker'; - -type UserData = { - fullName: string; - appleEmail: string; - pfpURL: string; - // Add other fields as needed -}; +import UserInfo from "@/components/home/UserInfo"; +import Relationships from "@/components/home/Relationships"; const Index = () => { - const scheme = useColorScheme() ?? 'light'; - const [userData, setUserData] = useState(null); + const [profilePictureUrl, setProfilePictureUrl] = useState(null); - useEffect(() => { - const fetchUserData = async () => { - try { - const data = await getUserData(); - setUserData(data); - } catch (error) { - console.error("Error fetching user data:", error); - Alert.alert("Error", "Failed to load user data"); - } - }; - - fetchUserData(); - }, []); - - const handleUpdateProfilePicture = async () => { - const permissionResult = await ImagePicker.requestMediaLibraryPermissionsAsync(); - - if (permissionResult.granted === false) { - Alert.alert("Permission Required", "You need to grant permission to access your photos"); - return; - } - - const result = await ImagePicker.launchImageLibraryAsync({ - mediaTypes: ImagePicker.MediaTypeOptions.Images, - allowsEditing: true, - aspect: [1, 1], - quality: 1, - }); - - if (!result.canceled) { - // Here you would typically upload the image to your server - // and update the user's profile picture URL - console.log("Selected image:", result.assets[0].uri); - // For now, let's just update the local state - setUserData(prevData => prevData ? {...prevData, pfpURL: result.assets[0].uri} : null); - } + const handleProfilePictureUpdate = (newUrl: string) => { + setProfilePictureUrl(newUrl); }; return ( - {userData ? ( - - - - - {userData.fullName} - {userData.appleEmail} - - ) : ( - Loading user data... - )} + + {/* Add your relationship request button or other components here */} @@ -86,26 +29,6 @@ const styles = StyleSheet.create({ flex: 1, alignItems: 'center', }, - profileContainer: { - alignItems: 'center', - marginTop: 20, - marginBottom: 20, - }, - profilePicture: { - width: 100, - height: 100, - borderRadius: 50, - marginBottom: 10, - }, - name: { - fontSize: 24, - fontWeight: 'bold', - marginBottom: 5, - }, - email: { - fontSize: 16, - marginBottom: 20, - }, footerContainer: { flex: 1 / 3, alignItems: 'center', diff --git a/components/home/Relationships.tsx b/components/home/Relationships.tsx new file mode 100644 index 0000000..ed1228b --- /dev/null +++ b/components/home/Relationships.tsx @@ -0,0 +1,172 @@ +import React, { useEffect, useState } from 'react'; +import { Image, StyleSheet, Alert } from 'react-native'; +import { ThemedView } from '@/components/ThemedView'; +import { ThemedText } from '@/components/ThemedText'; +import { getUserData } from '@/components/services/securestorage/UserData'; +import Button from '@/components/buttons/Button'; +import { Colors } from '@/constants/Colors'; +import { useColorScheme } from '@/hooks/useColorScheme'; + +type Partner = { + id: number; + appleId: string; + appleEmail: string; + fullName: string; + pfpURL: string; + pushToken: string; + createdAt: Date; +}; + +type Relationship = { + id: number; + title: string; + status: 'pending' | 'accepted' | 'rejected'; + relationshipStartDate: Date; + createdAt: Date; +}; + +type RelationshipStatus = { + relationship: Relationship | null; + partner: Partner | null; +}; + +type UserData = { + fullName: string; + appleEmail: string; + appleId: string; + pfpURL: string; +}; + +type RelationshipProps = { + profilePictureUrl: string | null; +}; + +const Relationships: React.FC = ({ profilePictureUrl }) => { + const scheme = useColorScheme() ?? 'light'; + const [status, setStatus] = useState(null); + const [userData, setUserData] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + fetchRelationshipStatus(); + }, []); + + useEffect(() => { + if (profilePictureUrl && userData) { + setUserData(prevData => prevData ? {...prevData, pfpURL: profilePictureUrl} : null); + } + }, [profilePictureUrl]); + + const fetchRelationshipStatus = async () => { + try { + const userDataFromStorage: UserData = await getUserData(); + if (!userDataFromStorage || !userDataFromStorage.appleId) { + throw new Error('User data not found'); + } + setUserData(userDataFromStorage); + + const response = await fetch(`${process.env.EXPO_PUBLIC_API_URL}/relationships/checkStatusByAppleId?appleId=${userDataFromStorage.appleId}`, { + headers: { + 'x-api-key': process.env.EXPO_PUBLIC_API_KEY ?? '', + }, + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.message || 'Failed to fetch relationship status'); + } + + const data = await response.json(); + setStatus(data); + } catch (error) { + console.error('Error fetching relationship status:', error); + } finally { + setLoading(false); + } + }; + + if (loading) { + return Loading...; + } + + if (!status || !status.relationship?.id) { + return ( + + + + ); + } + + return ( + + {status?.relationship?.title} + + {userData && ( + + + {userData.fullName.split(' ')[0]} + + )} + {status?.partner && ( + + + {status.partner.fullName.split(' ')[0]} + + )} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + title: { + fontSize: 24, + fontWeight: 'bold', + marginBottom: 20, + }, + profileContainer: { + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + marginTop: 20, + }, + profileWrapper: { + alignItems: 'center', + marginHorizontal: 10, + }, + profilePicture: { + width: 100, + height: 100, + borderRadius: 50, + marginBottom: 10, + }, + name: { + fontSize: 12, + fontWeight: 'bold', + }, + buttonText: { + fontSize: 16, + }, +}); + +export default Relationships; diff --git a/components/home/UserInfo.tsx b/components/home/UserInfo.tsx new file mode 100644 index 0000000..0e3ed3a --- /dev/null +++ b/components/home/UserInfo.tsx @@ -0,0 +1,154 @@ +import React, { useEffect, useState } from "react"; +import { StyleSheet, Alert, Image, TouchableOpacity } from "react-native"; +import { ThemedText } from "@/components/ThemedText"; +import { ThemedView } from "@/components/ThemedView"; +import { getUserData, updateUserData } from "@/components/services/securestorage/UserData"; +import { manipulateAsync, SaveFormat } from 'expo-image-manipulator'; +import * as ImagePicker from 'expo-image-picker'; +import * as FileSystem from 'expo-file-system'; + +type UserData = { + fullName: string; + appleEmail: string; + appleId: string; + pfpURL: string; +}; + +type UserInfoProps = { + onProfilePictureUpdate: (url: string) => void; +}; + +const UserInfo: React.FC = ({ onProfilePictureUpdate }) => { + const [userData, setUserData] = useState(null); + + useEffect(() => { + const fetchUserData = async () => { + try { + const data = await getUserData(); + setUserData(data); + if (data.pfpURL) { + onProfilePictureUpdate(data.pfpURL); + } + } catch (error) { + console.error("Error fetching user data:", error); + Alert.alert("Error", "Failed to load user data"); + } + }; + + fetchUserData(); + }, [onProfilePictureUpdate]); + + const handleUpdateProfilePicture = async () => { + const permissionResult = await ImagePicker.requestMediaLibraryPermissionsAsync(); + + if (permissionResult.granted === false) { + Alert.alert("Permission Required", "You need to grant permission to access your photos"); + return; + } + + const result = await ImagePicker.launchImageLibraryAsync({ + mediaTypes: ImagePicker.MediaTypeOptions.Images, + allowsEditing: true, + aspect: [1, 1], + quality: 1, + }); + + if (!result.canceled && result.assets[0].uri) { + try { + // Manipulate the image + const manipResult = await manipulateAsync( + result.assets[0].uri, + [ + { resize: { width: 300, height: 300 } }, + // You can add more manipulations here if needed + ], + { compress: 0.7, format: SaveFormat.JPEG } + ); + + const apiUrl = `${process.env.EXPO_PUBLIC_API_URL}/users/updatePfp`; + console.log("Sending request to:", apiUrl); + + const response = await FileSystem.uploadAsync(apiUrl, manipResult.uri, { + fieldName: 'file', + httpMethod: 'POST', + uploadType: FileSystem.FileSystemUploadType.MULTIPART, + parameters: { appleId: userData?.appleId || '' }, + headers: { + 'x-api-key': process.env.EXPO_PUBLIC_API_KEY ?? '', + }, + }); + + console.log('Response status:', response.status); + console.log('Response headers:', JSON.stringify(response.headers, null, 2)); + console.log('Response body:', response.body); + + if (response.status !== 200) { + throw new Error(`Server responded with status ${response.status}: ${response.body}`); + } + + const responseData = JSON.parse(response.body); + const newPfpURL = responseData.pfpURL; + + // Update local state + setUserData(prevData => prevData ? {...prevData, pfpURL: newPfpURL} : null); + + // Update SecureStorage + await updateUserData({ pfpURL: newPfpURL }); + onProfilePictureUpdate(newPfpURL); + + } catch (error) { + console.error("Error updating profile picture:", error); + console.error("Error details:", error.message); + Alert.alert("Error", `Failed to update profile picture: ${error.message}`); + } + } + }; + + return ( + + {userData ? ( + + + + + {userData.fullName} + {userData.appleEmail} + + ) : ( + Loading user data... + )} + + ); +} + +export default UserInfo; + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + }, + profileContainer: { + alignItems: 'center', + marginTop: 20, + marginBottom: 20, + }, + profilePicture: { + width: 100, + height: 100, + borderRadius: 50, + marginBottom: 10, + }, + name: { + fontSize: 24, + fontWeight: 'bold', + marginBottom: 5, + }, + email: { + fontSize: 16, + marginBottom: 20, + }, +}); diff --git a/components/services/securestorage/UserData.tsx b/components/services/securestorage/UserData.tsx index 22ba941..d6a3138 100644 --- a/components/services/securestorage/UserData.tsx +++ b/components/services/securestorage/UserData.tsx @@ -17,3 +17,18 @@ export const getUserData = async () => { return null; } }; + +export const updateUserData = async (updatedFields: Partial) => { + try { + const currentUserData = await getUserData(); + if (currentUserData) { + const updatedUserData = { ...currentUserData, ...updatedFields }; + await saveUserData(updatedUserData); + return updatedUserData; + } + return null; + } catch (error) { + console.error('Error updating user data:', error); + return null; + } +}; diff --git a/package-lock.json b/package-lock.json index 877f25b..afd58ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,8 +15,11 @@ "expo-clipboard": "~6.0.3", "expo-constants": "~16.0.2", "expo-device": "~6.0.2", + "expo-fast-image": "^1.1.3", + "expo-file-system": "~17.0.1", "expo-font": "~12.0.9", "expo-image": "~1.13.0", + "expo-image-manipulator": "~12.0.5", "expo-image-picker": "~15.0.7", "expo-linking": "~6.3.1", "expo-location": "~17.0.1", @@ -10198,6 +10201,12 @@ "expo": "*" } }, + "node_modules/expo-fast-image": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/expo-fast-image/-/expo-fast-image-1.1.3.tgz", + "integrity": "sha512-dpHvUFJxImD+yDVJk3ubCMGSYgbewPfbsPMOpD/EP7PKDA4RurtkJZcUDF8T/0gxuxZMCldPgHup6np/hrK/MQ==", + "license": "ISC" + }, "node_modules/expo-file-system": { "version": "17.0.1", "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-17.0.1.tgz", @@ -10237,6 +10246,18 @@ "expo": "*" } }, + "node_modules/expo-image-manipulator": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/expo-image-manipulator/-/expo-image-manipulator-12.0.5.tgz", + "integrity": "sha512-zJ8yINjckYw/yfoSuICt4yJ9xr112+W9e5QVXwK3nCAHr7sv45RQ5sxte0qppf594TPl+UoV6Tjim7WpoKipRQ==", + "license": "MIT", + "dependencies": { + "expo-image-loader": "~4.7.0" + }, + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-image-picker": { "version": "15.0.7", "resolved": "https://registry.npmjs.org/expo-image-picker/-/expo-image-picker-15.0.7.tgz", diff --git a/package.json b/package.json index b522181..d735d1c 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,11 @@ "expo-clipboard": "~6.0.3", "expo-constants": "~16.0.2", "expo-device": "~6.0.2", + "expo-fast-image": "^1.1.3", + "expo-file-system": "~17.0.1", "expo-font": "~12.0.9", "expo-image": "~1.13.0", + "expo-image-manipulator": "~12.0.5", "expo-image-picker": "~15.0.7", "expo-linking": "~6.3.1", "expo-location": "~17.0.1", @@ -41,11 +44,11 @@ "react-native-gesture-handler": "~2.16.1", "react-native-get-random-values": "~1.11.0", "react-native-gifted-chat": "^2.6.4", + "react-native-maps": "1.14.0", "react-native-reanimated": "~3.10.1", "react-native-safe-area-context": "4.10.5", "react-native-screens": "3.31.1", - "react-native-web": "~0.19.10", - "react-native-maps": "1.14.0" + "react-native-web": "~0.19.10" }, "devDependencies": { "@babel/core": "^7.20.0",