cart item
This commit is contained in:
@@ -120,7 +120,7 @@ const BlogDetail: NextPage = () => {
|
|||||||
|
|
||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
<div className="w-80 space-y-8">
|
<div className="w-80 space-y-8">
|
||||||
<LastPost />
|
<LastPost blogs={[]} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ const BrandFilterSection: FC<BrandFilterSectionProps> = ({
|
|||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
<div className='mt-4'>
|
<div className='mt-4'>
|
||||||
<CategoryTree
|
<CategoryTree
|
||||||
categories={categories as any}
|
categories={categories}
|
||||||
selectedId={filterState.categories[0] || null}
|
selectedId={filterState.categories[0] || null}
|
||||||
onSelect={(id) => id && onCategoryChange(id)}
|
onSelect={(id) => id && onCategoryChange(id)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Layout from '@/hoc/Layout'
|
|||||||
import { NextPage } from 'next'
|
import { NextPage } from 'next'
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import { useSearchParams } from 'next/navigation'
|
import { useSearchParams } from 'next/navigation'
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState, Suspense } from 'react';
|
||||||
import { useGetCompare, useSearchCompareProduct } from './hooks/useCompareData'
|
import { useGetCompare, useSearchCompareProduct } from './hooks/useCompareData'
|
||||||
import { useLocalCart } from '@/app/product/hooks/useLocalCart'
|
import { useLocalCart } from '@/app/product/hooks/useLocalCart'
|
||||||
import { toast } from '@/components/Toast'
|
import { toast } from '@/components/Toast'
|
||||||
@@ -13,7 +13,7 @@ import { CompareProduct } from './types/Types'
|
|||||||
import { useSharedStore } from '@/share/store/sharedStore'
|
import { useSharedStore } from '@/share/store/sharedStore'
|
||||||
import DefaulModal from '@/components/DefaulModal'
|
import DefaulModal from '@/components/DefaulModal'
|
||||||
|
|
||||||
const ComparePage: NextPage = () => {
|
const ComparePageContent: NextPage = () => {
|
||||||
|
|
||||||
const [productIds, setProductIds] = useState<string[]>([]);
|
const [productIds, setProductIds] = useState<string[]>([]);
|
||||||
const [isCompareModalOpen, setIsCompareModalOpen] = useState(false);
|
const [isCompareModalOpen, setIsCompareModalOpen] = useState(false);
|
||||||
@@ -282,4 +282,23 @@ const ComparePage: NextPage = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ComparePage: NextPage = () => {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={
|
||||||
|
<Layout>
|
||||||
|
<div className='mt-24 px-20'>
|
||||||
|
<div className='flex justify-center items-center min-h-[400px]'>
|
||||||
|
<div className='text-center'>
|
||||||
|
<div className='animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto'></div>
|
||||||
|
<p className='mt-4 text-gray-600'>در حال بارگذاری...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
}>
|
||||||
|
<ComparePageContent />
|
||||||
|
</Suspense>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default ComparePage
|
export default ComparePage
|
||||||
@@ -7,16 +7,38 @@ import {
|
|||||||
import { useGetCart } from '@/app/cart/hooks/useCartData'
|
import { useGetCart } from '@/app/cart/hooks/useCartData'
|
||||||
import { useRemoveFromCart } from '@/app/cart/hooks/useCartData'
|
import { useRemoveFromCart } from '@/app/cart/hooks/useCartData'
|
||||||
import { CartItemType } from '@/app/cart/types/Types'
|
import { CartItemType } from '@/app/cart/types/Types'
|
||||||
|
import { LocalCartItem } from '@/app/product/store/LocalCartStore'
|
||||||
|
|
||||||
|
// Union type for all possible cart items
|
||||||
|
type CartItem = CartItemType | EnrichedOfflineCartItem
|
||||||
import { NumberFormat } from '@/config/func'
|
import { NumberFormat } from '@/config/func'
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { useSharedStore } from '@/share/store/sharedStore'
|
||||||
|
import { useLocalCart } from '@/app/product/hooks/useLocalCart'
|
||||||
|
import { useOfflineCartProducts, EnrichedOfflineCartItem } from '@/share/hooks/useOfflineCartProducts'
|
||||||
|
|
||||||
const Cart = () => {
|
const Cart = () => {
|
||||||
|
const { isLogin } = useSharedStore()
|
||||||
const { data: cartData, isLoading } = useGetCart()
|
const { data: cartData, isLoading } = useGetCart()
|
||||||
const removeFromCartMutation = useRemoveFromCart()
|
const removeFromCartMutation = useRemoveFromCart()
|
||||||
|
|
||||||
|
// Local cart hooks for offline users
|
||||||
|
const {
|
||||||
|
localCartItems,
|
||||||
|
removeFromLocalCart,
|
||||||
|
getLocalCartItemsCount
|
||||||
|
} = useLocalCart()
|
||||||
|
|
||||||
|
// Get enriched product data for offline cart items
|
||||||
|
const {
|
||||||
|
enrichedCartItems,
|
||||||
|
isLoading: isLoadingOfflineProducts
|
||||||
|
} = useOfflineCartProducts(localCartItems)
|
||||||
|
|
||||||
const handleRemoveItem = async (productId: number, variantId: string) => {
|
const handleRemoveItem = async (productId: number, variantId: string) => {
|
||||||
|
if (isLogin) {
|
||||||
try {
|
try {
|
||||||
await removeFromCartMutation.mutateAsync({
|
await removeFromCartMutation.mutateAsync({
|
||||||
productId,
|
productId,
|
||||||
@@ -25,11 +47,20 @@ const Cart = () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error removing item from cart:', error)
|
console.error('Error removing item from cart:', error)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
removeFromLocalCart(productId, variantId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get cart data based on login status
|
||||||
const cart = cartData?.results?.cart
|
const cart = cartData?.results?.cart
|
||||||
const items = cart?.items || []
|
const onlineItems = cart?.items || []
|
||||||
const itemsCount = cart?.items_count || 0
|
const onlineItemsCount = cart?.items_count || 0
|
||||||
|
|
||||||
|
// Use appropriate data based on login status
|
||||||
|
const items = isLogin ? onlineItems : enrichedCartItems
|
||||||
|
const itemsCount = isLogin ? onlineItemsCount : getLocalCartItemsCount()
|
||||||
|
const isItemsLoading = isLogin ? isLoading : isLoadingOfflineProducts
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover>
|
<Popover>
|
||||||
@@ -56,14 +87,18 @@ const Cart = () => {
|
|||||||
|
|
||||||
{/* Cart Items */}
|
{/* Cart Items */}
|
||||||
<div className='space-y-3 mb-4'>
|
<div className='space-y-3 mb-4'>
|
||||||
{isLoading ? (
|
{isItemsLoading ? (
|
||||||
<div className='text-center py-4 text-gray-500'>در حال بارگذاری...</div>
|
<div className='text-center py-4 text-gray-500'>در حال بارگذاری...</div>
|
||||||
) : items.length > 0 ? (
|
) : items.length > 0 ? (
|
||||||
items.slice(0, 3).map((item: CartItemType) => (
|
items.slice(0, 3).map((item: CartItem, index: number) => (
|
||||||
<CartItemCard
|
<CartItemCard
|
||||||
key={`${item.product._id}-${item.variant._id}`}
|
key={isLogin ? `${(item as CartItemType).product._id}-${(item as CartItemType).variant._id}` : `${(item as LocalCartItem).productId}-${(item as LocalCartItem).variantId}-${index}`}
|
||||||
item={item}
|
item={item}
|
||||||
onRemove={() => handleRemoveItem(item.product._id, item.variant._id)}
|
isOnline={isLogin}
|
||||||
|
onRemove={() => handleRemoveItem(
|
||||||
|
isLogin ? (item as CartItemType).product._id : (item as LocalCartItem).productId,
|
||||||
|
isLogin ? (item as CartItemType).variant._id : (item as LocalCartItem).variantId
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
@@ -86,14 +121,46 @@ const Cart = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cart Item Card Component
|
// Cart Item Card Component
|
||||||
const CartItemCard = ({ item, onRemove }: { item: CartItemType, onRemove: () => void }) => {
|
const CartItemCard = ({
|
||||||
|
item,
|
||||||
|
isOnline,
|
||||||
|
onRemove
|
||||||
|
}: {
|
||||||
|
item: CartItem,
|
||||||
|
isOnline: boolean,
|
||||||
|
onRemove: () => void
|
||||||
|
}) => {
|
||||||
|
// Handle both online and offline items with enriched data
|
||||||
|
const productTitle = isOnline
|
||||||
|
? (item as CartItemType).product.title_fa
|
||||||
|
: (item as EnrichedOfflineCartItem).product?.title_fa || 'در حال بارگذاری...'
|
||||||
|
|
||||||
|
const productImage = isOnline
|
||||||
|
? (item as CartItemType).product.imagesUrl.cover
|
||||||
|
: (item as EnrichedOfflineCartItem).product?.imagesUrl?.cover || '/images/logo.png'
|
||||||
|
|
||||||
|
const productCategory = isOnline
|
||||||
|
? ((item as CartItemType).product.category?._id || 'دستهبندی')
|
||||||
|
: ((item as EnrichedOfflineCartItem).product?.category?._id || 'دستهبندی')
|
||||||
|
|
||||||
|
const productPrice = isOnline
|
||||||
|
? (item as CartItemType).variant.price.selling_price
|
||||||
|
: (item as EnrichedOfflineCartItem).variant?.price?.selling_price || 0
|
||||||
|
|
||||||
|
const quantity = isOnline
|
||||||
|
? (item as CartItemType).quantity
|
||||||
|
: (item as LocalCartItem).quantity
|
||||||
|
|
||||||
|
// Check if product data is still loading for offline items
|
||||||
|
const isProductLoading = !isOnline && !(item as EnrichedOfflineCartItem).product
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='bg-white rounded-lg p-3 flex gap-3 shadow-sm border border-gray-100'>
|
<div className='bg-white rounded-lg p-3 flex gap-3 shadow-sm border border-gray-100'>
|
||||||
{/* Product Image */}
|
{/* Product Image */}
|
||||||
<div className='flex-shrink-0'>
|
<div className='flex-shrink-0'>
|
||||||
<Image
|
<Image
|
||||||
src={item.product.imagesUrl.cover}
|
src={productImage}
|
||||||
alt={item.product.title_fa}
|
alt={productTitle}
|
||||||
width={60}
|
width={60}
|
||||||
height={60}
|
height={60}
|
||||||
className='w-15 h-15 object-contain rounded'
|
className='w-15 h-15 object-contain rounded'
|
||||||
@@ -103,14 +170,18 @@ const CartItemCard = ({ item, onRemove }: { item: CartItemType, onRemove: () =>
|
|||||||
{/* Product Details */}
|
{/* Product Details */}
|
||||||
<div className='flex-1 min-w-0'>
|
<div className='flex-1 min-w-0'>
|
||||||
<div className='text-xs text-gray-500 mb-1'>
|
<div className='text-xs text-gray-500 mb-1'>
|
||||||
{item.product.category?._id || 'دستهبندی'}
|
{productCategory}
|
||||||
</div>
|
</div>
|
||||||
<div className='text-sm text-gray-800 mb-2 line-clamp-2'>
|
<div className='text-sm text-gray-800 mb-2 line-clamp-2'>
|
||||||
{item.product.title_fa}
|
{isProductLoading ? 'در حال بارگذاری...' : productTitle}
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center justify-between'>
|
<div className='flex items-center justify-between'>
|
||||||
<div className='text-sm font-semibold text-gray-900'>
|
<div className='text-sm font-semibold text-gray-900'>
|
||||||
{NumberFormat(item.variant.price.selling_price)} تومان
|
{isProductLoading ? (
|
||||||
|
`${quantity} عدد`
|
||||||
|
) : (
|
||||||
|
`${NumberFormat(productPrice)} تومان`
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={onRemove}
|
onClick={onRemove}
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { getProduct } from "@/app/product/service/Service";
|
||||||
|
import {
|
||||||
|
ProductDetailResponse,
|
||||||
|
Product,
|
||||||
|
ProductVariant,
|
||||||
|
} from "@/types/product.types";
|
||||||
|
import { LocalCartItem } from "@/app/product/store/LocalCartStore";
|
||||||
|
|
||||||
|
// Type for enriched offline cart item
|
||||||
|
export type EnrichedOfflineCartItem = LocalCartItem & {
|
||||||
|
product: Product | null;
|
||||||
|
variant: ProductVariant | null;
|
||||||
|
isLoading: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useOfflineCartProducts = (localCartItems: LocalCartItem[]) => {
|
||||||
|
// Get unique product IDs from local cart items
|
||||||
|
const productIds = [
|
||||||
|
...new Set(localCartItems.map((item) => item.productId.toString())),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Use a single query with multiple product IDs
|
||||||
|
const { data: productsData, isLoading, error } = useQuery<
|
||||||
|
Map<string, ProductDetailResponse["results"]>
|
||||||
|
>({
|
||||||
|
queryKey: ["offline-cart-products", productIds],
|
||||||
|
queryFn: async () => {
|
||||||
|
if (productIds.length === 0) return new Map();
|
||||||
|
|
||||||
|
// Fetch all products in parallel
|
||||||
|
const productPromises = productIds.map(async (productId) => {
|
||||||
|
try {
|
||||||
|
const response = await getProduct(productId);
|
||||||
|
return { productId, data: response.results };
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to fetch product ${productId}:`, error);
|
||||||
|
return { productId, data: null };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const results = await Promise.all(productPromises);
|
||||||
|
const productMap = new Map<string, ProductDetailResponse["results"]>();
|
||||||
|
|
||||||
|
results.forEach(({ productId, data }) => {
|
||||||
|
if (data) {
|
||||||
|
productMap.set(productId, data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return productMap;
|
||||||
|
},
|
||||||
|
enabled: productIds.length > 0,
|
||||||
|
staleTime: 1000 * 60 * 10, // 10 minutes
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create enriched cart items with product data
|
||||||
|
const enrichedCartItems = localCartItems.map((item) => {
|
||||||
|
const productData = productsData?.get(item.productId.toString());
|
||||||
|
|
||||||
|
if (!productData) {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
product: null,
|
||||||
|
variant: null,
|
||||||
|
isLoading: isLoading,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the variant that matches the variantId
|
||||||
|
const variant = productData.product.variants?.find(
|
||||||
|
(v) => v._id === item.variantId
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
product: productData.product,
|
||||||
|
variant: variant || null,
|
||||||
|
isLoading: false,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
enrichedCartItems,
|
||||||
|
isLoading,
|
||||||
|
hasError: !!error,
|
||||||
|
productMap: productsData || new Map(),
|
||||||
|
};
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user