Files
dmenu-plus-front/src/components/listview/MenuItem.tsx
T
hamid zarghami dd18870b85 online cart
2025-12-01 14:12:34 +03:30

153 lines
5.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { memo, useMemo } from "react";
import Image from "next/image";
import PlusIcon from "@/components/icons/PlusIcon";
import MinusIcon from "@/components/icons/MinusIcon";
import { useCart } from "@/app/[name]/(Dialogs)/cart/hook/useCart";
import { motion } from "framer-motion";
import type { Food } from "@/app/[name]/(Main)/types/Types";
import { Refresh } from "iconsax-react";
import { useGetProfile } from "@/app/[name]/(Profile)/profile/hooks/userProfileData";
interface MenuItemProps {
food: Food;
}
const MenuItem = ({ food }: MenuItemProps) => {
const { isSuccess } = useGetProfile();
const { items, addToCart, removeFromCart, isItemLoading } = useCart();
const isLoading = isItemLoading(food.id);
const quantity = useMemo(() => {
const item = items?.[food.id];
if (item && typeof item === 'object' && 'quantity' in item) {
return item.quantity || 0;
}
return 0;
}, [items, food.id]);
const fallbackImage = "/assets/images/food-preview.png";
const resolvedImage = useMemo(() => {
if (food.image) return food.image;
if (Array.isArray(food.images) && food.images.length > 0) {
const [firstImage] = food.images;
if (typeof firstImage === "string") return firstImage;
return firstImage?.url || fallbackImage;
}
return fallbackImage;
}, [food.image, food.images]);
const foodName = useMemo(
() => food.name || food.title || food.foodName || 'بدون نام',
[food.name, food.title, food.foodName]
);
const foodDescription = useMemo(
() => food.description || food.desc || food.content || '',
[food.description, food.desc, food.content]
);
const formattedPrice = useMemo(
() => (food.price ? food.price.toLocaleString('fa-IR') : '0'),
[food.price]
);
const handleAddToCart = () => {
addToCart(food.id);
};
const handleRemoveFromCart = () => {
removeFromCart(food.id);
};
return (
<div className="flex gap-4 w-full h-full">
<Image
className="rounded-xl w-28 h-28 object-cover"
src={resolvedImage}
height={112}
width={112}
alt={foodName}
/>
<div className="w-full inline-flex flex-col justify-between">
<div>
<div className="text-sm2 font-normal text-black dark:text-white">
{foodName}
</div>
{foodDescription && (
<div className="text-[#7F7F7F] text-xs leading-5 font-normal mt-2">
{foodDescription}
</div>
)}
</div>
<div className="inline-flex gap-2 justify-between w-full items-center">
<span className="w-full text-sm mt-1" dir="ltr">
{formattedPrice} T
</span>
<motion.div
whileTap={{ scale: 1.05 }}
className="bg-background active:drop-shadow-xs max-w-[115px] rounded-md w-full h-8 inline-flex p-1 justify-between items-center gap-2 relative overflow-hidden"
>
{isSuccess && isLoading && (
<motion.div
className="absolute inset-0 bg-background/80 flex items-center justify-center z-10"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<motion.div
animate={{ rotateZ: [0, 360] }}
transition={{ duration: 1.2, repeat: Infinity, ease: 'linear' }}
>
<Refresh className="text-foreground" size={20} />
</motion.div>
</motion.div>
)}
{quantity <= 0 ? (
<button
onClick={handleAddToCart}
disabled={isSuccess && isLoading}
className="inline-flex w-full justify-center items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
>
<PlusIcon />
<div className="text-sm2 pt-0.5 font-normal text-foreground">
افزودن
</div>
</button>
) : (
<>
<button
onClick={handleAddToCart}
disabled={isSuccess && isLoading}
className="bg-container hover:bg-container/60 active:bg-container/30 rounded-sm p-2 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
<PlusIcon className="text-foreground" />
</button>
<motion.div
key={quantity}
initial={{ scale: 1 }}
animate={{ scale: [1.2, 0.95, 1] }}
transition={{ duration: 0.3 }}
className="text-sm2 pt-0.5 font-semibold"
>
{quantity}
</motion.div>
<button
onClick={handleRemoveFromCart}
disabled={isSuccess && isLoading}
className="bg-container hover:bg-container/60 active:bg-container/30 rounded-sm p-2 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
<MinusIcon className="text-foreground" />
</button>
</>
)}
</motion.div>
</div>
</div>
</div>
);
};
export default memo(MenuItem);