mirror of
https://github.com/Sosokker/chefhai.git
synced 2025-12-19 05:54:08 +01:00
feat: add recipes page
This commit is contained in:
parent
8c6252b19f
commit
9125ca19ba
@ -28,7 +28,7 @@ const useFoodsQuery = () => {
|
||||
return useQuery({
|
||||
queryKey: ["highlight-foods"],
|
||||
queryFn: async () => {
|
||||
const { data, error } = await getFoods(undefined, true, undefined, 4);
|
||||
const { data, error } = await getFoods(undefined, true, undefined, 3);
|
||||
if (error) throw error;
|
||||
return data || [];
|
||||
},
|
||||
|
||||
@ -1,99 +1,89 @@
|
||||
import { IconSymbol } from "@/components/ui/IconSymbol";
|
||||
import { Image } from "expo-image";
|
||||
import {
|
||||
ScrollView,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from "react-native";
|
||||
"use client";
|
||||
|
||||
import RecipeHighlightCard from "@/components/RecipeHighlightCard";
|
||||
import { supabase } from "@/services/supabase";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { router } from "expo-router";
|
||||
import { ActivityIndicator, ScrollView, Text, View } from "react-native";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
|
||||
interface Recipe {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
image_url: string;
|
||||
created_by: string;
|
||||
is_shared: boolean;
|
||||
time_to_cook_minutes?: number;
|
||||
}
|
||||
|
||||
export default function RecipesScreen() {
|
||||
const foodItems = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Padthaipro",
|
||||
image: require("@/assets/images/food/padthai.jpg"),
|
||||
color: "#FFCC00",
|
||||
const {
|
||||
data: allRecipes,
|
||||
isLoading: isAllLoading,
|
||||
error: allError,
|
||||
} = useQuery<Recipe[], Error>({
|
||||
queryKey: ["all-recipes"],
|
||||
queryFn: async () => {
|
||||
const { data, error } = await supabase
|
||||
.from("foods")
|
||||
.select("*")
|
||||
.eq("is_shared", true)
|
||||
.order("created_at", { ascending: false });
|
||||
if (error) throw error;
|
||||
return data ?? [];
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Jjajangmyeon",
|
||||
image: require("@/assets/images/food/jjajangmyeon.jpg"),
|
||||
color: "#FFA500",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Wingztab",
|
||||
image: require("@/assets/images/food/wings.jpg"),
|
||||
color: "#FFCC00",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "Ramen",
|
||||
image: require("@/assets/images/food/ramen.jpg"),
|
||||
color: "#FFA500",
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "Tiramisu",
|
||||
image: require("@/assets/images/food/tiramisu.jpg"),
|
||||
color: "#FFCC00",
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "Beef wellington",
|
||||
image: require("@/assets/images/food/beef.jpg"),
|
||||
color: "#FFA500",
|
||||
},
|
||||
];
|
||||
staleTime: 1000 * 60,
|
||||
});
|
||||
|
||||
const recipes: Recipe[] = allRecipes || [];
|
||||
const loading = isAllLoading;
|
||||
const error = allError;
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<SafeAreaView className="flex-1 bg-white items-center justify-center">
|
||||
<ActivityIndicator size="large" color="#FFCC00" />
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
if (error) {
|
||||
return (
|
||||
<SafeAreaView className="flex-1 bg-white items-center justify-center">
|
||||
<Text className="text-lg text-red-600">Failed to load recipes</Text>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView className="flex-1 bg-white" edges={["top"]}>
|
||||
<ScrollView className="flex-1">
|
||||
{/* Search Bar */}
|
||||
<View className="flex-row items-center mx-4 mt-2 mb-4 px-3 h-10 bg-white rounded-full border border-gray-300">
|
||||
<IconSymbol name="magnifyingglass" size={20} color="#FF0000" />
|
||||
<TextInput
|
||||
className="flex-1 ml-2 text-[#333]"
|
||||
placeholder="Search"
|
||||
placeholderTextColor="#FF0000"
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Filter Buttons */}
|
||||
<View className="flex-row mx-4 mb-4">
|
||||
<TouchableOpacity className="flex-1 bg-[#FFCC00] py-3 rounded-lg mr-2 items-center">
|
||||
<Text className="font-bold text-[#333]">All Recipes</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity className="flex-1 bg-red-600 py-3 rounded-lg items-center">
|
||||
<Text className="font-bold text-white">My Recipes</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Divider */}
|
||||
<View className="h-px bg-[#EEEEEE] mx-4 mb-4" />
|
||||
|
||||
{/* Food Grid */}
|
||||
<View className="flex-row flex-wrap p-2">
|
||||
{foodItems.map((item) => (
|
||||
<View key={item.id} className="w-1/2 p-2 relative">
|
||||
<Image
|
||||
source={item.image}
|
||||
className="w-full h-[120px] rounded-lg"
|
||||
resizeMode="cover"
|
||||
/>
|
||||
<View
|
||||
className="absolute bottom-4 left-4 py-1 px-2 rounded bg-opacity-90"
|
||||
style={{ backgroundColor: item.color }}
|
||||
>
|
||||
<Text className="text-[#333] font-bold text-xs">
|
||||
{item.name}
|
||||
<Text className="text-2xl font-bold text-[#bb0718] mx-4 mt-6 mb-4">
|
||||
All Recipes
|
||||
</Text>
|
||||
<ScrollView className="flex-1">
|
||||
<View className="flex-row flex-wrap px-2 pb-8">
|
||||
{recipes.length === 0 ? (
|
||||
<View className="w-full items-center mt-10">
|
||||
<Text className="text-gray-500">No recipes found.</Text>
|
||||
</View>
|
||||
) : (
|
||||
recipes.map((item, idx) => (
|
||||
<View key={item.id} className="w-1/2 p-2">
|
||||
<RecipeHighlightCard
|
||||
recipe={{
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
image_url: item.image_url,
|
||||
time_to_cook_minutes: item.time_to_cook_minutes,
|
||||
}}
|
||||
onPress={() => {
|
||||
router.push(`/food/${item.id}`);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
))
|
||||
)}
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
|
||||
@ -18,13 +18,6 @@ export default function RootLayout() {
|
||||
headerShown: false,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="recipe-detail"
|
||||
options={{
|
||||
headerShown: false,
|
||||
presentation: "card",
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</AuthProvider>
|
||||
</QueryClientProvider>
|
||||
|
||||
60
components/RecipeHighlightCard.tsx
Normal file
60
components/RecipeHighlightCard.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
import { IconSymbol } from "@/components/ui/IconSymbol";
|
||||
import { Image } from "expo-image";
|
||||
import { TouchableOpacity, View, Text } from "react-native";
|
||||
|
||||
interface RecipeHighlightCardProps {
|
||||
recipe: {
|
||||
id: number;
|
||||
name: string;
|
||||
description?: string;
|
||||
image_url?: string;
|
||||
time_to_cook_minutes?: number;
|
||||
calories?: number;
|
||||
};
|
||||
onPress?: () => void;
|
||||
}
|
||||
|
||||
export default function RecipeHighlightCard({ recipe, onPress }: RecipeHighlightCardProps) {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
className="flex-1 bg-white rounded-2xl shadow-lg mb-4 overflow-hidden"
|
||||
style={{ marginRight: 12, elevation: 4 }}
|
||||
onPress={onPress}
|
||||
activeOpacity={0.85}
|
||||
>
|
||||
<View className="relative">
|
||||
{recipe.image_url ? (
|
||||
<Image
|
||||
source={{ uri: recipe.image_url }}
|
||||
className="w-full h-36"
|
||||
contentFit="cover"
|
||||
/>
|
||||
) : (
|
||||
<View className="items-center justify-center w-full h-36 bg-gray-200">
|
||||
<Text className="text-gray-400">No Image</Text>
|
||||
</View>
|
||||
)}
|
||||
{/* Calories badge */}
|
||||
{recipe.calories !== undefined && (
|
||||
<View className="absolute top-2 right-2 bg-[#ffd60a] px-2 py-1 rounded-full shadow">
|
||||
<Text className="text-xs font-bold text-[#bb0718]">{recipe.calories} kcal</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
<View className="p-4">
|
||||
<Text className="text-lg font-bold text-[#222] mb-1" numberOfLines={1}>
|
||||
{recipe.name}
|
||||
</Text>
|
||||
<Text className="text-sm text-[#666] mb-2" numberOfLines={2}>
|
||||
{recipe.description || "No description"}
|
||||
</Text>
|
||||
<View className="flex-row items-center mt-1">
|
||||
<IconSymbol name="clock" size={14} color="#bb0718" />
|
||||
<Text className="text-xs text-[#bb0718] ml-1 font-semibold">
|
||||
{recipe.time_to_cook_minutes ? `${recipe.time_to_cook_minutes} min` : "-"}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user