mirror of
https://github.com/Sosokker/chefhai.git
synced 2025-12-19 05:54:08 +01:00
feat: sorting newest and like
This commit is contained in:
parent
6c0efacb1b
commit
576914de60
@ -1,153 +1,146 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { View, Text, Image, TextInput, TouchableOpacity, FlatList, SafeAreaView, ActivityIndicator, Alert } from 'react-native';
|
||||
import { Feather, FontAwesome } from '@expo/vector-icons';
|
||||
import { router, useFocusEffect } from 'expo-router';
|
||||
import { useAuth } from '../../context/auth-context';
|
||||
import { supabase } from '../../services/supabase';
|
||||
import {
|
||||
useFoods,
|
||||
useFoodStats,
|
||||
useFoodCreators,
|
||||
"use client"
|
||||
|
||||
import React, { useState, useEffect } from "react"
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
Image,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
FlatList,
|
||||
SafeAreaView,
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
} from "react-native"
|
||||
import { Feather, FontAwesome } from "@expo/vector-icons"
|
||||
import { router, useFocusEffect } from "expo-router"
|
||||
import { useAuth } from "../../context/auth-context"
|
||||
import { supabase } from "../../services/supabase"
|
||||
import {
|
||||
useFoods,
|
||||
useFoodStats,
|
||||
useFoodCreators,
|
||||
useUserInteractions,
|
||||
useLikeMutation,
|
||||
useSaveMutation
|
||||
} from '../../hooks/use-foods';
|
||||
|
||||
// Categories for filtering
|
||||
const categories = [
|
||||
{ id: 'main', name: 'Main dish' },
|
||||
{ id: 'dessert', name: 'Dessert' },
|
||||
{ id: 'appetizer', name: 'Appetite' },
|
||||
];
|
||||
useSaveMutation,
|
||||
} from "../../hooks/use-foods"
|
||||
|
||||
// Sort options
|
||||
const sortOptions = [
|
||||
{ id: 'rating', name: 'Rating', icon: 'star' },
|
||||
{ id: 'newest', name: 'Newest', icon: 'calendar' },
|
||||
{ id: 'best', name: 'Best', icon: 'fire' },
|
||||
];
|
||||
{ id: "newest", name: "Newest", icon: "calendar" },
|
||||
{ id: "like_desc", name: "Most Liked", icon: "heart" },
|
||||
]
|
||||
|
||||
export default function ForumScreen() {
|
||||
const { isAuthenticated } = useAuth();
|
||||
const [currentUserId, setCurrentUserId] = useState<string | null>(null);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [selectedCategory, setSelectedCategory] = useState('');
|
||||
const [selectedSort, setSelectedSort] = useState('rating');
|
||||
|
||||
const { isAuthenticated } = useAuth()
|
||||
const [currentUserId, setCurrentUserId] = useState<string | null>(null)
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
const [selectedCategory, setSelectedCategory] = useState("")
|
||||
const [selectedSort, setSelectedSort] = useState("newest")
|
||||
|
||||
// Get current user ID from Supabase session
|
||||
useEffect(() => {
|
||||
async function getCurrentUser() {
|
||||
if (isAuthenticated) {
|
||||
const { data } = await supabase.auth.getSession();
|
||||
const userId = data.session?.user?.id;
|
||||
console.log('Current user ID:', userId);
|
||||
setCurrentUserId(userId || null);
|
||||
const { data } = await supabase.auth.getSession()
|
||||
const userId = data.session?.user?.id
|
||||
console.log("Current user ID:", userId)
|
||||
setCurrentUserId(userId || null)
|
||||
} else {
|
||||
setCurrentUserId(null);
|
||||
setCurrentUserId(null)
|
||||
}
|
||||
}
|
||||
|
||||
getCurrentUser();
|
||||
}, [isAuthenticated]);
|
||||
|
||||
|
||||
getCurrentUser()
|
||||
}, [isAuthenticated])
|
||||
|
||||
// Use React Query hooks
|
||||
const {
|
||||
data: foods = [],
|
||||
const {
|
||||
data: foods = [],
|
||||
isLoading: isLoadingFoods,
|
||||
refetch: refetchFoods
|
||||
} = useFoods(selectedCategory, searchQuery, selectedSort);
|
||||
|
||||
const foodIds = foods.map(food => food.id);
|
||||
|
||||
const {
|
||||
data: foodStats = {},
|
||||
isLoading: isLoadingStats
|
||||
} = useFoodStats(foodIds);
|
||||
|
||||
const creatorIds = foods
|
||||
.filter(food => food.created_by)
|
||||
.map(food => food.created_by as string);
|
||||
|
||||
const {
|
||||
data: foodCreators = {},
|
||||
isLoading: isLoadingCreators
|
||||
} = useFoodCreators(creatorIds);
|
||||
|
||||
const {
|
||||
data: userInteractions = {},
|
||||
isLoading: isLoadingInteractions
|
||||
} = useUserInteractions(foodIds, currentUserId);
|
||||
|
||||
const likeMutation = useLikeMutation();
|
||||
const saveMutation = useSaveMutation();
|
||||
|
||||
refetch: refetchFoods,
|
||||
} = useFoods(selectedCategory, searchQuery, selectedSort)
|
||||
|
||||
const foodIds = foods.map((food) => food.id)
|
||||
|
||||
const { data: foodStats = {}, isLoading: isLoadingStats } = useFoodStats(foodIds)
|
||||
|
||||
const creatorIds = foods.filter((food) => food.created_by).map((food) => food.created_by as string)
|
||||
|
||||
const { data: foodCreators = {}, isLoading: isLoadingCreators } = useFoodCreators(creatorIds)
|
||||
|
||||
const { data: userInteractions = {}, isLoading: isLoadingInteractions } = useUserInteractions(foodIds, currentUserId)
|
||||
|
||||
const likeMutation = useLikeMutation()
|
||||
const saveMutation = useSaveMutation()
|
||||
|
||||
// Refetch data when the screen comes into focus
|
||||
useFocusEffect(
|
||||
React.useCallback(() => {
|
||||
refetchFoods();
|
||||
}, [refetchFoods])
|
||||
);
|
||||
|
||||
refetchFoods()
|
||||
}, [refetchFoods]),
|
||||
)
|
||||
|
||||
const handleSearch = (text: string) => {
|
||||
setSearchQuery(text);
|
||||
};
|
||||
|
||||
setSearchQuery(text)
|
||||
}
|
||||
|
||||
const navigateToPostDetail = (food: { id: string }) => {
|
||||
router.push(`/post-detail/${food.id}`);
|
||||
};
|
||||
|
||||
router.push(`/post-detail/${food.id}`)
|
||||
}
|
||||
|
||||
const handleLike = async (food: { id: string }) => {
|
||||
if (!isAuthenticated || !currentUserId) {
|
||||
Alert.alert('Authentication Required', 'Please log in to like posts.');
|
||||
return;
|
||||
Alert.alert("Authentication Required", "Please log in to like posts.")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const isLiked = userInteractions[food.id]?.liked || false;
|
||||
|
||||
const isLiked = userInteractions[food.id]?.liked || false
|
||||
|
||||
likeMutation.mutate({
|
||||
foodId: food.id,
|
||||
userId: currentUserId,
|
||||
isLiked
|
||||
});
|
||||
isLiked,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error toggling like:', error);
|
||||
Alert.alert('Error', 'Failed to update like. Please try again.');
|
||||
console.error("Error toggling like:", error)
|
||||
Alert.alert("Error", "Failed to update like. Please try again.")
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
const handleSave = async (food: { id: string }) => {
|
||||
if (!isAuthenticated || !currentUserId) {
|
||||
Alert.alert('Authentication Required', 'Please log in to save posts.');
|
||||
return;
|
||||
Alert.alert("Authentication Required", "Please log in to save posts.")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const isSaved = userInteractions[food.id]?.saved || false;
|
||||
|
||||
const isSaved = userInteractions[food.id]?.saved || false
|
||||
|
||||
saveMutation.mutate({
|
||||
foodId: food.id,
|
||||
userId: currentUserId,
|
||||
isSaved
|
||||
});
|
||||
isSaved,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error toggling save:', error);
|
||||
Alert.alert('Error', 'Failed to update save. Please try again.');
|
||||
console.error("Error toggling save:", error)
|
||||
Alert.alert("Error", "Failed to update save. Please try again.")
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
const renderFoodItem = ({ item }: { item: any }) => {
|
||||
// Get stats for this food
|
||||
const stats = foodStats[item.id] || { likes: 0, saves: 0, comments: 0 };
|
||||
|
||||
const stats = foodStats[item.id] || { likes: 0, saves: 0, comments: 0 }
|
||||
|
||||
// Get creator profile
|
||||
const creator = item.created_by ? foodCreators[item.created_by] : null;
|
||||
|
||||
const creator = item.created_by ? foodCreators[item.created_by] : null
|
||||
|
||||
// Get user interactions
|
||||
const interactions = userInteractions[item.id] || { liked: false, saved: false };
|
||||
|
||||
const interactions = userInteractions[item.id] || { liked: false, saved: false }
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
<TouchableOpacity
|
||||
className="mb-6 bg-white rounded-lg overflow-hidden shadow-sm"
|
||||
onPress={() => navigateToPostDetail(item)}
|
||||
>
|
||||
@ -157,90 +150,72 @@ export default function ForumScreen() {
|
||||
<View className="flex-row items-center">
|
||||
<View className="w-12 h-12 bg-gray-200 rounded-full overflow-hidden">
|
||||
{creator?.avatar_url ? (
|
||||
<Image
|
||||
source={{ uri: creator.avatar_url }}
|
||||
className="w-full h-full"
|
||||
/>
|
||||
<Image source={{ uri: creator.avatar_url }} className="w-full h-full" />
|
||||
) : (
|
||||
<View className="w-full h-full bg-gray-300 items-center justify-center">
|
||||
<Text className="text-base font-bold text-gray-600">
|
||||
{creator?.username?.charAt(0).toUpperCase() || '?'}
|
||||
{creator?.username?.charAt(0).toUpperCase() || "?"}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
<Text className="ml-3 text-lg font-bold">
|
||||
{creator?.username || creator?.full_name || 'Unknown Chef'}
|
||||
{creator?.username || creator?.full_name || "Unknown Chef"}
|
||||
</Text>
|
||||
</View>
|
||||
<View className="flex-row items-center">
|
||||
<Text className="text-lg font-bold mr-1">4.2</Text>
|
||||
<FontAwesome name="star" size={20} color="#ffd60a" />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
|
||||
{/* Food image */}
|
||||
<View className="rounded-lg overflow-hidden mb-4">
|
||||
<Image
|
||||
source={{ uri: item.image_url || "/placeholder.svg?height=300&width=500&query=food dish" }}
|
||||
<Image
|
||||
source={{ uri: item.image_url }}
|
||||
className="w-full h-48"
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</View>
|
||||
|
||||
|
||||
{/* Food title and description */}
|
||||
<View>
|
||||
<Text className="text-2xl font-bold mb-2">{item.name}</Text>
|
||||
<Text className="text-gray-700 mb-4">{item.description}</Text>
|
||||
</View>
|
||||
|
||||
|
||||
{/* Interaction buttons */}
|
||||
<View className="flex-row justify-between">
|
||||
<View className="flex-row items-center">
|
||||
<TouchableOpacity
|
||||
<TouchableOpacity
|
||||
className="flex-row items-center mr-6"
|
||||
onPress={(e) => {
|
||||
e.stopPropagation();
|
||||
handleLike(item);
|
||||
e.stopPropagation()
|
||||
handleLike(item)
|
||||
}}
|
||||
>
|
||||
<Feather
|
||||
name="heart"
|
||||
size={22}
|
||||
color={interactions.liked ? "#E91E63" : "#333"}
|
||||
/>
|
||||
<Feather name="heart" size={22} color={interactions.liked ? "#E91E63" : "#333"} />
|
||||
<Text className="ml-2 text-lg">{stats.likes}</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
className="flex-row items-center mr-6"
|
||||
onPress={() => navigateToPostDetail(item)}
|
||||
>
|
||||
|
||||
<TouchableOpacity className="flex-row items-center mr-6" onPress={() => navigateToPostDetail(item)}>
|
||||
<Feather name="message-circle" size={22} color="#333" />
|
||||
<Text className="ml-2 text-lg">{stats.comments}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
|
||||
<TouchableOpacity
|
||||
onPress={(e) => {
|
||||
e.stopPropagation();
|
||||
handleSave(item);
|
||||
e.stopPropagation()
|
||||
handleSave(item)
|
||||
}}
|
||||
>
|
||||
<Feather
|
||||
name="bookmark"
|
||||
size={22}
|
||||
color={interactions.saved ? "#ffd60a" : "#333"}
|
||||
/>
|
||||
<Feather name="bookmark" size={22} color={interactions.saved ? "#ffd60a" : "#333"} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
const isLoading = isLoadingFoods || isLoadingStats || isLoadingCreators || isLoadingInteractions;
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
const isLoading = isLoadingFoods || isLoadingStats || isLoadingCreators || isLoadingInteractions
|
||||
|
||||
return (
|
||||
<SafeAreaView className="flex-1 bg-white">
|
||||
{/* Search Bar */}
|
||||
@ -255,25 +230,8 @@ export default function ForumScreen() {
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Categories */}
|
||||
<View className="px-4 py-4">
|
||||
<FlatList
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
data={categories}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={({ item }) => (
|
||||
<TouchableOpacity
|
||||
className={`mr-3 px-6 py-4 rounded-lg ${selectedCategory === item.id ? 'bg-[#ffd60a]' : 'bg-[#ffd60a]'}`}
|
||||
onPress={() => setSelectedCategory(item.id === selectedCategory ? '' : item.id)}
|
||||
>
|
||||
<Text className="text-lg font-medium">{item.name}</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
|
||||
|
||||
{/* Sort Options */}
|
||||
<View className="px-4 pb-4">
|
||||
<FlatList
|
||||
@ -282,17 +240,21 @@ export default function ForumScreen() {
|
||||
data={sortOptions}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={({ item }) => (
|
||||
<TouchableOpacity
|
||||
className={`mr-3 px-6 py-3 rounded-lg flex-row items-center ${selectedSort === item.id ? 'bg-[#bb0718]' : 'bg-[#bb0718]'}`}
|
||||
<TouchableOpacity
|
||||
className={`mr-3 px-6 py-3 rounded-lg flex-row items-center ${selectedSort === item.id ? "bg-[#bb0718]" : "bg-gray-200"}`}
|
||||
onPress={() => setSelectedSort(item.id)}
|
||||
>
|
||||
<Text className="text-lg font-medium text-[#ffd60a] mr-2">{item.name}</Text>
|
||||
<Feather name={item.icon as any} size={18} color="#ffd60a" />
|
||||
<Text
|
||||
className={`text-lg font-medium ${selectedSort === item.id ? "text-[#ffd60a]" : "text-gray-800"} mr-2`}
|
||||
>
|
||||
{item.name}
|
||||
</Text>
|
||||
<Feather name={item.icon as any} size={18} color={selectedSort === item.id ? "#ffd60a" : "#333"} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
|
||||
{/* Food Posts */}
|
||||
{isLoading ? (
|
||||
<View className="flex-1 items-center justify-center">
|
||||
@ -308,5 +270,5 @@ export default function ForumScreen() {
|
||||
/>
|
||||
)}
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ -47,6 +47,21 @@ export function useFoods(category?: string, search?: string, sort?: string) {
|
||||
sortedData.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
|
||||
} else if (sort === 'best') {
|
||||
sortedData.sort((a, b) => (b.ingredient_count ?? 0) - (a.ingredient_count ?? 0));
|
||||
} else if (sort === 'like_desc') {
|
||||
// First, we need to get likes count for each food
|
||||
const likesPromises = sortedData.map(async (food) => {
|
||||
const { count } = await getLikesCount(food.id);
|
||||
return { foodId: food.id, likes: count || 0 };
|
||||
});
|
||||
|
||||
const likesData = await Promise.all(likesPromises);
|
||||
const likesMap = likesData.reduce((acc, item) => {
|
||||
acc[item.foodId] = item.likes;
|
||||
return acc;
|
||||
}, {} as Record<string, number>);
|
||||
|
||||
// Sort by likes count (high to low)
|
||||
sortedData.sort((a, b) => (likesMap[b.id] || 0) - (likesMap[a.id] || 0));
|
||||
}
|
||||
|
||||
return sortedData.map(food => ({
|
||||
@ -284,4 +299,4 @@ export function useSaveMutation() {
|
||||
queryClient.invalidateQueries({ queryKey: [queryKeys.userInteractions] });
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user