diff --git a/app/(tabs)/home.tsx b/app/(tabs)/home.tsx index e9d9502..1ac4897 100644 --- a/app/(tabs)/home.tsx +++ b/app/(tabs)/home.tsx @@ -1,14 +1,17 @@ -import { IconSymbol } from "@/components/ui/IconSymbol"; -import { getFoods, insertGenAIResult } from "@/services/data/foods"; -import { uploadImageToSupabase } from "@/services/data/imageUpload"; -import { callGenAIonImage } from "@/services/gemini"; -import { supabase } from "@/services/supabase"; -import { Feather, FontAwesome, Ionicons } from "@expo/vector-icons"; -import { useQuery } from "@tanstack/react-query"; -import * as FileSystem from "expo-file-system"; -import * as ImagePicker from "expo-image-picker"; -import { router } from "expo-router"; -import React, { useMemo, useState } from "react"; +"use client" + +import { IconSymbol } from "@/components/ui/IconSymbol" +import { getFoods, insertGenAIResult } from "@/services/data/foods" +import { uploadImageToSupabase } from "@/services/data/imageUpload" +import { getProfile } from "@/services/data/profile" +import { callGenAIonImage } from "@/services/gemini" +import { supabase } from "@/services/supabase" +import { Feather, FontAwesome, Ionicons } from "@expo/vector-icons" +import { useQuery } from "@tanstack/react-query" +import * as FileSystem from "expo-file-system" +import * as ImagePicker from "expo-image-picker" +import { router } from "expo-router" +import { useMemo, useState, useEffect } from "react" import { Alert, Image, @@ -19,71 +22,104 @@ import { TextInput, TouchableOpacity, View, -} from "react-native"; + ActivityIndicator, +} from "react-native" const useFoodsQuery = () => { return useQuery({ queryKey: ["highlight-foods"], queryFn: async () => { - const { data, error } = await getFoods(undefined, true, undefined, 4); - if (error) throw error; - return data || []; + const { data, error } = await getFoods(undefined, true, undefined, 4) + if (error) throw error + return data || [] }, staleTime: 1000 * 60 * 5, - }); -}; + }) +} -const runImagePipeline = async ( - imageBase64: string, - imageType: string, - userId: string -) => { - const imageUri = await uploadImageToSupabase(imageBase64, imageType, userId); - const genAIResult = await callGenAIonImage(imageUri); - if (genAIResult.error) throw genAIResult.error; - const { data: genAIResultData } = genAIResult; - if (!genAIResultData) throw new Error("GenAI result is null"); - await insertGenAIResult(genAIResultData, userId, imageUri); -}; +const useUserProfile = () => { + const [userId, setUserId] = useState(null) + const [isLoadingUserId, setIsLoadingUserId] = useState(true) -const processImage = async ( - asset: ImagePicker.ImagePickerAsset, - userId: string -) => { + // Get current user ID + useEffect(() => { + const fetchUserId = async () => { + try { + const { data, error } = await supabase.auth.getUser() + if (error) throw error + setUserId(data?.user?.id || null) + } catch (error) { + console.error("Error fetching user:", error) + } finally { + setIsLoadingUserId(false) + } + } + + fetchUserId() + }, []) + + // Fetch user profile data + const { + data: profileData, + isLoading: isLoadingProfile, + error: profileError, + } = useQuery({ + queryKey: ["profile", userId], + queryFn: async () => { + if (!userId) throw new Error("No user id") + return getProfile(userId) + }, + enabled: !!userId, + staleTime: 1000 * 60 * 5, // 5 minutes + }) + + return { + userId, + profileData: profileData?.data, + isLoading: isLoadingUserId || isLoadingProfile, + error: profileError, + } +} + +const runImagePipeline = async (imageBase64: string, imageType: string, userId: string) => { + const imageUri = await uploadImageToSupabase(imageBase64, imageType, userId) + const genAIResult = await callGenAIonImage(imageUri) + if (genAIResult.error) throw genAIResult.error + const { data: genAIResultData } = genAIResult + if (!genAIResultData) throw new Error("GenAI result is null") + await insertGenAIResult(genAIResultData, userId, imageUri) +} + +const processImage = async (asset: ImagePicker.ImagePickerAsset, userId: string) => { const base64 = await FileSystem.readAsStringAsync(asset.uri, { encoding: "base64", - }); - const imageType = asset.mimeType || "image/jpeg"; - await runImagePipeline(base64, imageType, userId); -}; + }) + const imageType = asset.mimeType || "image/jpeg" + await runImagePipeline(base64, imageType, userId) +} const navigateToFoodDetail = (foodId: string) => { - router.push({ pathname: "/recipe-detail", params: { id: foodId } }); -}; + router.push({ pathname: "/recipe-detail", params: { id: foodId } }) +} const handleImageSelection = async ( - pickerFn: - | typeof ImagePicker.launchCameraAsync - | typeof ImagePicker.launchImageLibraryAsync + pickerFn: typeof ImagePicker.launchCameraAsync | typeof ImagePicker.launchImageLibraryAsync, ) => { const result = await pickerFn({ - mediaTypes: ["images"], + mediaTypes: ImagePicker.MediaTypeOptions.Images, allowsEditing: true, aspect: [1, 1], quality: 1, - }); + }) if (!result.canceled) { try { - const { data, error } = await supabase.auth.getUser(); - if (error || !data?.user?.id) throw new Error("Cannot get user id"); - const userId = data.user.id; - await processImage(result.assets[0], userId); + const { data, error } = await supabase.auth.getUser() + if (error || !data?.user?.id) throw new Error("Cannot get user id") + const userId = data.user.id + await processImage(result.assets[0], userId) } catch (err) { - Alert.alert( - "Image Processing Failed", - (err as Error).message || "Unknown error" - ); + Alert.alert("Image Processing Failed", (err as Error).message || "Unknown error") } router.push({ pathname: "/recipe-detail", @@ -91,28 +127,38 @@ const handleImageSelection = async ( title: "My New Recipe", image: result.assets[0].uri, }, - }); + }) } -}; +} export default function HomeScreen() { - const [searchQuery, setSearchQuery] = useState(""); - const { data: foodsData = [], isLoading, error } = useFoodsQuery(); + const [searchQuery, setSearchQuery] = useState("") + const { data: foodsData = [], isLoading: isLoadingFoods, error: foodsError } = useFoodsQuery() + const { profileData, isLoading: isLoadingProfile, userId } = useUserProfile() const filteredFoods = useMemo(() => { return searchQuery - ? foodsData.filter((food) => - food.name.toLowerCase().includes(searchQuery.toLowerCase()) - ) - : foodsData; - }, [foodsData, searchQuery]); + ? foodsData.filter((food) => food.name.toLowerCase().includes(searchQuery.toLowerCase())) + : foodsData + }, [foodsData, searchQuery]) + + // Get username or fallback to a default greeting + const username = profileData?.username || profileData?.full_name || "Chef" + const greeting = `Hi! ${username}` return ( - - Hi! Mr. Chef + + {isLoadingProfile ? ( + + Hi! + + + ) : ( + {greeting} + )} @@ -123,132 +169,105 @@ export default function HomeScreen() { showsVerticalScrollIndicator={false} contentContainerStyle={{ paddingBottom: 100 }} > - - - Show your dishes - - + {/* Main content container with consistent padding */} + + {/* "Show your dishes" section */} + + + Show your dishes + + - - - - + + + + + - - { - const { status } = - await ImagePicker.requestCameraPermissionsAsync(); - if (status !== "granted") { - Alert.alert( - "Permission needed", - "Please grant camera permissions." - ); - return; - } - await handleImageSelection(ImagePicker.launchCameraAsync); - }} - > - - - From Camera - - Straight from Camera - - - - { - const { status } = - await ImagePicker.requestMediaLibraryPermissionsAsync(); - if (status !== "granted") { - Alert.alert( - "Permission needed", - "Please grant gallery permissions." - ); - return; - } - await handleImageSelection(ImagePicker.launchImageLibraryAsync); - }} - > - - - From Gallery - - Straight from Gallery - - - + {/* Upload feature section */} + + + { + const { status } = await ImagePicker.requestCameraPermissionsAsync() + if (status !== "granted") { + Alert.alert("Permission needed", "Please grant camera permissions.") + return + } + await handleImageSelection(ImagePicker.launchCameraAsync) + }} + > + + + From Camera + Straight from Camera + + + { + const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync() + if (status !== "granted") { + Alert.alert("Permission needed", "Please grant gallery permissions.") + return + } + await handleImageSelection(ImagePicker.launchImageLibraryAsync) + }} + > + + + From Gallery + Straight from Gallery + + + - + {/* Highlights section */} + - Highlights + Highlights - {isLoading ? ( - - Loading highlights... - - ) : error ? ( - - Failed to load highlights - + {isLoadingFoods ? ( + Loading highlights... + ) : foodsError ? ( + Failed to load highlights ) : filteredFoods.length === 0 ? ( - - No highlights available - + No highlights available ) : ( {filteredFoods.map((food, idx) => ( navigateToFoodDetail(food.id)} > {food.image_url ? ( - + ) : ( - + No Image )} - - + + {food.name} - + {food.description || "No description"} - {food.time_to_cook_minutes - ? `${food.time_to_cook_minutes} min` - : "-"} + {food.time_to_cook_minutes ? `${food.time_to_cook_minutes} min` : "-"} @@ -259,9 +278,10 @@ export default function HomeScreen() { )} + {/* Extra space at bottom */} - ); + ) } diff --git a/app/(tabs)/profile.tsx b/app/(tabs)/profile.tsx index 5332c71..9c3be35 100644 --- a/app/(tabs)/profile.tsx +++ b/app/(tabs)/profile.tsx @@ -6,7 +6,7 @@ import { getBookmarkedPosts } from "@/services/data/bookmarks" import { getLikedPosts } from "@/services/data/likes" import { getProfile, updateProfile } from "@/services/data/profile" import { supabase } from "@/services/supabase" -import { useIsFocused, useNavigation } from "@react-navigation/native" +import { useIsFocused } from "@react-navigation/native" import { useQuery, useQueryClient } from "@tanstack/react-query" import * as ImagePicker from "expo-image-picker" import { useEffect, useState } from "react" @@ -23,6 +23,7 @@ import { } from "react-native" import { SafeAreaView } from "react-native-safe-area-context" import uuid from "react-native-uuid" +import { router } from "expo-router" // Define the Food type based on your database structure type Food = { @@ -44,7 +45,6 @@ export default function ProfileScreen() { const { isAuthenticated } = useAuth() const isFocused = useIsFocused() const queryClient = useQueryClient() - const navigation = useNavigation() const { data: userData, @@ -125,10 +125,9 @@ export default function ProfileScreen() { staleTime: 1000 * 60, // 1 minute }) - // Navigate to post detail + // Navigate to post detail using Expo Router instead of navigation API const handleFoodPress = (foodId: number) => { - // @ts-ignore - Navigation typing might be different in your app - navigation.navigate("post-detail", { id: foodId }) + router.push(`/post-detail/${foodId}`) } // Refetch data when tab changes @@ -237,7 +236,7 @@ export default function ProfileScreen() { if (isUserLoading) { return ( - + ) @@ -245,8 +244,8 @@ export default function ProfileScreen() { if (userError) { return ( - - {userError.message || "Failed to load user data."} + + {userError.message || "Failed to load user data."} ) } @@ -268,12 +267,12 @@ export default function ProfileScreen() { {isLoading ? ( ) : error ? ( - {error.message || error.toString()} + {error.message || error.toString()} ) : ( - {profileData?.data?.username ?? "-"} + {profileData?.data?.username ?? "-"} )} { setEditUsername(profileData?.data?.username ?? "") setEditImage(profileData?.data?.avatar_url ?? null) @@ -281,49 +280,49 @@ export default function ProfileScreen() { setModalVisible(true) }} > - Edit + Edit {/* Edit Modal */} setModalVisible(false)}> - - - Edit Profile + + + Edit Profile Change Photo Username - {editError && {editError}} + {editError && {editError}} setModalVisible(false)} disabled={editLoading} > - Cancel + Cancel {editLoading ? ( ) : ( - Save + Save )} @@ -347,23 +346,23 @@ export default function ProfileScreen() { {/* Tab Content */} {isActiveLoading ? ( - + ) : activeError ? ( - - {activeError.message || "Failed to load data"} + + {activeError.message || "Failed to load data"} ) : !activeData?.data?.length ? ( - - No items found + + No items found ) : ( {activeData.data.map((item: Food) => ( handleFoodPress(item.id)} activeOpacity={0.7} > @@ -371,7 +370,7 @@ export default function ProfileScreen() { source={item.image_url ? { uri: item.image_url } : require("@/assets/images/placeholder-food.jpg")} className="w-full h-[120px] rounded-lg" /> - + {item.name} diff --git a/app/post-detail/[id].tsx b/app/post-detail/[id].tsx index f2f233c..152c5b2 100644 --- a/app/post-detail/[id].tsx +++ b/app/post-detail/[id].tsx @@ -504,7 +504,7 @@ export default function PostDetailScreen() { return ( {/* Fixed Header */} - + router.back()} @@ -716,9 +716,6 @@ export default function PostDetailScreen() { - - {/* Separator */} - )) ) : (