mirror of
https://github.com/Sosokker/chefhai.git
synced 2025-12-19 05:54:08 +01:00
Merge branch 'main' of https://github.com/Sosokker/chefhai
This commit is contained in:
commit
8c6252b19f
@ -1,43 +1,32 @@
|
|||||||
"use client";
|
"use client"
|
||||||
|
import { getFoodById, getIngredients, getNutrients } from "@/services/data/foods"
|
||||||
import { IconSymbol } from "@/components/ui/IconSymbol";
|
import { supabase } from "@/services/supabase"
|
||||||
import {
|
import type { Foods } from "@/types"
|
||||||
getFoodById,
|
import type { Ingredient, Nutrient } from "@/types/index"
|
||||||
getIngredients,
|
import { Feather, MaterialCommunityIcons, Ionicons } from "@expo/vector-icons"
|
||||||
getNutrients,
|
import { useQuery } from "@tanstack/react-query"
|
||||||
} from "@/services/data/foods";
|
import { Image } from "expo-image"
|
||||||
import { supabase } from "@/services/supabase";
|
import { router, useLocalSearchParams } from "expo-router"
|
||||||
import { Foods } from "@/types";
|
import { useRef } from "react"
|
||||||
import { Ingredient, Nutrient } from "@/types/index";
|
import { ScrollView, Text, TouchableOpacity, View, ActivityIndicator, Animated, Dimensions } from "react-native"
|
||||||
import { Feather } from "@expo/vector-icons";
|
import { LinearGradient } from "expo-linear-gradient"
|
||||||
import { useQuery } from "@tanstack/react-query";
|
|
||||||
import { Image } from "expo-image";
|
|
||||||
import { router, useLocalSearchParams } from "expo-router";
|
|
||||||
import { useState } from "react";
|
|
||||||
import {
|
|
||||||
KeyboardAvoidingView,
|
|
||||||
Platform,
|
|
||||||
ScrollView,
|
|
||||||
Text,
|
|
||||||
TouchableOpacity,
|
|
||||||
View,
|
|
||||||
} from "react-native";
|
|
||||||
import { SafeAreaView } from "react-native-safe-area-context";
|
|
||||||
|
|
||||||
interface Step {
|
interface Step {
|
||||||
id: string;
|
id: string
|
||||||
food_id: string;
|
food_id: string
|
||||||
title: string;
|
title: string
|
||||||
step_order: number;
|
step_order: number
|
||||||
description: string;
|
description: string
|
||||||
created_at: string;
|
created_at: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function FoodDetailScreen() {
|
const { width } = Dimensions.get("window")
|
||||||
const { id } = useLocalSearchParams();
|
|
||||||
const [activeTab, setActiveTab] = useState("Ingredients");
|
|
||||||
|
|
||||||
const foodId = typeof id === "string" ? id : "";
|
export default function FoodDetailScreen() {
|
||||||
|
const { id } = useLocalSearchParams()
|
||||||
|
const scrollY = useRef(new Animated.Value(0)).current
|
||||||
|
|
||||||
|
const foodId = typeof id === "string" ? id : ""
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: foodData,
|
data: foodData,
|
||||||
@ -46,13 +35,13 @@ export default function FoodDetailScreen() {
|
|||||||
} = useQuery<Foods, Error>({
|
} = useQuery<Foods, Error>({
|
||||||
queryKey: ["food-detail", foodId],
|
queryKey: ["food-detail", foodId],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data, error } = await getFoodById(foodId);
|
const { data, error } = await getFoodById(foodId)
|
||||||
if (error) throw error;
|
if (error) throw error
|
||||||
if (!data) throw new Error("Food not found");
|
if (!data) throw new Error("Food not found")
|
||||||
return data;
|
return data
|
||||||
},
|
},
|
||||||
enabled: !!foodId,
|
enabled: !!foodId,
|
||||||
});
|
})
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: nutrients,
|
data: nutrients,
|
||||||
@ -61,12 +50,12 @@ export default function FoodDetailScreen() {
|
|||||||
} = useQuery<Nutrient | null, Error>({
|
} = useQuery<Nutrient | null, Error>({
|
||||||
queryKey: ["food-nutrients", foodId],
|
queryKey: ["food-nutrients", foodId],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data, error } = await getNutrients(foodId);
|
const { data, error } = await getNutrients(foodId)
|
||||||
if (error) throw error;
|
if (error) throw error
|
||||||
return data;
|
return data
|
||||||
},
|
},
|
||||||
enabled: !!foodId && !!foodData,
|
enabled: !!foodId && !!foodData,
|
||||||
});
|
})
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: ingredients,
|
data: ingredients,
|
||||||
@ -75,12 +64,12 @@ export default function FoodDetailScreen() {
|
|||||||
} = useQuery<Ingredient[], Error>({
|
} = useQuery<Ingredient[], Error>({
|
||||||
queryKey: ["food-ingredients", foodId],
|
queryKey: ["food-ingredients", foodId],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data, error } = await getIngredients(foodId);
|
const { data, error } = await getIngredients(foodId)
|
||||||
if (error) throw error;
|
if (error) throw error
|
||||||
return data ?? [];
|
return data ?? []
|
||||||
},
|
},
|
||||||
enabled: !!foodId && !!foodData,
|
enabled: !!foodId && !!foodData,
|
||||||
});
|
})
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: steps,
|
data: steps,
|
||||||
@ -99,321 +88,314 @@ export default function FoodDetailScreen() {
|
|||||||
step_order,
|
step_order,
|
||||||
description,
|
description,
|
||||||
created_at
|
created_at
|
||||||
`
|
`,
|
||||||
)
|
)
|
||||||
.eq("food_id", foodId)
|
.eq("food_id", foodId)
|
||||||
.order("step_order", { ascending: true });
|
.order("step_order", { ascending: true })
|
||||||
if (error) throw error;
|
if (error) throw error
|
||||||
return data ?? [];
|
return data ?? []
|
||||||
},
|
},
|
||||||
enabled: !!foodId && !!foodData,
|
enabled: !!foodId && !!foodData,
|
||||||
});
|
})
|
||||||
|
|
||||||
|
// Calculate header opacity based on scroll position
|
||||||
|
const headerOpacity = scrollY.interpolate({
|
||||||
|
inputRange: [0, 100, 150],
|
||||||
|
outputRange: [0, 0.5, 1],
|
||||||
|
extrapolate: "clamp",
|
||||||
|
})
|
||||||
|
|
||||||
|
// Calculate image scale based on scroll position
|
||||||
|
const imageScale = scrollY.interpolate({
|
||||||
|
inputRange: [-100, 0, 100],
|
||||||
|
outputRange: [1.2, 1, 0.8],
|
||||||
|
extrapolate: "clamp",
|
||||||
|
})
|
||||||
|
|
||||||
if (isLoading || stepsLoading || nutrientsLoading || ingredientsLoading) {
|
if (isLoading || stepsLoading || nutrientsLoading || ingredientsLoading) {
|
||||||
return (
|
return (
|
||||||
<SafeAreaView className="flex-1 bg-white" edges={["top"]}>
|
<View className="items-center justify-center flex-1 bg-white">
|
||||||
<View className="flex-1 justify-center items-center">
|
<ActivityIndicator size="large" color="#bb0718" />
|
||||||
<Text>Loading...</Text>
|
<Text className="mt-4 font-medium text-gray-600">Loading recipe details...</Text>
|
||||||
</View>
|
</View>
|
||||||
</SafeAreaView>
|
)
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error || !foodData || ingredientsError || stepsError || nutrientsError) {
|
if (error || !foodData || ingredientsError || stepsError || nutrientsError) {
|
||||||
return (
|
return (
|
||||||
<SafeAreaView className="flex-1 bg-white" edges={["top"]}>
|
<View className="items-center justify-center flex-1 px-6 bg-white">
|
||||||
<View className="flex-1 justify-center items-center">
|
<Ionicons name="alert-circle-outline" size={64} color="#bb0718" />
|
||||||
<Text>Error loading food details</Text>
|
<Text className="mt-4 mb-2 text-xl font-bold text-center text-gray-800">Oops! Something went wrong</Text>
|
||||||
<TouchableOpacity
|
<Text className="mb-6 text-base text-center text-gray-600">
|
||||||
className="px-4 py-2 bg-yellow-400 rounded-full mt-4"
|
We couldn't load the recipe details. Please try again later.
|
||||||
onPress={() => router.push("/home")}
|
</Text>
|
||||||
>
|
<TouchableOpacity className="px-6 py-3 bg-[#ffd60a] rounded-full" onPress={() => router.push("/home")}>
|
||||||
<Text className="text-lg font-bold text-white">
|
<Text className="text-lg font-bold text-[#bb0718]">Go back to home</Text>
|
||||||
Go back to home page
|
</TouchableOpacity>
|
||||||
</Text>
|
</View>
|
||||||
</TouchableOpacity>
|
)
|
||||||
</View>
|
|
||||||
</SafeAreaView>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const startCookingSession = () => {
|
const startCookingSession = () => {
|
||||||
// Corrected router push to use the actual foodId
|
router.push(`/cooking/${foodId}`)
|
||||||
router.push(`/cooking/${foodId}`);
|
}
|
||||||
};
|
|
||||||
|
// Recipe info cards data
|
||||||
|
const recipeInfoCards = [
|
||||||
|
{
|
||||||
|
id: "skill_level",
|
||||||
|
title: "Skill Level",
|
||||||
|
icon: <MaterialCommunityIcons name="chef-hat" size={22} color="#4CAF50" />,
|
||||||
|
value: foodData.skill_level || "Easy",
|
||||||
|
color: "#4CAF50",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "cooking_time",
|
||||||
|
title: "Cooking Time",
|
||||||
|
icon: <Feather name="clock" size={22} color="#bb0718" />,
|
||||||
|
value: `${foodData.time_to_cook_minutes || 0} min`,
|
||||||
|
color: "#bb0718",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "ingredients",
|
||||||
|
title: "Ingredients",
|
||||||
|
icon: <Feather name="list" size={22} color="#2196F3" />,
|
||||||
|
value: `${foodData.ingredient_count ?? ingredients?.length ?? 0}`,
|
||||||
|
color: "#2196F3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "calories",
|
||||||
|
title: "Calories",
|
||||||
|
icon: <Ionicons name="flame" size={22} color="#F44336" />,
|
||||||
|
value: `${foodData.calories || 0} kcal`,
|
||||||
|
color: "#F44336",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView className="flex-1 bg-white" edges={["top"]}>
|
<View className="flex-1 mt-16">
|
||||||
<KeyboardAvoidingView
|
{/* Animated header background */}
|
||||||
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
<Animated.View
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
height: 90,
|
||||||
|
// backgroundColor: "white",
|
||||||
|
opacity: headerOpacity,
|
||||||
|
zIndex: 10,
|
||||||
|
// shadowColor: "#000",
|
||||||
|
// shadowOffset: { width: 0, height: 2 },
|
||||||
|
// shadowOpacity: 0.1,
|
||||||
|
// shadowRadius: 4,
|
||||||
|
elevation: 5,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Header with back and share buttons */}
|
||||||
|
<View className="absolute top-0 left-0 right-0 z-20 flex-row justify-between px-4 py-3">
|
||||||
|
<TouchableOpacity
|
||||||
|
className="p-3 rounded-full shadow-sm bg-white/80 backdrop-blur-md"
|
||||||
|
onPress={() => router.back()}
|
||||||
|
>
|
||||||
|
<Feather name="arrow-left" size={24} color="#bb0718" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity className="p-3 rounded-full shadow-sm bg-white/80 backdrop-blur-md">
|
||||||
|
<Feather name="share-2" size={24} color="#333" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Animated.ScrollView
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
scrollEventThrottle={16}
|
||||||
|
onScroll={Animated.event([{ nativeEvent: { contentOffset: { y: scrollY } } }], { useNativeDriver: true })}
|
||||||
>
|
>
|
||||||
<ScrollView className="flex-1">
|
{/* Food Image with gradient overlay */}
|
||||||
{/* Header with back and share buttons */}
|
<Animated.View
|
||||||
<View className="flex-row justify-between px-4 py-3 absolute top-0 left-0 right-0 z-10">
|
style={{
|
||||||
<TouchableOpacity
|
height: 350,
|
||||||
className="bg-[#ffd60a] p-3 rounded-lg"
|
width: "100%",
|
||||||
onPress={() => router.back()}
|
transform: [{ scale: imageScale }],
|
||||||
>
|
overflow: "hidden",
|
||||||
<Feather name="arrow-left" size={24} color="#bb0718" />
|
}}
|
||||||
</TouchableOpacity>
|
>
|
||||||
<TouchableOpacity className="w-10 h-10 rounded-full bg-white justify-center items-center">
|
<Image
|
||||||
<IconSymbol
|
source={{ uri: foodData.image_url || "/vibrant-food-dish.png" }}
|
||||||
name="square.and.arrow.up"
|
style={{ width: "100%", height: "100%" }}
|
||||||
size={24}
|
contentFit="cover"
|
||||||
color="#FFCC00"
|
/>
|
||||||
/>
|
<LinearGradient
|
||||||
</TouchableOpacity>
|
colors={["transparent", "rgba(0,0,0,0.7)"]}
|
||||||
</View>
|
style={{
|
||||||
|
position: "absolute",
|
||||||
{/* Food Image */}
|
bottom: 0,
|
||||||
<View className="items-center mt-16 mb-5">
|
left: 0,
|
||||||
<View
|
right: 0,
|
||||||
style={{
|
height: 150,
|
||||||
width: 200,
|
}}
|
||||||
height: 200,
|
/>
|
||||||
backgroundColor: "#e0e0e0",
|
<View className="absolute bottom-8 left-6 right-6">
|
||||||
borderRadius: 24,
|
<Text className="mb-2 text-3xl font-bold text-white">{foodData.name}</Text>
|
||||||
overflow: "hidden",
|
<View className="flex-row items-center">
|
||||||
}}
|
<View className="bg-[#ffd60a] px-3 py-1 rounded-full">
|
||||||
>
|
<Text className="text-sm font-bold text-[#bb0718]">{foodData.skill_level || "Easy"}</Text>
|
||||||
{foodData.image_url ? (
|
</View>
|
||||||
<Image
|
<View className="px-3 py-1 ml-2 rounded-full bg-white/30">
|
||||||
source={{ uri: foodData.image_url }}
|
<Text className="text-sm font-bold text-white">{foodData.time_to_cook_minutes || 0} min</Text>
|
||||||
className="w-52 h-52 rounded-full border-4 border-white"
|
|
||||||
style={{ width: "100%", height: "100%" }}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Text className="text-lg font-bold text-gray-500">
|
|
||||||
Image not available
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Food Title and Description */}
|
|
||||||
<View className="px-4">
|
|
||||||
<Text className="text-2xl font-bold text-gray-800 mb-2">
|
|
||||||
{foodData.name}
|
|
||||||
</Text>
|
|
||||||
<Text className="text-base text-gray-500 mb-5 leading-6">
|
|
||||||
{foodData.description}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
{/* Info Tabs */}
|
|
||||||
<View className="flex-row justify-between mb-5">
|
|
||||||
<TouchableOpacity
|
|
||||||
className="items-center"
|
|
||||||
onPress={() => setActiveTab("Skills")}
|
|
||||||
>
|
|
||||||
<Text className="text-sm text-gray-500">Skills</Text>
|
|
||||||
<Text className="text-base font-bold text-gray-800 mt-1">
|
|
||||||
{foodData.skill_level}
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<TouchableOpacity
|
|
||||||
className="items-center"
|
|
||||||
onPress={() => setActiveTab("Time")}
|
|
||||||
>
|
|
||||||
<Text className="text-sm text-gray-500">Time</Text>
|
|
||||||
<Text className="text-base font-bold text-gray-800 mt-1">
|
|
||||||
{foodData.time_to_cook_minutes}
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<TouchableOpacity
|
|
||||||
className={`items-center ${
|
|
||||||
activeTab === "Ingredients"
|
|
||||||
? "border-b-2 border-gray-800"
|
|
||||||
: ""
|
|
||||||
}`}
|
|
||||||
onPress={() => setActiveTab("Ingredients")}
|
|
||||||
>
|
|
||||||
<Text className="text-sm text-gray-500">Ingredients</Text>
|
|
||||||
<Text className="text-base font-bold text-gray-800 mt-1">
|
|
||||||
{/* Use ingredient_count from foodData or length of the fetched ingredients array */}
|
|
||||||
{foodData.ingredient_count ?? ingredients?.length ?? 0}
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<TouchableOpacity
|
|
||||||
className="items-center"
|
|
||||||
onPress={() => setActiveTab("Calories")}
|
|
||||||
>
|
|
||||||
<Text className="text-sm text-gray-500">Calories</Text>
|
|
||||||
<Text className="text-base font-bold text-gray-800 mt-1">
|
|
||||||
{foodData.calories}
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Ingredients Section */}
|
|
||||||
<View className="mb-5">
|
|
||||||
<Text className="text-xl font-bold text-gray-800 mb-4">
|
|
||||||
Ingredients
|
|
||||||
</Text>
|
|
||||||
<View className="flex-row flex-wrap">
|
|
||||||
{(ingredients ?? []).map(
|
|
||||||
// Use the 'ingredients' state variable
|
|
||||||
(
|
|
||||||
ingredient: Ingredient,
|
|
||||||
index: number // Added type for ingredient
|
|
||||||
) => (
|
|
||||||
<View
|
|
||||||
key={ingredient.id || index}
|
|
||||||
className="w-1/4 items-center mb-4"
|
|
||||||
>
|
|
||||||
<View className="w-15 h-15 rounded-full bg-gray-100 justify-center items-center mb-2 shadow">
|
|
||||||
<Text className="text-2xl">{ingredient.emoji}</Text>
|
|
||||||
</View>
|
|
||||||
<Text className="text-xs text-center text-gray-800">
|
|
||||||
{ingredient.name}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
{/* You might want to show a loading/empty state for ingredients here too */}
|
|
||||||
{/*!ingredientsLoading && ingredients?.length === 0 && (
|
|
||||||
<Text className="text-sm text-gray-500">No ingredients listed.</Text>
|
|
||||||
)*/}
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
</View>
|
||||||
|
</Animated.View>
|
||||||
|
|
||||||
{/* Nutrition Section - Improved UI */}
|
<View className="px-6 pt-8 pb-24">
|
||||||
<View className="mb-5">
|
{/* Description */}
|
||||||
<Text className="text-xl font-bold text-gray-800 mb-4">
|
{foodData.description && (
|
||||||
Nutrition Facts
|
<Text className="mb-8 text-base leading-6 text-gray-700">{foodData.description}</Text>
|
||||||
</Text>
|
)}
|
||||||
{/* Conditionally render nutrients or show placeholder/loading */}
|
|
||||||
{nutrients ? (
|
{/* Recipe Info Cards - Horizontal Scrollable */}
|
||||||
<View className="flex-row justify-between bg-white rounded-xl p-4 shadow">
|
<View className="mb-10">
|
||||||
|
<ScrollView
|
||||||
|
horizontal
|
||||||
|
showsHorizontalScrollIndicator={false}
|
||||||
|
contentContainerStyle={{ paddingRight: 20 }}
|
||||||
|
className="mb-2"
|
||||||
|
>
|
||||||
|
{recipeInfoCards.map((card) => (
|
||||||
|
<View
|
||||||
|
key={card.id}
|
||||||
|
className="p-5 mr-4 bg-white border border-gray-100 rounded-2xl"
|
||||||
|
style={{ width: 130 }}
|
||||||
|
>
|
||||||
<View className="items-center">
|
<View className="items-center">
|
||||||
<View
|
{card.icon}
|
||||||
className="w-15 h-15 rounded-full justify-center items-center mb-2"
|
<Text className="mt-2 text-sm font-medium text-gray-500">{card.title}</Text>
|
||||||
style={{ backgroundColor: "#FFD700" }}
|
<Text className="mt-1 text-xl font-bold" style={{ color: card.color }}>
|
||||||
>
|
{card.value}
|
||||||
<Text className="text-lg font-bold text-gray-800">
|
|
||||||
{nutrients.fat_g ?? 0}
|
|
||||||
</Text>
|
|
||||||
<Text className="text-xs text-gray-800 absolute bottom-2.5 right-2.5">
|
|
||||||
g
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<Text className="text-sm font-medium text-gray-800">
|
|
||||||
Fat
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View className="items-center">
|
|
||||||
<View
|
|
||||||
className="w-15 h-15 rounded-full justify-center items-center mb-2"
|
|
||||||
style={{ backgroundColor: "#90EE90" }}
|
|
||||||
>
|
|
||||||
<Text className="text-lg font-bold text-gray-800">
|
|
||||||
{nutrients.fiber_g ?? 0}
|
|
||||||
</Text>
|
|
||||||
<Text className="text-xs text-gray-800 absolute bottom-2.5 right-2.5">
|
|
||||||
g
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<Text className="text-sm font-medium text-gray-800">
|
|
||||||
Fiber
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View className="items-center">
|
|
||||||
<View
|
|
||||||
className="w-15 h-15 rounded-full justify-center items-center mb-2"
|
|
||||||
style={{ backgroundColor: "#ADD8E6" }}
|
|
||||||
>
|
|
||||||
<Text className="text-lg font-bold text-gray-800">
|
|
||||||
{nutrients.protein_g ?? 0}
|
|
||||||
</Text>
|
|
||||||
<Text className="text-xs text-gray-800 absolute bottom-2.5 right-2.5">
|
|
||||||
g
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<Text className="text-sm font-medium text-gray-800">
|
|
||||||
Protein
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View className="items-center">
|
|
||||||
<View
|
|
||||||
className="w-15 h-15 rounded-full justify-center items-center mb-2"
|
|
||||||
style={{ backgroundColor: "#FFA07A" }}
|
|
||||||
>
|
|
||||||
<Text className="text-lg font-bold text-gray-800">
|
|
||||||
{nutrients.carbs_g ?? 0}
|
|
||||||
</Text>
|
|
||||||
<Text className="text-xs text-gray-800 absolute bottom-2.5 right-2.5">
|
|
||||||
g
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<Text className="text-sm font-medium text-gray-800">
|
|
||||||
Carbs
|
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
))}
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Ingredients Section */}
|
||||||
|
<View className="mb-10">
|
||||||
|
<Text className="mb-5 text-2xl font-bold text-gray-800">Ingredients</Text>
|
||||||
|
<View className="bg-white border border-gray-100 shadow-sm rounded-2xl">
|
||||||
|
{ingredients && ingredients.length > 0 ? (
|
||||||
|
<View className="flex-row flex-wrap justify-between p-4">
|
||||||
|
{ingredients.map((ingredient, index) => (
|
||||||
|
<View key={ingredient.id || index} className="w-[30%] items-center mb-6">
|
||||||
|
<View className="items-center justify-center w-16 h-16 mb-2 rounded-full bg-gray-50">
|
||||||
|
<Text className="text-3xl">{ingredient.emoji || "🍴"}</Text>
|
||||||
|
</View>
|
||||||
|
<Text className="text-sm font-medium text-center text-gray-800">{ingredient.name}</Text>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<Text className="text-sm text-gray-500">
|
<View className="items-center py-8">
|
||||||
Nutrition facts not available.
|
<Feather name="shopping-bag" size={40} color="#e0e0e0" />
|
||||||
</Text>
|
<Text className="mt-2 text-center text-gray-400">No ingredients listed</Text>
|
||||||
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
{/* Steps Preview */}
|
{/* Nutrition Section */}
|
||||||
<View className="mb-5">
|
{nutrients && (
|
||||||
<Text className="text-xl font-bold text-gray-800 mb-4">
|
<View className="mb-10">
|
||||||
Cooking Steps
|
<Text className="mb-5 text-2xl font-bold text-gray-800">Nutrition Facts</Text>
|
||||||
</Text>
|
<View className="p-6 bg-white border border-gray-100 shadow-sm rounded-2xl">
|
||||||
<View className="bg-gray-100 rounded-xl p-4">
|
<View className="flex-row justify-between">
|
||||||
{steps && steps.length > 0 ? (
|
<NutrientCircle
|
||||||
steps.slice(0, 2).map(
|
value={nutrients.protein_g ?? 0}
|
||||||
(
|
label="Protein"
|
||||||
step: Step,
|
color="#2196F3"
|
||||||
index: number // Added type for step
|
bgColor="#2196F3/10"
|
||||||
) => (
|
/>
|
||||||
<View
|
<NutrientCircle value={nutrients.carbs_g ?? 0} label="Carbs" color="#F44336" bgColor="#F44336/10" />
|
||||||
key={step.id || index}
|
<NutrientCircle value={nutrients.fat_g ?? 0} label="Fat" color="#FFD700" bgColor="#FFD700/10" />
|
||||||
className="flex-row items-center mb-3"
|
<NutrientCircle value={nutrients.fiber_g ?? 0} label="Fiber" color="#4CAF50" bgColor="#4CAF50/10" />
|
||||||
>
|
</View>
|
||||||
<View className="w-7.5 h-7.5 rounded-full bg-yellow-400 justify-center items-center mr-3">
|
|
||||||
<Text className="text-base font-bold text-gray-800">
|
|
||||||
{step.step_order ?? index + 1}{" "}
|
|
||||||
{/* Use step_order or fallback to index */}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<Text className="text-base text-gray-800 flex-1">
|
|
||||||
{step.description || step.title}{" "}
|
|
||||||
{/* Display description or title */}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<Text className="text-sm text-gray-500 italic text-center mt-2">
|
|
||||||
No cooking steps listed
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
{steps && steps.length > 2 && (
|
|
||||||
<Text className="text-sm text-gray-500 italic text-center mt-2">
|
|
||||||
...and {steps.length - 2} more steps
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
)}
|
||||||
</ScrollView>
|
|
||||||
|
|
||||||
{/* Cook Button */}
|
{/* Steps Preview */}
|
||||||
|
<View className="mb-8">
|
||||||
|
<Text className="mb-5 text-2xl font-bold text-gray-800">Cooking Steps</Text>
|
||||||
|
<View className="bg-white border border-gray-100 shadow-sm rounded-2xl">
|
||||||
|
{steps && steps.length > 0 ? (
|
||||||
|
<View className="p-4">
|
||||||
|
{steps.slice(0, 3).map((step, index) => (
|
||||||
|
<View key={step.id || index} className="flex-row mb-6 last:mb-0">
|
||||||
|
<View className="w-10 h-10 rounded-full bg-[#ffd60a] justify-center items-center mr-4">
|
||||||
|
<Text className="text-base font-bold text-[#bb0718]">{step.step_order ?? index + 1}</Text>
|
||||||
|
</View>
|
||||||
|
<View className="flex-1">
|
||||||
|
{step.title && <Text className="mb-1 text-base font-bold text-gray-800">{step.title}</Text>}
|
||||||
|
<Text className="text-base text-gray-700">
|
||||||
|
{step.description || "No description available"}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
{steps.length > 3 && (
|
||||||
|
<TouchableOpacity
|
||||||
|
className="items-center py-3 mt-4 border-t border-gray-100"
|
||||||
|
onPress={startCookingSession}
|
||||||
|
>
|
||||||
|
<Text className="text-[#bb0718] font-bold">View all {steps.length} steps</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<View className="items-center py-8">
|
||||||
|
<Feather name="list" size={40} color="#e0e0e0" />
|
||||||
|
<Text className="mt-2 text-center text-gray-400">No cooking steps available</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Animated.ScrollView>
|
||||||
|
|
||||||
|
{/* Cook Button */}
|
||||||
|
<View className="absolute bottom-0 left-0 right-0 px-6 pt-4 pb-8 bg-white border-t border-gray-100 shadow-lg">
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
className="absolute bottom-0 left-0 right-0 bg-red-600 flex-row justify-center items-center py-4"
|
className="bg-[#bb0718] rounded-full py-4 flex-row justify-center items-center"
|
||||||
onPress={startCookingSession}
|
onPress={startCookingSession}
|
||||||
// Disable button if essential data is missing or still loading
|
|
||||||
// disabled={isLoading || ingredientsLoading || stepsLoading || !ingredients || !steps}
|
|
||||||
>
|
>
|
||||||
<Text className="text-lg font-bold text-yellow-400 mr-2">
|
<Text className="mr-2 text-lg font-bold text-white">Let's Cook!</Text>
|
||||||
Let's Cook!
|
<MaterialCommunityIcons name="chef-hat" size={24} color="white" />
|
||||||
</Text>
|
|
||||||
<IconSymbol name="fork.knife" size={20} color="#FFCC00" />
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</KeyboardAvoidingView>
|
</View>
|
||||||
</SafeAreaView>
|
</View>
|
||||||
);
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper component for nutrition facts
|
||||||
|
function NutrientCircle({
|
||||||
|
value,
|
||||||
|
label,
|
||||||
|
color,
|
||||||
|
bgColor,
|
||||||
|
}: { value: number; label: string; color: string; bgColor: string }) {
|
||||||
|
return (
|
||||||
|
<View className="items-center">
|
||||||
|
<View
|
||||||
|
className="items-center justify-center w-16 h-16 mb-2 rounded-full"
|
||||||
|
style={{ backgroundColor: bgColor.replace("/", "-") }}
|
||||||
|
>
|
||||||
|
<Text className="text-xl font-bold" style={{ color }}>
|
||||||
|
{value}g
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<Text className="text-sm font-medium text-gray-700">{label}</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user