"use client"
import { useState, useEffect, useRef } from "react"
import {
View,
Text,
Image,
TouchableOpacity,
ScrollView,
ActivityIndicator,
FlatList,
Alert,
TextInput,
KeyboardAvoidingView,
Platform,
Keyboard,
} from "react-native"
import { Feather, MaterialCommunityIcons, Ionicons } from "@expo/vector-icons"
import { useLocalSearchParams, router } from "expo-router"
import { useAuth } from "../../context/auth-context"
import { supabase } from "../../services/supabase"
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import {
getComments,
createComment,
getLikesCount,
getSavesCount,
getCommentsCount,
checkUserLiked,
checkUserSaved,
} from "../../services/data/forum"
import { getProfile } from "../../services/data/profile"
import { queryKeys, useLikeMutation, useSaveMutation } from "../../hooks/use-foods"
import { updateFoodSharing, deleteFood } from "../../services/data/foods"
function MenuButton({ food, currentUserId }: { food: any; currentUserId: string | null }) {
const [menuVisible, setMenuVisible] = useState(false)
const queryClient = useQueryClient()
const isOwner = currentUserId && food.created_by === currentUserId
const updateSharingMutation = useMutation({
mutationFn: async ({ foodId, isShared }: { foodId: string; isShared: boolean }) => {
const { error } = await updateFoodSharing(foodId, isShared)
if (error) throw error
return { success: true }
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["food", food.id] })
Alert.alert("Success", `Food is now ${!food.is_shared ? "public" : "private"}`)
setMenuVisible(false)
},
onError: (error) => {
console.error("Error updating sharing status:", error)
Alert.alert("Error", "Failed to update sharing status")
},
})
const deleteFoodMutation = useMutation({
mutationFn: async (foodId: string) => {
const { error } = await deleteFood(foodId)
if (error) throw error
return { success: true }
},
onSuccess: () => {
router.back()
queryClient.invalidateQueries({ queryKey: ["my-recipes", currentUserId] })
Alert.alert("Success", "Food deleted successfully")
},
onError: (error) => {
console.error("Error deleting food:", error)
Alert.alert("Error", "Failed to delete food")
},
})
const handleToggleSharing = () => {
if (!isOwner) {
Alert.alert("Permission Denied", "You can only modify your own posts")
return
}
updateSharingMutation.mutate({
foodId: food.id,
isShared: !food.is_shared,
})
}
const handleDelete = () => {
if (!isOwner) {
Alert.alert("Permission Denied", "You can only delete your own posts")
return
}
Alert.alert("Confirm Delete", "Are you sure you want to delete this post? This action cannot be undone.", [
{ text: "Cancel", style: "cancel" },
{
text: "Delete",
style: "destructive",
onPress: () => deleteFoodMutation.mutate(food.id),
},
])
}
const handleNavigateToFood = () => {
router.push(`/food/${food.id}`)
setMenuVisible(false)
}
return (
setMenuVisible(!menuVisible)}>
{menuVisible && (
{food.is_shared ? "Make Private" : "Make Public"}
View Recipe
{isOwner && (
Delete
)}
)}
)
}
export default function PostDetailScreen() {
const params = useLocalSearchParams()
const foodId = typeof params.id === "string" ? params.id : ""
const queryClient = useQueryClient()
const scrollViewRef = useRef(null)
console.log("Post detail screen - Food ID:", foodId)
const { isAuthenticated } = useAuth()
const [currentUserId, setCurrentUserId] = useState(null)
const [commentText, setCommentText] = useState("")
const [submittingComment, setSubmittingComment] = useState(false)
const [showReviews, setShowReviews] = useState(true)
const [keyboardVisible, setKeyboardVisible] = useState(false)
// Listen for keyboard events
useEffect(() => {
const keyboardDidShowListener = Keyboard.addListener("keyboardDidShow", () => {
setKeyboardVisible(true)
// Scroll to bottom when keyboard appears
setTimeout(() => {
scrollViewRef.current?.scrollToEnd({ animated: true })
}, 100)
})
const keyboardDidHideListener = Keyboard.addListener("keyboardDidHide", () => {
setKeyboardVisible(false)
})
return () => {
keyboardDidShowListener.remove()
keyboardDidHideListener.remove()
}
}, [])
// Recipe info cards data
const recipeInfoCards = [
{
id: "cooking_time",
title: "Cooking Time",
icon: (
),
value: (food: any) => food.time_to_cook_minutes,
unit: (food: any) => (food.time_to_cook_minutes === 1 ? "minute" : "minutes"),
gradient: ["#fff8e1", "#fffde7"],
valueColor: "#bb0718",
},
{
id: "skill_level",
title: "Skill Level",
icon: (
),
value: (food: any) => food.skill_level,
unit: () => "",
gradient: ["#e8f5e9", "#f1f8e9"],
valueColor: "",
customContent: (food: any) => (
{food.skill_level}
{renderSkillLevelDots(food.skill_level)}
),
},
{
id: "ingredients",
title: "Ingredients",
icon: (
),
value: (food: any) => food.ingredient_count,
unit: (food: any) => (food.ingredient_count === 1 ? "item" : "items"),
gradient: ["#e3f2fd", "#e8f5e9"],
valueColor: "#2196F3",
},
{
id: "calories",
title: "Calories",
icon: (
),
value: (food: any) => food.calories,
unit: () => "kcal",
gradient: ["#ffebee", "#fff8e1"],
valueColor: "#F44336",
},
]
// 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)
} else {
setCurrentUserId(null)
}
}
getCurrentUser()
}, [isAuthenticated])
// Fetch food details
const {
data: food,
isLoading: isLoadingFood,
error: foodError,
} = useQuery({
queryKey: queryKeys.foodDetails(foodId),
queryFn: async () => {
const { data, error } = await supabase.from("foods").select("*").eq("id", foodId).single()
if (error) throw error
return {
...data,
description: data.description || "",
ingredient_count: data.ingredient_count ?? 0,
calories: data.calories ?? 0,
time_to_cook_minutes: data.time_to_cook_minutes ?? 0,
skill_level: data.skill_level || "Easy",
image_url: data.image_url || "",
}
},
enabled: !!foodId,
})
// Fetch food creator
const { data: foodCreator, isLoading: isLoadingCreator } = useQuery({
queryKey: ["food-creator", food?.created_by],
queryFn: async () => {
if (!food?.created_by) return null
const { data, error } = await getProfile(food.created_by)
if (error) throw error
return data
},
enabled: !!food?.created_by,
})
// Fetch food stats
const {
data: stats = { likes: 0, saves: 0, comments: 0 },
isLoading: isLoadingStats,
refetch: refetchStats,
} = useQuery({
queryKey: ["food-stats", foodId],
queryFn: async () => {
const [likesRes, savesRes, commentsRes] = await Promise.all([
getLikesCount(foodId),
getSavesCount(foodId),
getCommentsCount(foodId),
])
return {
likes: likesRes.count || 0,
saves: savesRes.count || 0,
comments: commentsRes.count || 0,
}
},
enabled: !!foodId,
})
// Fetch user interactions
const {
data: interactions = { liked: false, saved: false },
isLoading: isLoadingInteractions,
refetch: refetchInteractions,
} = useQuery({
queryKey: ["user-interactions", foodId, currentUserId],
queryFn: async () => {
if (!currentUserId) return { liked: false, saved: false }
const [likedRes, savedRes] = await Promise.all([
checkUserLiked(foodId, currentUserId),
checkUserSaved(foodId, currentUserId),
])
return {
liked: !!likedRes.data,
saved: !!savedRes.data,
}
},
enabled: !!foodId && !!currentUserId,
})
// Fetch comments
const {
data: comments = [],
isLoading: isLoadingComments,
refetch: refetchComments,
} = useQuery({
queryKey: queryKeys.foodComments(foodId),
queryFn: async () => {
const { data, error } = await getComments(foodId)
if (error) throw error
return data || []
},
enabled: !!foodId,
})
// Set up mutations
const likeMutation = useLikeMutation()
const saveMutation = useSaveMutation()
const commentMutation = useMutation({
mutationFn: async ({ foodId, userId, content }: { foodId: string; userId: string; content: string }) => {
return createComment(foodId, userId, content)
},
onSuccess: () => {
// Invalidate the comments for this specific food
queryClient.invalidateQueries({ queryKey: queryKeys.foodComments(foodId) })
// Invalidate the food stats for this food
queryClient.invalidateQueries({ queryKey: ["food-stats", foodId] })
// Also invalidate the general food stats that might be used in the forum screen
queryClient.invalidateQueries({
queryKey: ["food-stats", foodId],
exact: false,
})
setCommentText("")
Keyboard.dismiss()
},
})
// Set up real-time subscription for comments
useEffect(() => {
if (!foodId) return
console.log(`Setting up real-time subscription for comments on food_id: ${foodId}`)
const subscription = supabase
.channel(`food_comments:${foodId}`)
.on(
"postgres_changes",
{
event: "*",
schema: "public",
table: "food_comments",
filter: `food_id=eq.${foodId}`,
},
() => {
console.log("Comment change detected, refreshing comments")
refetchComments()
refetchStats()
},
)
.subscribe()
return () => {
supabase.removeChannel(subscription)
}
}, [foodId, refetchComments, refetchStats])
// Set up real-time subscription for likes and saves
useEffect(() => {
if (!foodId) return
const likesSubscription = supabase
.channel(`food_likes:${foodId}`)
.on(
"postgres_changes",
{
event: "*",
schema: "public",
table: "food_likes",
filter: `food_id=eq.${foodId}`,
},
() => {
console.log("Like change detected, refreshing stats and interactions")
refetchStats()
refetchInteractions()
},
)
.subscribe()
const savesSubscription = supabase
.channel(`food_saves:${foodId}`)
.on(
"postgres_changes",
{
event: "*",
schema: "public",
table: "food_saves",
filter: `food_id=eq.${foodId}`,
},
() => {
console.log("Save change detected, refreshing stats and interactions")
refetchStats()
refetchInteractions()
},
)
.subscribe()
return () => {
supabase.removeChannel(likesSubscription)
supabase.removeChannel(savesSubscription)
}
}, [foodId, refetchStats, refetchInteractions])
const handleLike = async () => {
if (!isAuthenticated || !currentUserId || !food) {
Alert.alert("Authentication Required", "Please log in to like posts.")
return
}
try {
likeMutation.mutate({
foodId,
userId: currentUserId,
isLiked: interactions.liked,
})
} catch (error) {
console.error("Error toggling like:", error)
Alert.alert("Error", "Failed to update like. Please try again.")
}
}
const handleSave = async () => {
if (!isAuthenticated || !currentUserId || !food) {
Alert.alert("Authentication Required", "Please log in to save posts.")
return
}
try {
saveMutation.mutate({
foodId,
userId: currentUserId,
isSaved: interactions.saved,
})
} catch (error) {
console.error("Error toggling save:", error)
Alert.alert("Error", "Failed to update save. Please try again.")
}
}
const handleSubmitComment = async () => {
if (!isAuthenticated || !currentUserId || !foodId || !commentText.trim()) {
if (!isAuthenticated || !currentUserId) {
Alert.alert("Authentication Required", "Please log in to comment.")
}
return
}
setSubmittingComment(true)
try {
await commentMutation.mutateAsync({
foodId,
userId: currentUserId,
content: commentText.trim(),
})
} catch (error) {
console.error("Error submitting comment:", error)
Alert.alert("Error", "Failed to submit comment. Please try again.")
} finally {
setSubmittingComment(false)
}
}
// Helper function to get skill level color
const getSkillLevelColor = (level: string) => {
switch (level) {
case "Easy":
return "#4CAF50" // Green
case "Medium":
return "#FFC107" // Amber
case "Hard":
return "#F44336" // Red
default:
return "#4CAF50" // Default to green
}
}
// Helper function to get skill level dots
const renderSkillLevelDots = (level: string) => {
const totalDots = 3
let activeDots = 1
if (level === "Medium") activeDots = 2
if (level === "Hard") activeDots = 3
return (
{[...Array(totalDots)].map((_, i) => (
))}
)
}
// Render recipe info card
const renderRecipeInfoCard = ({ item }: { item: any }) => {
if (!food) return null
return (
{item.icon}
{item.title}
{item.customContent ? (
item.customContent(food)
) : (
{item.value(food)}
{item.unit(food)}
)}
)
}
const isLoading = isLoadingFood || isLoadingCreator || isLoadingStats || isLoadingInteractions || isLoadingComments
if (isLoading) {
return (
)
}
if (foodError || !food) {
return (
Post not found
router.back()}
>
Go Back
)
}
return (
{/* Fixed Header */}
router.back()}>
Post
{/* User info */}
{foodCreator?.avatar_url ? (
) : (
{foodCreator?.username?.charAt(0).toUpperCase() || food.created_by?.charAt(0).toUpperCase() || "?"}
)}
{foodCreator?.username || foodCreator?.full_name || "Chef"}
{/* Food image */}
{/* Food title and description */}
{food.name}
{food.description}
{new Date(food.created_at).toLocaleDateString()} -{" "}
{new Date(food.created_at).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}
{/* Recipe Info Cards - Horizontal Scrollable */}
Recipe Details
item.id}
contentContainerStyle={{ paddingLeft: 16, paddingRight: 8 }}
/>
{/* Interaction buttons */}
{stats.likes}
Save
{/* Reviews section */}
setShowReviews(!showReviews)}
>
Reviews
{stats.comments}
{showReviews && (
{comments.length > 0 ? (
comments.map((comment) => (
{/* Profile picture */}
{comment.user?.avatar_url ? (
) : (
{comment.user?.username?.charAt(0).toUpperCase() ||
comment.user_id?.charAt(0).toUpperCase() ||
"?"}
)}
{/* Comment bubble with username inside */}
{/* Username inside bubble */}
{comment.user?.username || comment.user?.full_name || "User"}
{/* Comment content */}
{comment.content}
{/* Date below bubble */}
{new Date(comment.created_at).toLocaleDateString()}
{/* Separator */}
))
) : (
No reviews yet.
Be the first to comment!
)}
)}
{/* Extra space at the bottom to ensure content is visible above keyboard */}
{/* Comment input */}
{
// Scroll to bottom when input is focused
setTimeout(() => {
scrollViewRef.current?.scrollToEnd({ animated: true })
}, 100)
}}
/>
{!isAuthenticated && (
Please log in to comment
)}
)
}