Files
shop-front/src/share/components/Cart.tsx
T
2025-10-29 10:41:57 +03:30

253 lines
11 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
import { ShoppingCart, Trash } from 'iconsax-react'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
import { useGetCart, useUpdateCart } from '@/app/cart/hooks/useCartData'
import { useRemoveFromCart } from '@/app/cart/hooks/useCartData'
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 Image from 'next/image'
import Link from 'next/link'
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'
import { useEffect, useRef } from 'react'
const Cart = () => {
const { isLogin } = useSharedStore()
const { data: cartData, isLoading } = useGetCart()
const removeFromCartMutation = useRemoveFromCart()
const updateCartMutation = useUpdateCart()
// Local cart hooks for offline users
const {
localCartItems,
removeFromLocalCart,
getLocalCartItemsCount,
clearLocalCart
} = useLocalCart()
// Track previous login state to detect login events
const prevIsLoginRef = useRef(isLogin)
// Get enriched product data for offline cart items
const {
enrichedCartItems,
isLoading: isLoadingOfflineProducts
} = useOfflineCartProducts(localCartItems)
// Sync local cart to server when user logs in
useEffect(() => {
const prevIsLogin = prevIsLoginRef.current
prevIsLoginRef.current = isLogin
// Check if user just logged in and has local cart items
if (!prevIsLogin && isLogin && localCartItems.length > 0) {
// Add each local cart item to server cart
const syncPromises = localCartItems.map(async (item) => {
try {
await updateCartMutation.mutateAsync({
productId: item.productId,
variantId: item.variantId,
quantity: item.quantity
})
} catch (error) {
console.error('Failed to sync cart item:', item, error)
}
})
// Clear local cart after sync attempt
Promise.all(syncPromises).finally(() => {
clearLocalCart()
})
}
}, [isLogin, localCartItems, updateCartMutation, clearLocalCart])
const handleRemoveItem = async (productId: number, variantId: string) => {
if (isLogin) {
await removeFromCartMutation.mutateAsync({
productId,
variantId
})
} else {
removeFromLocalCart(productId, variantId)
}
}
// Get cart data based on login status
const cart = cartData?.results?.cart
const onlineItems = cart?.items || []
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 (
<Popover>
<PopoverTrigger asChild>
<div className='h-10 p-3 rounded-[12px] border border-border flex items-center gap-2.5 relative'>
<ShoppingCart size={20} color='#333333' />
{itemsCount > 0 && (
<div className='absolute -top-2 -right-2 bg-primary text-white text-xs rounded-full w-5 h-5 flex items-center justify-center'>
{itemsCount}
</div>
)}
</div>
</PopoverTrigger>
<PopoverContent className="w-[350px] ml-2 z-[9999] p-0 max-h-[600px] overflow-y-auto" style={{ left: 4 }}>
<div className='p-4'>
{/* Header */}
{
itemsCount > 0 && (
<div className='text-primary text-sm font-medium mb-4'>
{itemsCount} کالا در سبد خرید شما ثبت شده
</div>
)
}
{/* Cart Items */}
<div className='space-y-3 mb-4'>
{isItemsLoading ? (
<div className='text-center py-4 text-gray-500'>در حال بارگذاری...</div>
) : items.length > 0 ? (
items.slice(0, 3).map((item: CartItem, index: number) => (
<CartItemCard
key={isLogin ? `${(item as CartItemType).product._id}-${(item as CartItemType).variant._id}` : `${(item as LocalCartItem).productId}-${(item as LocalCartItem).variantId}-${index}`}
item={item}
isOnline={isLogin}
onRemove={() => handleRemoveItem(
isLogin ? (item as CartItemType).product._id : (item as LocalCartItem).productId,
isLogin ? (item as CartItemType).variant._id : (item as LocalCartItem).variantId
)}
/>
))
) : (
<div className='text-center py-4'>
<div className='text-gray-500 mb-2'>
{!isLogin ? 'سبد خرید شما خالی است' : 'سبد خرید شما خالی است'}
</div>
{!isLogin && (
<div className='text-xs text-gray-400'>
آیتمهای اضافه شده به سبد خرید در مرورگر شما ذخیره میشوند
</div>
)}
</div>
)}
</div>
{/* View Cart Button */}
{itemsCount > 0 && isLogin && (
<Link href="/cart" className='block'>
<Button className='w-full bg-primary hover:bg-primary/90 text-white rounded-lg py-3'>
مشاهده سبد خرید
</Button>
</Link>
)}
{/* Login encouragement message for offline users */}
{!isLogin && (
<div className='mt-4 p-3 bg-blue-50 border border-blue-200 rounded-lg text-center'>
<div className='text-xs text-blue-700 mb-2'>
برای ذخیره سبد خرید و همگامسازی در همه دستگاهها، وارد حساب کاربری شوید
</div>
<Link href="/auth" className='inline-block'>
<Button size="sm" variant="outline" className='text-blue-600 border-blue-300 hover:bg-blue-100'>
ورود به حساب
</Button>
</Link>
</div>
)}
</div>
</PopoverContent>
</Popover>
)
}
// Cart Item Card Component
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
const hasError = !isOnline && !isProductLoading && !(item as EnrichedOfflineCartItem).variant
return (
<div className='bg-white rounded-lg p-3 flex gap-3 shadow-sm border border-gray-100'>
{/* Product Image */}
<div className='flex-shrink-0'>
<Image
src={productImage}
alt={productTitle}
width={60}
height={60}
className='w-15 h-15 object-contain rounded'
/>
</div>
{/* Product Details */}
<div className='flex-1 min-w-0'>
<div className='text-xs text-gray-500 mb-1'>
{productCategory}
</div>
<div className='text-sm text-gray-800 mb-2 line-clamp-2'>
{hasError ? 'خطا در بارگذاری محصول' : isProductLoading ? 'در حال بارگذاری محصول...' : productTitle}
</div>
<div className='flex items-center justify-between'>
<div className='text-sm font-semibold text-gray-900'>
{hasError ? (
`${quantity} عدد`
) : isProductLoading ? (
'در حال بارگذاری...'
) : (
`${NumberFormat(productPrice)} تومان`
)}
</div>
<button
onClick={onRemove}
className='p-1 hover:bg-red-100 rounded'
>
<Trash size={16} color='#AD3434' />
</button>
</div>
</div>
</div>
)
}
export default Cart