diff --git a/app/post-detail/[id].tsx b/app/post-detail/[id].tsx
index fbfe008..7de261f 100644
--- a/app/post-detail/[id].tsx
+++ b/app/post-detail/[id].tsx
@@ -1,77 +1,181 @@
-"use client";
+"use client"
-import { Feather, Ionicons, MaterialCommunityIcons } from "@expo/vector-icons";
-import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
-import { router, useLocalSearchParams } from "expo-router";
-import { useEffect, useRef, useState } from "react";
+import { useState, useEffect, useRef } from "react"
import {
- ActivityIndicator,
- Alert,
- FlatList,
+ View,
+ Text,
Image,
- Keyboard,
+ TouchableOpacity,
+ ScrollView,
+ ActivityIndicator,
+ FlatList,
+ Alert,
+ TextInput,
KeyboardAvoidingView,
Platform,
- ScrollView,
- Text,
- TextInput,
- TouchableOpacity,
- View,
-} from "react-native";
-import { useAuth } from "../../context/auth-context";
+ 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 {
- queryKeys,
- useLikeMutation,
- useSaveMutation,
-} from "../../hooks/use-foods";
-import {
- checkUserLiked,
- checkUserSaved,
- createComment,
getComments,
- getCommentsCount,
+ createComment,
getLikesCount,
getSavesCount,
-} from "../../services/data/forum";
-import { getProfile } from "../../services/data/profile";
-import { supabase } from "../../services/supabase";
+ 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);
+ 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);
+ 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);
+ 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);
- }
- );
+ 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);
- }
- );
+ const keyboardDidHideListener = Keyboard.addListener("keyboardDidHide", () => {
+ setKeyboardVisible(false)
+ })
return () => {
- keyboardDidShowListener.remove();
- keyboardDidHideListener.remove();
- };
- }, []);
+ keyboardDidShowListener.remove()
+ keyboardDidHideListener.remove()
+ }
+ }, [])
// Recipe info cards data
const recipeInfoCards = [
@@ -79,15 +183,12 @@ export default function PostDetailScreen() {
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",
+ unit: (food: any) => (food.time_to_cook_minutes === 1 ? "minute" : "minutes"),
gradient: ["#fff8e1", "#fffde7"],
valueColor: "#bb0718",
},
@@ -95,9 +196,7 @@ export default function PostDetailScreen() {
id: "skill_level",
title: "Skill Level",
icon: (
-
+
),
@@ -107,13 +206,7 @@ export default function PostDetailScreen() {
valueColor: "",
customContent: (food: any) => (
-
+
{food.skill_level}
{renderSkillLevelDots(food.skill_level)}
@@ -124,9 +217,7 @@ export default function PostDetailScreen() {
id: "ingredients",
title: "Ingredients",
icon: (
-
+
),
@@ -139,9 +230,7 @@ export default function PostDetailScreen() {
id: "calories",
title: "Calories",
icon: (
-
+
),
@@ -150,23 +239,23 @@ export default function PostDetailScreen() {
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);
+ 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])
// Fetch food details
const {
@@ -176,13 +265,9 @@ export default function PostDetailScreen() {
} = useQuery({
queryKey: queryKeys.foodDetails(foodId),
queryFn: async () => {
- const { data, error } = await supabase
- .from("foods")
- .select("*")
- .eq("id", foodId)
- .single();
+ const { data, error } = await supabase.from("foods").select("*").eq("id", foodId).single()
- if (error) throw error;
+ if (error) throw error
return {
...data,
@@ -192,25 +277,25 @@ export default function PostDetailScreen() {
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;
+ if (!food?.created_by) return null
- const { data, error } = await getProfile(food.created_by);
+ const { data, error } = await getProfile(food.created_by)
- if (error) throw error;
+ if (error) throw error
- return data;
+ return data
},
enabled: !!food?.created_by,
- });
+ })
// Fetch food stats
const {
@@ -224,16 +309,16 @@ export default function PostDetailScreen() {
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 {
@@ -243,20 +328,20 @@ export default function PostDetailScreen() {
} = useQuery({
queryKey: ["user-interactions", foodId, currentUserId],
queryFn: async () => {
- if (!currentUserId) return { liked: false, saved: false };
+ 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 {
@@ -266,48 +351,46 @@ export default function PostDetailScreen() {
} = useQuery({
queryKey: queryKeys.foodComments(foodId),
queryFn: async () => {
- const { data, error } = await getComments(foodId);
+ const { data, error } = await getComments(foodId)
- if (error) throw error;
+ if (error) throw error
- return data || [];
+ return data || []
},
enabled: !!foodId,
- });
+ })
// Set up mutations
- const likeMutation = useLikeMutation();
- const saveMutation = useSaveMutation();
+ 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);
+ 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: queryKeys.foodComments(foodId),
- });
- queryClient.invalidateQueries({ queryKey: ["food-stats", foodId] });
- setCommentText("");
- Keyboard.dismiss();
+ queryKey: ["food-stats", foodId],
+ exact: false,
+ })
+
+ setCommentText("")
+ Keyboard.dismiss()
},
- });
+ })
// Set up real-time subscription for comments
useEffect(() => {
- if (!foodId) return;
+ if (!foodId) return
- console.log(
- `Setting up real-time subscription for comments on food_id: ${foodId}`
- );
+ console.log(`Setting up real-time subscription for comments on food_id: ${foodId}`)
const subscription = supabase
.channel(`food_comments:${foodId}`)
@@ -320,21 +403,21 @@ export default function PostDetailScreen() {
filter: `food_id=eq.${foodId}`,
},
() => {
- console.log("Comment change detected, refreshing comments");
- refetchComments();
- refetchStats();
- }
+ console.log("Comment change detected, refreshing comments")
+ refetchComments()
+ refetchStats()
+ },
)
- .subscribe();
+ .subscribe()
return () => {
- supabase.removeChannel(subscription);
- };
- }, [foodId, refetchComments, refetchStats]);
+ supabase.removeChannel(subscription)
+ }
+ }, [foodId, refetchComments, refetchStats])
// Set up real-time subscription for likes and saves
useEffect(() => {
- if (!foodId) return;
+ if (!foodId) return
const likesSubscription = supabase
.channel(`food_likes:${foodId}`)
@@ -347,14 +430,12 @@ export default function PostDetailScreen() {
filter: `food_id=eq.${foodId}`,
},
() => {
- console.log(
- "Like change detected, refreshing stats and interactions"
- );
- refetchStats();
- refetchInteractions();
- }
+ console.log("Like change detected, refreshing stats and interactions")
+ refetchStats()
+ refetchInteractions()
+ },
)
- .subscribe();
+ .subscribe()
const savesSubscription = supabase
.channel(`food_saves:${foodId}`)
@@ -367,25 +448,23 @@ export default function PostDetailScreen() {
filter: `food_id=eq.${foodId}`,
},
() => {
- console.log(
- "Save change detected, refreshing stats and interactions"
- );
- refetchStats();
- refetchInteractions();
- }
+ console.log("Save change detected, refreshing stats and interactions")
+ refetchStats()
+ refetchInteractions()
+ },
)
- .subscribe();
+ .subscribe()
return () => {
- supabase.removeChannel(likesSubscription);
- supabase.removeChannel(savesSubscription);
- };
- }, [foodId, refetchStats, refetchInteractions]);
+ 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;
+ Alert.alert("Authentication Required", "Please log in to like posts.")
+ return
}
try {
@@ -393,17 +472,17 @@ export default function PostDetailScreen() {
foodId,
userId: currentUserId,
isLiked: interactions.liked,
- });
+ })
} 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 () => {
if (!isAuthenticated || !currentUserId || !food) {
- Alert.alert("Authentication Required", "Please log in to save posts.");
- return;
+ Alert.alert("Authentication Required", "Please log in to save posts.")
+ return
}
try {
@@ -411,57 +490,57 @@ export default function PostDetailScreen() {
foodId,
userId: currentUserId,
isSaved: interactions.saved,
- });
+ })
} 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 handleSubmitComment = async () => {
if (!isAuthenticated || !currentUserId || !foodId || !commentText.trim()) {
if (!isAuthenticated || !currentUserId) {
- Alert.alert("Authentication Required", "Please log in to comment.");
+ Alert.alert("Authentication Required", "Please log in to comment.")
}
- return;
+ return
}
- setSubmittingComment(true);
+ 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.");
+ console.error("Error submitting comment:", error)
+ Alert.alert("Error", "Failed to submit comment. Please try again.")
} finally {
- setSubmittingComment(false);
+ setSubmittingComment(false)
}
- };
+ }
// Helper function to get skill level color
const getSkillLevelColor = (level: string) => {
switch (level) {
case "Easy":
- return "#4CAF50"; // Green
+ return "#4CAF50" // Green
case "Medium":
- return "#FFC107"; // Amber
+ return "#FFC107" // Amber
case "Hard":
- return "#F44336"; // Red
+ return "#F44336" // Red
default:
- return "#4CAF50"; // Default to green
+ return "#4CAF50" // Default to green
}
- };
+ }
// Helper function to get skill level dots
const renderSkillLevelDots = (level: string) => {
- const totalDots = 3;
- let activeDots = 1;
+ const totalDots = 3
+ let activeDots = 1
- if (level === "Medium") activeDots = 2;
- if (level === "Hard") activeDots = 3;
+ if (level === "Medium") activeDots = 2
+ if (level === "Hard") activeDots = 3
return (
@@ -479,12 +558,12 @@ export default function PostDetailScreen() {
/>
))}
- );
- };
+ )
+ }
// Render recipe info card
const renderRecipeInfoCard = ({ item }: { item: any }) => {
- if (!food) return null;
+ if (!food) return null
return (
-
+
{item.icon}
-
- {item.title}
-
+ {item.title}
{item.customContent ? (
item.customContent(food)
) : (
-
- {item.value(food)}
-
-
- {item.unit(food)}
-
+ {item.value(food)}
+ {item.unit(food)}
)}
- );
- };
+ )
+ }
- const isLoading =
- isLoadingFood ||
- isLoadingCreator ||
- isLoadingStats ||
- isLoadingInteractions ||
- isLoadingComments;
+ const isLoading = isLoadingFood || isLoadingCreator || isLoadingStats || isLoadingInteractions || isLoadingComments
if (isLoading) {
return (
-
+
- );
+ )
}
if (foodError || !food) {
return (
-
+
Post not found
- );
+ )
}
return (
-
- {/* Fixed Header */}
-
- router.back()}
- >
-
-
-
- Post
-
- router.push(`/food/${food.id}`)}>
-
-
-
-
- {/* Scrollable Content */}
-
+
+
+ {/* Fixed Header */}
+
+ router.back()}>
+
+
+
+ Post
+
+
+
+
{/* User info */}
-
-
+
+
{foodCreator?.avatar_url ? (
-
+
) : (
-
- {foodCreator?.username?.charAt(0).toUpperCase() ||
- food.created_by?.charAt(0).toUpperCase() ||
- "?"}
+
+ {foodCreator?.username?.charAt(0).toUpperCase() || food.created_by?.charAt(0).toUpperCase() || "?"}
)}
@@ -671,38 +685,17 @@ export default function PostDetailScreen() {
{/* Food title and description */}
-
- {food.name}
-
-
- {food.description}
-
+ {food.name}
+ {food.description}
{new Date(food.created_at).toLocaleDateString()} -{" "}
- {new Date(food.created_at).toLocaleTimeString([], {
- hour: "2-digit",
- minute: "2-digit",
- })}
+ {new Date(food.created_at).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}
{/* Recipe Info Cards - Horizontal Scrollable */}
-
+
Recipe Details
-
- {stats.likes}
-
+ {stats.likes}
-
-
- Save
-
+
+ Save
@@ -793,18 +778,10 @@ export default function PostDetailScreen() {
borderRadius: 12,
}}
>
-
- {stats.comments}
-
+ {stats.comments}
-
+
{showReviews && (
@@ -824,10 +801,7 @@ export default function PostDetailScreen() {
}}
>
{comment.user?.avatar_url ? (
-
+
) : (
-
- {comment.user?.username
- ?.charAt(0)
- .toUpperCase() ||
+
+ {comment.user?.username?.charAt(0).toUpperCase() ||
comment.user_id?.charAt(0).toUpperCase() ||
"?"}
@@ -857,69 +823,42 @@ export default function PostDetailScreen() {
{/* Comment bubble with username inside */}
-
+
{/* Username inside bubble */}
-
- {comment.user?.username ||
- comment.user?.full_name ||
- "User"}
+
+ {comment.user?.username || comment.user?.full_name || "User"}
{/* Comment content */}
-
- {comment.content}
-
+ {comment.content}
{/* Date below bubble */}
-
+
{new Date(comment.created_at).toLocaleDateString()}
+
+ {/* Separator */}
+
))
) : (
-
- No reviews yet.
-
-
- Be the first to comment!
-
+ No reviews yet.
+ Be the first to comment!
)}
)}
+
+ {/* Extra space at the bottom to ensure content is visible above keyboard */}
+
- {/* Comment input - Positioned above keyboard */}
+ {/* Comment input */}
{
+ // Scroll to bottom when input is focused
+ setTimeout(() => {
+ scrollViewRef.current?.scrollToEnd({ animated: true })
+ }, 100)
+ }}
/>
-
+
{!isAuthenticated && (
-
+
Please log in to comment
)}
-
-
- );
+
+
+ )
}
diff --git a/package-lock.json b/package-lock.json
index cfad776..7a6e6ec 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -46,6 +46,7 @@
"react-native": "0.79.2",
"react-native-gesture-handler": "~2.24.0",
"react-native-reanimated": "^3.16.2",
+ "react-native-responsive-screen": "^1.4.2",
"react-native-safe-area-context": "^5.4.0",
"react-native-screens": "~4.10.0",
"react-native-uuid": "^2.0.3",
@@ -11072,6 +11073,15 @@
"react-native": "*"
}
},
+ "node_modules/react-native-responsive-screen": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/react-native-responsive-screen/-/react-native-responsive-screen-1.4.2.tgz",
+ "integrity": "sha512-BLYz0UUpeohrib7jbz6wDmtBD5OmiuMRko4IT8kIF63taXPod/c5iZgmWnr5qOnK8hMuKiGMvsM3sC+eHX/lEQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react-native": ">=0.35"
+ }
+ },
"node_modules/react-native-safe-area-context": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.4.0.tgz",
diff --git a/package.json b/package.json
index a1abfa1..d36a0f7 100644
--- a/package.json
+++ b/package.json
@@ -49,6 +49,7 @@
"react-native": "0.79.2",
"react-native-gesture-handler": "~2.24.0",
"react-native-reanimated": "^3.16.2",
+ "react-native-responsive-screen": "^1.4.2",
"react-native-safe-area-context": "^5.4.0",
"react-native-screens": "~4.10.0",
"react-native-uuid": "^2.0.3",
diff --git a/services/data/foods.ts b/services/data/foods.ts
index b7cd004..79eb521 100644
--- a/services/data/foods.ts
+++ b/services/data/foods.ts
@@ -226,4 +226,12 @@ export const insertGenAIResult = async (
}
return { data: foodId, error: null };
-};
\ No newline at end of file
+};
+
+export async function updateFoodSharing(foodId: string, isShared: boolean) {
+ return await supabase.from("foods").update({ is_shared: isShared }).eq("id", foodId)
+}
+
+export async function deleteFood(foodId: string) {
+ return await supabase.from("foods").delete().eq("id", foodId)
+}