base variant
This commit is contained in:
@@ -23,20 +23,40 @@ const CartSummary = ({ isPremium }: CartSummaryProps) => {
|
||||
const name = params.name as string;
|
||||
const { isSuccess } = useGetProfile();
|
||||
const { data: productsResponse } = useGetProducts();
|
||||
const products = React.useMemo(() => productsResponse?.data ?? [], [productsResponse?.data]);
|
||||
const products = React.useMemo(
|
||||
() => productsResponse?.data ?? [],
|
||||
[productsResponse?.data]
|
||||
);
|
||||
const { items } = useCart();
|
||||
const { data: cartData } = useGetCartItems(isSuccess);
|
||||
const { t } = useTranslation('parallels', { keyPrefix: 'Cart' });
|
||||
const { t } = useTranslation("parallels", { keyPrefix: "Cart" });
|
||||
const { description, setDescription } = useCartStore();
|
||||
|
||||
const cartProducts = React.useMemo(() => {
|
||||
if (!products.length) return [];
|
||||
return Object.entries(items)
|
||||
const cartEntries = React.useMemo(() => {
|
||||
if (products.length === 0) return [];
|
||||
|
||||
const result: { product: Product; variantId: string; quantity: number }[] =
|
||||
[];
|
||||
|
||||
Object.entries(items)
|
||||
.filter(([, detail]) => detail?.quantity && detail.quantity > 0)
|
||||
.map(([id]) =>
|
||||
products.find((productItem) => String(productItem.id) === String(id))
|
||||
.forEach(([variantId, detail]) => {
|
||||
const product = products.find((productItem) =>
|
||||
productItem.variants?.some(
|
||||
(variant) => String(variant.id) === String(variantId)
|
||||
)
|
||||
.filter((product): product is Product => Boolean(product));
|
||||
);
|
||||
|
||||
if (!product) return;
|
||||
|
||||
result.push({
|
||||
product,
|
||||
variantId: String(variantId),
|
||||
quantity: detail.quantity,
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
}, [products, items]);
|
||||
|
||||
const totalPrice = React.useMemo(() => {
|
||||
@@ -45,17 +65,19 @@ const CartSummary = ({ isPremium }: CartSummaryProps) => {
|
||||
return cartData.data.total;
|
||||
}
|
||||
|
||||
// در غیر این صورت، محاسبه محلی با در نظر گرفتن تخفیف
|
||||
return cartProducts.reduce((sum, product) => {
|
||||
const qty = items[String(product.id)]?.quantity ?? 0;
|
||||
const basePrice = product.price ?? 0;
|
||||
const discount = product.discount ?? 0;
|
||||
// در غیر این صورت، محاسبه محلی با در نظر گرفتن تخفیف و تنوعها
|
||||
return cartEntries.reduce((sum, entry) => {
|
||||
const variant = entry.product.variants?.find(
|
||||
(v) => String(v.id) === entry.variantId
|
||||
);
|
||||
const basePrice = variant?.price ?? entry.product.price ?? 0;
|
||||
const discount = entry.product.discount ?? 0;
|
||||
const priceAfterDiscount = basePrice * (1 - discount / 100);
|
||||
return sum + priceAfterDiscount * qty;
|
||||
return sum + priceAfterDiscount * entry.quantity;
|
||||
}, 0);
|
||||
}, [cartProducts, items, isSuccess, cartData?.data?.total]);
|
||||
}, [cartEntries, isSuccess, cartData?.data?.total]);
|
||||
|
||||
const isCartEmpty = cartProducts.length === 0;
|
||||
const isCartEmpty = cartEntries.length === 0;
|
||||
|
||||
const formatPrice = React.useCallback(
|
||||
(value: number) => value.toLocaleString('fa-IR'),
|
||||
@@ -96,7 +118,7 @@ const CartSummary = ({ isPremium }: CartSummaryProps) => {
|
||||
|
||||
{isPremium && (
|
||||
<Link
|
||||
href={'order/checkout/0'}
|
||||
href={`/${name}/order/checkout/0`}
|
||||
onClick={(event) => {
|
||||
if (!isSuccess) {
|
||||
event.preventDefault();
|
||||
@@ -106,7 +128,9 @@ const CartSummary = ({ isPremium }: CartSummaryProps) => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button className='w-fit! px-10 dark:bg-white dark:text-black! dark:hover:bg-gray-100'>{t('ButtonCheckout')}</Button>
|
||||
<Button className='w-fit! px-10 dark:bg-white dark:text-black! dark:hover:bg-gray-100'>
|
||||
{t('ButtonCheckout')}
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
{!isPremium && (
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import * as api from "../service/CartService";
|
||||
import type { CartResponse, CartItem } from "../types/Types";
|
||||
import type { ProductsResponse } from "@/app/[name]/(Main)/types/Types";
|
||||
import { getProductEffectivePrice } from "@/app/[name]/(Main)/types/Types";
|
||||
|
||||
export const useBulkCart = () => {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -47,15 +48,22 @@ export const useIncrementCart = () => {
|
||||
});
|
||||
newTotalItems = previousCart.data.totalItems + 1;
|
||||
} else {
|
||||
const product = productsData.data.find((f) => String(f.id) === String(id));
|
||||
if (product) {
|
||||
const variantId = String(id);
|
||||
const product = productsData.data.find((p) =>
|
||||
p.variants?.some((v) => v.id === variantId)
|
||||
);
|
||||
const variant = product?.variants?.find((v) => v.id === variantId);
|
||||
if (product && variant) {
|
||||
const effectivePrice = getProductEffectivePrice(product);
|
||||
const price = variant.price ?? effectivePrice;
|
||||
const discount = product.discount ?? 0;
|
||||
const newItem: CartItem = {
|
||||
productId: String(id),
|
||||
productId: variantId,
|
||||
productTitle: product.title || product.name || "",
|
||||
quantity: 1,
|
||||
price: product.price,
|
||||
discount: product.discount || 0,
|
||||
totalPrice: product.price * (1 - (product.discount || 0) / 100),
|
||||
price,
|
||||
discount,
|
||||
totalPrice: price * (1 - discount / 100),
|
||||
};
|
||||
updatedItems = [...previousCart.data.items, newItem];
|
||||
newTotalItems = previousCart.data.totalItems + 1;
|
||||
|
||||
@@ -26,10 +26,13 @@ const CartIndex = () => {
|
||||
if (!products.length) return [];
|
||||
return Object.entries(items)
|
||||
.filter(([, detail]) => detail?.quantity && detail.quantity > 0)
|
||||
.map(([id]) =>
|
||||
products.find((productItem) => String(productItem.id) === String(id))
|
||||
)
|
||||
.filter((product): product is Product => Boolean(product));
|
||||
.map(([variantId]) => {
|
||||
const product = products.find((p) =>
|
||||
p.variants?.some((v) => v.id === variantId)
|
||||
);
|
||||
return product ? { product, variantId } : null;
|
||||
})
|
||||
.filter((x): x is { product: Product; variantId: string } => Boolean(x));
|
||||
}, [products, items]);
|
||||
|
||||
const isCartEmpty = cartProducts.length === 0;
|
||||
@@ -95,9 +98,9 @@ const CartIndex = () => {
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-4 pb-24">
|
||||
{cartProducts.map((product) => (
|
||||
<MenuItemRenderer key={product.id}>
|
||||
<MenuItem product={product} />
|
||||
{cartProducts.map(({ product, variantId }) => (
|
||||
<MenuItemRenderer key={variantId}>
|
||||
<MenuItem product={product} variantId={variantId} />
|
||||
</MenuItemRenderer>
|
||||
))}
|
||||
<CartSummary isPremium />
|
||||
|
||||
@@ -7,12 +7,12 @@ export const bulkCart = async (params: BulkCartItem) => {
|
||||
};
|
||||
|
||||
export const increment = async (id: string | number) => {
|
||||
const { data } = await api.post(`/public/cart/product/${id}/increment`);
|
||||
const { data } = await api.post(`/public/cart/variant/${id}/increment`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const decrement = async (id: string | number) => {
|
||||
const { data } = await api.post(`/public/cart/product/${id}/decrement`);
|
||||
const { data } = await api.post(`/public/cart/variant/${id}/decrement`);
|
||||
return data;
|
||||
};
|
||||
|
||||
|
||||
@@ -2,12 +2,13 @@ import { BaseResponse } from "@/app/[name]/(Main)/types/Types";
|
||||
|
||||
export type BulkCartItem = {
|
||||
items: {
|
||||
productId: string;
|
||||
variantId: string;
|
||||
quantity: number;
|
||||
}[];
|
||||
};
|
||||
|
||||
export interface CartItem {
|
||||
/** شناسه واریانت (در API ممکن است هنوز productId نامیده شود) */
|
||||
productId: string;
|
||||
productTitle: string;
|
||||
quantity: number;
|
||||
|
||||
@@ -26,26 +26,28 @@ function ProductPage({ }: Props) {
|
||||
const { data: about } = useGetAbout();
|
||||
const { items, addToCart, removeFromCart } = useCart();
|
||||
const [isFavorite, setIsFavorite] = useState<boolean>(false);
|
||||
const [selectedVariantId, setSelectedVariantId] = useState<string | null>(null);
|
||||
const { mutate: toggleFavorite } = useToggleFavorite();
|
||||
|
||||
const productId = product?.data?.id || id as string;
|
||||
const quantity = useMemo(() => {
|
||||
const item = items?.[productId];
|
||||
if (!selectedVariantId) return 0;
|
||||
const item = items?.[selectedVariantId];
|
||||
if (item && typeof item === 'object' && 'quantity' in item) {
|
||||
return item.quantity || 0;
|
||||
}
|
||||
return 0;
|
||||
}, [items, productId]);
|
||||
}, [items, selectedVariantId]);
|
||||
|
||||
const handleAddToCart = () => {
|
||||
if (productId) {
|
||||
addToCart(productId);
|
||||
if (selectedVariantId) {
|
||||
addToCart(selectedVariantId);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveFromCart = () => {
|
||||
if (productId) {
|
||||
removeFromCart(productId);
|
||||
if (selectedVariantId) {
|
||||
removeFromCart(selectedVariantId);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -75,12 +77,20 @@ function ProductPage({ }: Props) {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (product?.data?.isFavorite) {
|
||||
setIsFavorite(true);
|
||||
} else {
|
||||
setIsFavorite(false);
|
||||
const typed = product as unknown as { data?: { isFavorite?: boolean } };
|
||||
const isFav = Boolean(typed?.data?.isFavorite);
|
||||
setIsFavorite(isFav);
|
||||
}, [product]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!product?.data) return;
|
||||
const variants = product.data.variants ?? [];
|
||||
if (!variants.length) {
|
||||
setSelectedVariantId(null);
|
||||
return;
|
||||
}
|
||||
}, [product?.data?.isFavorite]);
|
||||
setSelectedVariantId((prev) => prev ?? variants[0].id);
|
||||
}, [product?.data]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
@@ -99,7 +109,7 @@ function ProductPage({ }: Props) {
|
||||
}
|
||||
|
||||
const productData = product.data;
|
||||
const productName = productData.title || productData.name || productData.foodName || productData.productName || '';
|
||||
const productName = productData.title || productData.name || '';
|
||||
const productImage = typeof productData.image === 'string'
|
||||
? productData.image
|
||||
: Array.isArray(productData.images) && productData.images.length > 0
|
||||
@@ -110,8 +120,15 @@ function ProductPage({ }: Props) {
|
||||
: '/assets/images/no-image.png'
|
||||
: '/assets/images/no-image.png';
|
||||
const categoryName = productData.category?.title;
|
||||
const variants = productData.variants ?? [];
|
||||
const hasVariants = Boolean(productData.attribute?.trim());
|
||||
const selectedVariant = selectedVariantId
|
||||
? variants.find((v) => v.id === selectedVariantId) ?? variants[0]
|
||||
: variants[0];
|
||||
const price = hasVariants
|
||||
? (selectedVariant?.price ?? 0)
|
||||
: (productData.price ?? selectedVariant?.price ?? 0);
|
||||
const prepareTime = productData.prepareTime || 0;
|
||||
const price = productData.price || 0;
|
||||
const content = Array.isArray(productData.content)
|
||||
? productData.content.join('، ')
|
||||
: productData.content || productData.desc || '';
|
||||
@@ -186,7 +203,7 @@ function ProductPage({ }: Props) {
|
||||
<div>
|
||||
{ef(
|
||||
Math.round(
|
||||
productData?.price *
|
||||
price *
|
||||
(Number(about.data.score.purchaseScore) / Number(about.data.score.purchaseAmount))
|
||||
).toLocaleString('en-US')
|
||||
)}
|
||||
@@ -211,6 +228,28 @@ function ProductPage({ }: Props) {
|
||||
<p className='mt-2 leading-6'>{content || '-'}</p>
|
||||
</div>
|
||||
|
||||
{hasVariants && variants.length > 0 && (
|
||||
<div className='mt-6 text-xs'>
|
||||
<p className='font-bold'>{productData.attribute}</p>
|
||||
<div className='flex flex-wrap gap-2 mt-2'>
|
||||
{variants.map((variant) => (
|
||||
<button
|
||||
key={variant.id}
|
||||
type="button"
|
||||
onClick={() => setSelectedVariantId(variant.id)}
|
||||
className={`px-3 py-1 rounded-full border text-xs ${
|
||||
variant.id === selectedVariant?.id
|
||||
? 'bg-primary text-white border-primary'
|
||||
: 'bg-background text-foreground border-border'
|
||||
}`}
|
||||
>
|
||||
{variant.value}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='mt-12 flex justify-between items-center'>
|
||||
<span dir='ltr'>{ef(price.toLocaleString('en-US'))} T</span>
|
||||
<motion.div
|
||||
|
||||
@@ -20,64 +20,82 @@ export type CategoriesResponse = BaseResponse<Category[]>;
|
||||
|
||||
export type ProductImage = string | { url: string; alt?: string | null };
|
||||
|
||||
export interface ShopRef {
|
||||
/** Category as nested in product list API */
|
||||
export interface ProductCategory {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
parent: string | null;
|
||||
title: string;
|
||||
isActive: boolean;
|
||||
shop: string;
|
||||
avatarUrl: string;
|
||||
order: number;
|
||||
}
|
||||
|
||||
export interface InventoryRef {
|
||||
export interface ProductVariant {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
product: string;
|
||||
value: string;
|
||||
price: number;
|
||||
}
|
||||
|
||||
export type MealType = "breakfast" | "lunch" | "dinner" | "snack";
|
||||
|
||||
export interface Product {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
restaurant: ShopRef;
|
||||
category: Category;
|
||||
inventory: InventoryRef;
|
||||
shop: string;
|
||||
category: ProductCategory;
|
||||
attribute: string;
|
||||
title: string;
|
||||
desc: string;
|
||||
content: string[];
|
||||
price: number;
|
||||
order: number | null;
|
||||
prepareTime: number | null;
|
||||
weekDays: number[];
|
||||
mealTypes: MealType[];
|
||||
order: number;
|
||||
isActive: boolean;
|
||||
images: string[];
|
||||
inPlaceServe: boolean;
|
||||
pickupServe: boolean;
|
||||
score: number;
|
||||
score: number | null;
|
||||
discount: number;
|
||||
isSpecialOffer: boolean;
|
||||
reviews: unknown[];
|
||||
favorites: unknown[];
|
||||
isFavorite: boolean;
|
||||
// فیلدهای قدیمی برای سازگاری با API
|
||||
price: number | null;
|
||||
variants: ProductVariant[];
|
||||
/** Backward compatibility / other APIs */
|
||||
name?: string;
|
||||
foodName?: string;
|
||||
productName?: string;
|
||||
description?: string;
|
||||
content?: string[] | string | null;
|
||||
image?: string | null;
|
||||
inPlaceServe?: boolean;
|
||||
pickupServe?: boolean;
|
||||
points?: number | null;
|
||||
sat?: boolean;
|
||||
sun?: boolean;
|
||||
mon?: boolean;
|
||||
breakfast?: boolean;
|
||||
noon?: boolean;
|
||||
dinner?: boolean;
|
||||
stock?: number;
|
||||
stockDefault?: number;
|
||||
rate?: number;
|
||||
[key: string]: unknown;
|
||||
rate?: number | null;
|
||||
prepareTime?: number | null;
|
||||
isFavorite?: boolean;
|
||||
}
|
||||
|
||||
export type ProductsResponse = BaseResponse<Product[]>;
|
||||
export type ProductResponse = BaseResponse<Product>;
|
||||
|
||||
/** محصول بدون تنوع: attribute خالی → قیمت از اولین واریانت */
|
||||
export function hasVariants(product: Product): boolean {
|
||||
return Boolean(product.attribute?.trim());
|
||||
}
|
||||
|
||||
/** شناسهای که برای سبد/API استفاده میشود: همیشه variantId (اگر بدون تنوع، همان واریانت اول) */
|
||||
export function getPrimaryVariantId(product: Product): string | null {
|
||||
const first = product.variants?.[0];
|
||||
return first?.id ?? null;
|
||||
}
|
||||
|
||||
/** قیمت مؤثر: بدون تنوع = قیمت واریانت اول، با تنوع = product.price یا اولین واریانت */
|
||||
export function getProductEffectivePrice(product: Product): number {
|
||||
if (product.price != null && product.price > 0) return product.price;
|
||||
const first = product.variants?.[0];
|
||||
return first?.price ?? 0;
|
||||
}
|
||||
|
||||
export interface NotificationsCountData {
|
||||
count: number;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import MenuItem from '@/components/listview/MenuItem'
|
||||
import MenuItemRenderer from '@/components/listview/MenuItemRenderer'
|
||||
import VerticalScrollView from '@/components/listview/VerticalScrollView'
|
||||
import type { Favorite } from './types/Types'
|
||||
import type { Product } from '@/app/[name]/(Main)/types/Types'
|
||||
import type { Product, ProductCategory, ProductVariant } from '@/app/[name]/(Main)/types/Types'
|
||||
|
||||
function FavoritePage() {
|
||||
const router = useRouter()
|
||||
@@ -22,22 +22,40 @@ function FavoritePage() {
|
||||
return favorites.map((favorite: Favorite) => {
|
||||
const favProduct = favorite.food
|
||||
// تبدیل FavoriteProduct به Product برای استفاده در MenuItem
|
||||
const category: ProductCategory = {
|
||||
id: favProduct.category,
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
deletedAt: null,
|
||||
parent: null,
|
||||
title: '',
|
||||
isActive: true,
|
||||
shop: '',
|
||||
avatarUrl: '',
|
||||
order: 0,
|
||||
}
|
||||
const singleVariant: ProductVariant = {
|
||||
id: favProduct.id,
|
||||
createdAt: favProduct.createdAt,
|
||||
updatedAt: favProduct.updatedAt,
|
||||
deletedAt: favProduct.deletedAt,
|
||||
product: favProduct.id,
|
||||
value: '',
|
||||
price: favProduct.price,
|
||||
}
|
||||
const product: Product = {
|
||||
id: favProduct.id,
|
||||
createdAt: favProduct.createdAt,
|
||||
updatedAt: favProduct.updatedAt,
|
||||
deletedAt: favProduct.deletedAt,
|
||||
restaurant: favProduct.restaurant as unknown as Product['restaurant'],
|
||||
category: favProduct.category as unknown as Product['category'],
|
||||
inventory: {} as Product['inventory'],
|
||||
shop: favProduct.restaurant,
|
||||
category,
|
||||
attribute: '',
|
||||
title: favProduct.title,
|
||||
desc: favProduct.desc,
|
||||
content: favProduct.content,
|
||||
price: favProduct.price,
|
||||
order: favProduct.order,
|
||||
prepareTime: favProduct.prepareTime,
|
||||
weekDays: favProduct.weekDays,
|
||||
mealTypes: favProduct.mealTypes,
|
||||
isActive: favProduct.isActive,
|
||||
images: favProduct.images,
|
||||
inPlaceServe: favProduct.inPlaceServe,
|
||||
@@ -45,11 +63,9 @@ function FavoritePage() {
|
||||
score: favProduct.score,
|
||||
discount: favProduct.discount,
|
||||
isSpecialOffer: favProduct.isSpecialOffer,
|
||||
reviews: [],
|
||||
favorites: [],
|
||||
isFavorite: true,
|
||||
name: favProduct.title,
|
||||
description: favProduct.desc,
|
||||
variants: [singleVariant],
|
||||
}
|
||||
return product
|
||||
})
|
||||
|
||||
@@ -9,23 +9,38 @@ import MinusIcon from "@/components/icons/MinusIcon";
|
||||
import { useCart } from "@/app/[name]/(Dialogs)/cart/hook/useCart";
|
||||
import { motion } from "framer-motion";
|
||||
import type { Product, ProductImage } from "@/app/[name]/(Main)/types/Types";
|
||||
import {
|
||||
hasVariants,
|
||||
getPrimaryVariantId,
|
||||
getProductEffectivePrice,
|
||||
} from "@/app/[name]/(Main)/types/Types";
|
||||
|
||||
interface MenuItemProps {
|
||||
product: Product;
|
||||
/** در صفحه سبد هر خط با variantId مشخص میشود */
|
||||
variantId?: string | null;
|
||||
}
|
||||
|
||||
const MenuItem = ({ product }: MenuItemProps) => {
|
||||
const MenuItem = ({ product, variantId: variantIdProp }: MenuItemProps) => {
|
||||
const { items, addToCart, removeFromCart } = useCart();
|
||||
const params = useParams<{ name: string }>();
|
||||
const name = params?.name || '';
|
||||
const name = params?.name || "";
|
||||
const variantId = variantIdProp ?? getPrimaryVariantId(product);
|
||||
const selectedVariant = variantId
|
||||
? product.variants?.find((v) => v.id === variantId)
|
||||
: undefined;
|
||||
const withVariants = hasVariants(product);
|
||||
/** در لیست منو اگر تنوع داشت دکمه جزئیات؛ در سبد همیشه افزودن/کم کردن */
|
||||
const showDetailsInsteadOfAdd = withVariants && variantIdProp == null;
|
||||
|
||||
const quantity = useMemo(() => {
|
||||
const item = items?.[product.id];
|
||||
if (item && typeof item === 'object' && 'quantity' in item) {
|
||||
if (!variantId) return 0;
|
||||
const item = items?.[variantId];
|
||||
if (item && typeof item === "object" && "quantity" in item) {
|
||||
return item.quantity || 0;
|
||||
}
|
||||
return 0;
|
||||
}, [items, product.id]);
|
||||
}, [items, variantId]);
|
||||
|
||||
const fallbackImage = "/assets/images/no-image.png";
|
||||
const resolvedImage = useMemo(() => {
|
||||
@@ -40,8 +55,8 @@ const MenuItem = ({ product }: MenuItemProps) => {
|
||||
}, [product.image, product.images]);
|
||||
|
||||
const productName = useMemo(
|
||||
() => product.name || product.title || product.foodName || product.productName || 'بدون نام',
|
||||
[product.name, product.title, product.foodName, product.productName]
|
||||
() => product.name || product.title || 'بدون نام',
|
||||
[product.name, product.title]
|
||||
);
|
||||
|
||||
const productContent = useMemo(() => {
|
||||
@@ -61,23 +76,27 @@ const MenuItem = ({ product }: MenuItemProps) => {
|
||||
return desc.replace(/,/g, '،');
|
||||
}, [product.description, product.desc]);
|
||||
|
||||
const effectivePrice =
|
||||
selectedVariant != null
|
||||
? selectedVariant.price
|
||||
: getProductEffectivePrice(product);
|
||||
const finalPrice = useMemo(() => {
|
||||
const basePrice = product.price || 0;
|
||||
const discount = product.discount || 0;
|
||||
if (discount > 0) {
|
||||
return Math.max(0, basePrice - discount);
|
||||
return Math.max(0, effectivePrice - discount);
|
||||
}
|
||||
return basePrice;
|
||||
}, [product.price, product.discount]);
|
||||
return effectivePrice;
|
||||
}, [effectivePrice, product.discount]);
|
||||
|
||||
const formattedPrice = useMemo(
|
||||
() => (finalPrice ? finalPrice.toLocaleString('fa-IR') : '0'),
|
||||
() => (finalPrice ? finalPrice.toLocaleString("fa-IR") : "0"),
|
||||
[finalPrice]
|
||||
);
|
||||
|
||||
const formattedOriginalPrice = useMemo(
|
||||
() => (product.price ? product.price.toLocaleString('fa-IR') : '0'),
|
||||
[product.price]
|
||||
() =>
|
||||
effectivePrice ? effectivePrice.toLocaleString("fa-IR") : "0",
|
||||
[effectivePrice]
|
||||
);
|
||||
|
||||
const hasDiscount = useMemo(
|
||||
@@ -86,11 +105,11 @@ const MenuItem = ({ product }: MenuItemProps) => {
|
||||
);
|
||||
|
||||
const handleAddToCart = () => {
|
||||
addToCart(product.id);
|
||||
if (variantId) addToCart(variantId);
|
||||
};
|
||||
|
||||
const handleRemoveFromCart = () => {
|
||||
removeFromCart(product.id);
|
||||
if (variantId) removeFromCart(variantId);
|
||||
};
|
||||
|
||||
const productDetailUrl = `/${name}/${product.id}?category=${product.category.id}`;
|
||||
@@ -136,6 +155,14 @@ const MenuItem = ({ product }: MenuItemProps) => {
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{showDetailsInsteadOfAdd ? (
|
||||
<Link
|
||||
href={productDetailUrl}
|
||||
className="bg-background active:drop-shadow-xs max-w-[115px] rounded-md w-full h-8 inline-flex justify-center items-center gap-2 px-2 text-sm2 font-normal text-foreground"
|
||||
>
|
||||
جزئیات
|
||||
</Link>
|
||||
) : (
|
||||
<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"
|
||||
@@ -144,7 +171,6 @@ const MenuItem = ({ product }: MenuItemProps) => {
|
||||
<button
|
||||
onClick={handleAddToCart}
|
||||
className="inline-flex w-full justify-center items-center gap-2"
|
||||
|
||||
>
|
||||
<PlusIcon />
|
||||
<div className="text-sm2 pt-0.5 font-normal text-foreground">
|
||||
@@ -177,6 +203,7 @@ const MenuItem = ({ product }: MenuItemProps) => {
|
||||
</>
|
||||
)}
|
||||
</motion.div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -52,15 +52,15 @@ const StepOtp = ({ phone, slug }: Props) => {
|
||||
setRefreshToken(data?.data?.tokens?.refreshToken?.token as string)
|
||||
|
||||
if (Object.keys(items).length > 0) {
|
||||
const bulkCartItems = Object.entries(items).map(([key, value]) => {
|
||||
const bulkCartItems = Object.entries(items).map(([variantId, value]) => {
|
||||
if (value.quantity > 0) {
|
||||
return {
|
||||
productId: key,
|
||||
variantId,
|
||||
quantity: value.quantity
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}).filter((item): item is { productId: string; quantity: number } => item !== undefined)
|
||||
}).filter((item): item is { variantId: string; quantity: number } => item !== undefined)
|
||||
if (!!bulkCartItems.length) {
|
||||
mutateBulkCart({ items: bulkCartItems }, {
|
||||
onSuccess: () => {
|
||||
|
||||
Reference in New Issue
Block a user