fix bulk + fix variand
This commit is contained in:
@@ -14,8 +14,8 @@ export const useCart = () => {
|
|||||||
const params = useParams();
|
const params = useParams();
|
||||||
const currentSlug = (params.name as string) || null;
|
const currentSlug = (params.name as string) || null;
|
||||||
const { clear, increment, decrement, items, slug, setSlug } = useReceiptStore();
|
const { clear, increment, decrement, items, slug, setSlug } = useReceiptStore();
|
||||||
const { mutate: mutateIncrementCart } = useIncrementCart();
|
const { mutate: mutateIncrementCart, isPending: isIncrementPending } = useIncrementCart();
|
||||||
const { mutate: mutateDecrementCart } = useDecrementCart();
|
const { mutate: mutateDecrementCart, isPending: isDecrementPending } = useDecrementCart();
|
||||||
const { mutate: mutateClearCart } = useClearCart();
|
const { mutate: mutateClearCart } = useClearCart();
|
||||||
const { data: cartItems } = useGetCartItems(isSuccess);
|
const { data: cartItems } = useGetCartItems(isSuccess);
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ export const useCart = () => {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
return cartItems.data.items.reduce((acc, item) => {
|
return cartItems.data.items.reduce((acc, item) => {
|
||||||
acc[item.productId] = { quantity: item.quantity };
|
acc[item.variantId] = { quantity: item.quantity };
|
||||||
return acc;
|
return acc;
|
||||||
}, {} as Record<string | number, { quantity: number }>);
|
}, {} as Record<string | number, { quantity: number }>);
|
||||||
} else {
|
} else {
|
||||||
@@ -61,9 +61,7 @@ export const useCart = () => {
|
|||||||
}
|
}
|
||||||
}, [isSuccess, cartItems?.data?.items, items]);
|
}, [isSuccess, cartItems?.data?.items, items]);
|
||||||
|
|
||||||
const isItemLoading = () => {
|
const isCartMutating = isIncrementPending || isDecrementPending;
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// برای کاربران لاگین شده، slug از API میآید (در cartItems.data.shopId)
|
// برای کاربران لاگین شده، slug از API میآید (در cartItems.data.shopId)
|
||||||
// برای کاربران لاگین نشده، slug از receiptStore میآید
|
// برای کاربران لاگین نشده، slug از receiptStore میآید
|
||||||
@@ -83,6 +81,6 @@ export const useCart = () => {
|
|||||||
addToCart,
|
addToCart,
|
||||||
removeFromCart,
|
removeFromCart,
|
||||||
clearCart,
|
clearCart,
|
||||||
isItemLoading,
|
isCartMutating,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export const useIncrementCart = () => {
|
|||||||
|
|
||||||
if (previousCart?.data && productsData?.data) {
|
if (previousCart?.data && productsData?.data) {
|
||||||
const existingItem = previousCart.data.items.find(
|
const existingItem = previousCart.data.items.find(
|
||||||
(item) => item.productId === String(id)
|
(item) => item.variantId === String(id)
|
||||||
);
|
);
|
||||||
|
|
||||||
let updatedItems: CartItem[];
|
let updatedItems: CartItem[];
|
||||||
@@ -37,7 +37,7 @@ export const useIncrementCart = () => {
|
|||||||
|
|
||||||
if (existingItem) {
|
if (existingItem) {
|
||||||
updatedItems = previousCart.data.items.map((item) => {
|
updatedItems = previousCart.data.items.map((item) => {
|
||||||
if (item.productId === String(id)) {
|
if (item.variantId === String(id)) {
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
quantity: item.quantity + 1,
|
quantity: item.quantity + 1,
|
||||||
@@ -58,7 +58,7 @@ export const useIncrementCart = () => {
|
|||||||
const price = variant.price ?? effectivePrice;
|
const price = variant.price ?? effectivePrice;
|
||||||
const discount = product.discount ?? 0;
|
const discount = product.discount ?? 0;
|
||||||
const newItem: CartItem = {
|
const newItem: CartItem = {
|
||||||
productId: variantId,
|
variantId,
|
||||||
productTitle: product.title || product.name || "",
|
productTitle: product.title || product.name || "",
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
price,
|
price,
|
||||||
@@ -124,7 +124,7 @@ export const useDecrementCart = () => {
|
|||||||
if (previousCart?.data) {
|
if (previousCart?.data) {
|
||||||
const updatedItems = previousCart.data.items
|
const updatedItems = previousCart.data.items
|
||||||
.map((item: CartItem) => {
|
.map((item: CartItem) => {
|
||||||
if (item.productId === String(id)) {
|
if (item.variantId === String(id)) {
|
||||||
const newQuantity = item.quantity - 1;
|
const newQuantity = item.quantity - 1;
|
||||||
if (newQuantity <= 0) {
|
if (newQuantity <= 0) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { api } from "@/config/axios";
|
|||||||
import { BulkCartItem, CartResponse, SaveAllMethod } from "../types/Types";
|
import { BulkCartItem, CartResponse, SaveAllMethod } from "../types/Types";
|
||||||
|
|
||||||
export const bulkCart = async (params: BulkCartItem) => {
|
export const bulkCart = async (params: BulkCartItem) => {
|
||||||
const { data } = await api.post("/public/cart/items/bulk", params);
|
const { data } = await api.post("/public/cart/variant/bulk", params);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ export type BulkCartItem = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface CartItem {
|
export interface CartItem {
|
||||||
/** شناسه واریانت (در API ممکن است هنوز productId نامیده شود) */
|
variantId: string;
|
||||||
productId: string;
|
|
||||||
productTitle: string;
|
productTitle: string;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
price: number;
|
price: number;
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import MinusIcon from '@/components/icons/MinusIcon'
|
import { CartQuantityControl } from '@/components/CartQuantityControl'
|
||||||
import PlusIcon from '@/components/icons/PlusIcon'
|
|
||||||
import { ef } from '@/lib/helpers/utfNumbers'
|
import { ef } from '@/lib/helpers/utfNumbers'
|
||||||
import { useCart } from '@/app/[name]/(Dialogs)/cart/hook/useCart'
|
import { useCart } from '@/app/[name]/(Dialogs)/cart/hook/useCart'
|
||||||
import { motion } from 'framer-motion'
|
import { getPrimaryVariantId } from '@/app/[name]/(Main)/types/Types'
|
||||||
import { ArrowLeft, Clock, Cup, Heart, TruckTick } from 'iconsax-react'
|
import { ArrowLeft, Clock, Cup, Heart, TruckTick } from 'iconsax-react'
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import { useParams, useRouter } from 'next/navigation'
|
import { useParams, useRouter } from 'next/navigation'
|
||||||
@@ -24,30 +23,34 @@ function ProductPage({ }: Props) {
|
|||||||
const { isSuccess } = useGetProfile();
|
const { isSuccess } = useGetProfile();
|
||||||
const { data: product, isLoading } = useGetProduct(id as string);
|
const { data: product, isLoading } = useGetProduct(id as string);
|
||||||
const { data: about } = useGetAbout();
|
const { data: about } = useGetAbout();
|
||||||
const { items, addToCart, removeFromCart } = useCart();
|
const { items, addToCart, removeFromCart, isCartMutating } = useCart();
|
||||||
const [isFavorite, setIsFavorite] = useState<boolean>(false);
|
const [isFavorite, setIsFavorite] = useState<boolean>(false);
|
||||||
const [selectedVariantId, setSelectedVariantId] = useState<string | null>(null);
|
const [selectedVariantId, setSelectedVariantId] = useState<string | null>(null);
|
||||||
const { mutate: toggleFavorite } = useToggleFavorite();
|
const { mutate: toggleFavorite } = useToggleFavorite();
|
||||||
|
|
||||||
const productId = product?.data?.id || id as string;
|
const productId = product?.data?.id || id as string;
|
||||||
|
const cartId = useMemo(() => {
|
||||||
|
if (!product?.data) return null;
|
||||||
|
return selectedVariantId ?? getPrimaryVariantId(product.data) ?? productId;
|
||||||
|
}, [product?.data, selectedVariantId, productId]);
|
||||||
const quantity = useMemo(() => {
|
const quantity = useMemo(() => {
|
||||||
if (!selectedVariantId) return 0;
|
if (!cartId) return 0;
|
||||||
const item = items?.[selectedVariantId];
|
const item = items?.[cartId] ?? items?.[String(cartId)];
|
||||||
if (item && typeof item === 'object' && 'quantity' in item) {
|
if (item && typeof item === 'object' && 'quantity' in item) {
|
||||||
return item.quantity || 0;
|
return item.quantity || 0;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}, [items, selectedVariantId]);
|
}, [items, cartId]);
|
||||||
|
|
||||||
const handleAddToCart = () => {
|
const handleAddToCart = () => {
|
||||||
if (selectedVariantId) {
|
if (cartId) {
|
||||||
addToCart(selectedVariantId);
|
addToCart(cartId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveFromCart = () => {
|
const handleRemoveFromCart = () => {
|
||||||
if (selectedVariantId) {
|
if (cartId) {
|
||||||
removeFromCart(selectedVariantId);
|
removeFromCart(cartId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -86,7 +89,7 @@ function ProductPage({ }: Props) {
|
|||||||
if (!product?.data) return;
|
if (!product?.data) return;
|
||||||
const variants = product.data.variants ?? [];
|
const variants = product.data.variants ?? [];
|
||||||
if (!variants.length) {
|
if (!variants.length) {
|
||||||
setSelectedVariantId(null);
|
setSelectedVariantId(product.data.id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setSelectedVariantId((prev) => prev ?? variants[0].id);
|
setSelectedVariantId((prev) => prev ?? variants[0].id);
|
||||||
@@ -252,46 +255,13 @@ function ProductPage({ }: Props) {
|
|||||||
|
|
||||||
<div className='mt-12 flex justify-between items-center'>
|
<div className='mt-12 flex justify-between items-center'>
|
||||||
<span dir='ltr'>{ef(price.toLocaleString('en-US'))} T</span>
|
<span dir='ltr'>{ef(price.toLocaleString('en-US'))} T</span>
|
||||||
<motion.div
|
<CartQuantityControl
|
||||||
whileTap={{ scale: 1.05 }}
|
quantity={quantity}
|
||||||
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"
|
onAdd={handleAddToCart}
|
||||||
>
|
onRemove={handleRemoveFromCart}
|
||||||
{quantity <= 0 ? (
|
isMutating={isCartMutating}
|
||||||
<button
|
addDisabled={!cartId}
|
||||||
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">
|
|
||||||
افزودن
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<button
|
|
||||||
onClick={handleAddToCart}
|
|
||||||
className="bg-container hover:bg-container/60 active:bg-container/30 rounded-sm p-2 transition-colors"
|
|
||||||
>
|
|
||||||
<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}
|
|
||||||
className="bg-container hover:bg-container/60 active:bg-container/30 rounded-sm p-2 transition-colors"
|
|
||||||
>
|
|
||||||
<MinusIcon className="text-foreground" />
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { motion } from 'framer-motion'
|
||||||
|
import MinusIcon from '@/components/icons/MinusIcon'
|
||||||
|
import PlusIcon from '@/components/icons/PlusIcon'
|
||||||
|
|
||||||
|
export interface CartQuantityControlProps {
|
||||||
|
quantity: number
|
||||||
|
onAdd: () => void
|
||||||
|
onRemove: () => void
|
||||||
|
/** در حال ارسال درخواست به سرور (نمایش اسپینر بهجای آیکون) */
|
||||||
|
isMutating?: boolean
|
||||||
|
/** غیرفعال کردن دکمه افزودن (مثلاً وقتی variant انتخاب نشده) */
|
||||||
|
addDisabled?: boolean
|
||||||
|
/** متن دکمه وقتی quantity صفر است */
|
||||||
|
addLabel?: string
|
||||||
|
/** متن دکمه در حالت loading */
|
||||||
|
addingLabel?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Spinner = () => (
|
||||||
|
<span className="size-4 border-2 border-foreground/30 border-t-foreground rounded-full animate-spin block" />
|
||||||
|
)
|
||||||
|
|
||||||
|
export function CartQuantityControl({
|
||||||
|
quantity,
|
||||||
|
onAdd,
|
||||||
|
onRemove,
|
||||||
|
isMutating = false,
|
||||||
|
addDisabled = false,
|
||||||
|
addLabel = 'افزودن',
|
||||||
|
addingLabel = 'در حال افزودن...',
|
||||||
|
}: CartQuantityControlProps) {
|
||||||
|
return (
|
||||||
|
<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"
|
||||||
|
>
|
||||||
|
{quantity <= 0 ? (
|
||||||
|
<button
|
||||||
|
onClick={onAdd}
|
||||||
|
disabled={isMutating || addDisabled}
|
||||||
|
className="inline-flex w-full justify-center items-center gap-2 disabled:opacity-60 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
{isMutating ? <Spinner /> : <PlusIcon />}
|
||||||
|
<div className="text-sm2 pt-0.5 font-normal text-foreground">
|
||||||
|
{isMutating ? addingLabel : addLabel}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={onAdd}
|
||||||
|
disabled={isMutating}
|
||||||
|
className="bg-container hover:bg-container/60 active:bg-container/30 rounded-sm p-2 transition-colors disabled:opacity-60 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
{isMutating ? <Spinner /> : <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={onRemove}
|
||||||
|
disabled={isMutating}
|
||||||
|
className="bg-container hover:bg-container/60 active:bg-container/30 rounded-sm p-2 transition-colors disabled:opacity-60 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
{isMutating ? <Spinner /> : <MinusIcon className="text-foreground" />}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</motion.div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -4,10 +4,8 @@
|
|||||||
import { memo, useMemo } from "react";
|
import { memo, useMemo } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import PlusIcon from "@/components/icons/PlusIcon";
|
import { CartQuantityControl } from "@/components/CartQuantityControl";
|
||||||
import MinusIcon from "@/components/icons/MinusIcon";
|
|
||||||
import { useCart } from "@/app/[name]/(Dialogs)/cart/hook/useCart";
|
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 type { Product, ProductImage } from "@/app/[name]/(Main)/types/Types";
|
||||||
import {
|
import {
|
||||||
hasVariants,
|
hasVariants,
|
||||||
@@ -22,7 +20,7 @@ interface MenuItemProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const MenuItem = ({ product, variantId: variantIdProp }: MenuItemProps) => {
|
const MenuItem = ({ product, variantId: variantIdProp }: MenuItemProps) => {
|
||||||
const { items, addToCart, removeFromCart } = useCart();
|
const { items, addToCart, removeFromCart, isCartMutating } = useCart();
|
||||||
const params = useParams<{ name: string }>();
|
const params = useParams<{ name: string }>();
|
||||||
const name = params?.name || "";
|
const name = params?.name || "";
|
||||||
const variantId = variantIdProp ?? getPrimaryVariantId(product);
|
const variantId = variantIdProp ?? getPrimaryVariantId(product);
|
||||||
@@ -163,46 +161,12 @@ const MenuItem = ({ product, variantId: variantIdProp }: MenuItemProps) => {
|
|||||||
جزئیات
|
جزئیات
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<motion.div
|
<CartQuantityControl
|
||||||
whileTap={{ scale: 1.05 }}
|
quantity={quantity}
|
||||||
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"
|
onAdd={handleAddToCart}
|
||||||
>
|
onRemove={handleRemoveFromCart}
|
||||||
{quantity <= 0 ? (
|
isMutating={isCartMutating}
|
||||||
<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">
|
|
||||||
افزودن
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<button
|
|
||||||
onClick={handleAddToCart}
|
|
||||||
className="bg-container hover:bg-container/60 active:bg-container/30 rounded-sm p-2 transition-colors"
|
|
||||||
>
|
|
||||||
<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}
|
|
||||||
className="bg-container hover:bg-container/60 active:bg-container/30 rounded-sm p-2 transition-colors"
|
|
||||||
>
|
|
||||||
<MinusIcon className="text-foreground" />
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</motion.div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+2
-3
@@ -1,6 +1,5 @@
|
|||||||
// export const API_BASE_URL = "https://dmenuplus-api.dev.danakcorp.com";
|
// export const API_BASE_URL = "https://dmenuplus-api.dev.danakcorp.com";
|
||||||
// export const API_BASE_URL =
|
// export const API_BASE_URL = "https://dkala-api.danakcorp.com";
|
||||||
// "https://dmenuplus-api-production.dev.danakcorp.com";
|
export const API_BASE_URL = "http://192.168.99.209:4000";
|
||||||
export const API_BASE_URL = "http://10.86.60.88:4000";
|
|
||||||
export const TOKEN_NAME = "dmenu-t";
|
export const TOKEN_NAME = "dmenu-t";
|
||||||
export const REFRESH_TOKEN_NAME = "dmenu-rt";
|
export const REFRESH_TOKEN_NAME = "dmenu-rt";
|
||||||
|
|||||||
Reference in New Issue
Block a user