"use client"; import { useState, useEffect, useRef, useCallback } from "react"; import Image from "next/image"; import { Minus, Plus, Trash2, AlertTriangle } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { useUpdateCartItemQuantity, useRemoveFromCart } from "@/lib/hooks"; import { useTranslations } from "next-intl"; import type { CartItem } from "@/lib/types/api"; interface CartItemCardProps { item: CartItem; onUpdate?: () => void; } const PENDING_CART_UPDATES_KEY = "pendingCartUpdates"; interface PendingUpdate { quantity: number; timestamp: number; retryCount: number; } export default function CartItemCard({ item, onUpdate }: CartItemCardProps) { const t = useTranslations(); const [localQuantity, setLocalQuantity] = useState(item.quantity); const [isSyncing, setIsSyncing] = useState(false); const [syncError, setSyncError] = useState(false); const [showStockModal, setShowStockModal] = useState(false); const debounceTimerRef = useRef(undefined); const isRequestInFlightRef = useRef(false); const pendingQuantityRef = useRef(null); const retryCountRef = useRef(0); const retryTimerRef = useRef(undefined); const isInitializedRef = useRef(false); const syncToServerRef = useRef<((quantity: number) => void) | null>(null); const retrySyncRef = useRef<((quantity: number) => void) | null>(null); const { mutate: updateQuantity } = useUpdateCartItemQuantity(); const { mutate: removeItem, isPending: isRemoving } = useRemoveFromCart(); const availableStock = item.product.stock || 0; useEffect(() => { setLocalQuantity(item.quantity); if (!isInitializedRef.current) { isInitializedRef.current = true; } }, [item.quantity]); const savePendingUpdate = useCallback( (quantity: number) => { try { const stored = sessionStorage.getItem(PENDING_CART_UPDATES_KEY); const pending: Record = stored ? JSON.parse(stored) : {}; pending[item.product_id] = { quantity, timestamp: Date.now(), retryCount: retryCountRef.current, }; sessionStorage.setItem( PENDING_CART_UPDATES_KEY, JSON.stringify(pending), ); } catch (error) { console.error("Failed to save pending update:", error); } }, [item.product_id], ); const clearPendingUpdate = useCallback(() => { try { const stored = sessionStorage.getItem(PENDING_CART_UPDATES_KEY); if (stored) { const pending: Record = JSON.parse(stored); delete pending[item.product_id]; if (Object.keys(pending).length === 0) { sessionStorage.removeItem(PENDING_CART_UPDATES_KEY); } else { sessionStorage.setItem( PENDING_CART_UPDATES_KEY, JSON.stringify(pending), ); } } } catch (error) { console.error("Failed to clear pending update:", error); } }, [item.product_id]); const retrySync = useCallback((quantity: number) => { const maxRetries = 4; const retryCount = retryCountRef.current; if (retryCount >= maxRetries) { setSyncError(true); setIsSyncing(false); return; } const delay = Math.min(1000 * Math.pow(2, retryCount), 16000); retryCountRef.current++; retryTimerRef.current = setTimeout(() => { syncToServerRef.current?.(quantity); }, delay); }, []); retrySyncRef.current = retrySync; const syncToServer = useCallback( (quantity: number) => { if (isRequestInFlightRef.current) { pendingQuantityRef.current = quantity; return; } isRequestInFlightRef.current = true; setIsSyncing(true); setSyncError(false); if (quantity <= 0) { removeItem(item.product_id, { onSuccess: () => { isRequestInFlightRef.current = false; setIsSyncing(false); retryCountRef.current = 0; clearPendingUpdate(); onUpdate?.(); if (pendingQuantityRef.current !== null) { const nextQuantity = pendingQuantityRef.current; pendingQuantityRef.current = null; setTimeout(() => syncToServerRef.current?.(nextQuantity), 100); } }, onError: (error) => { console.error("Remove failed:", error); isRequestInFlightRef.current = false; retrySyncRef.current?.(quantity); }, }); } else { updateQuantity( { productId: item.product_id, quantity }, { onSuccess: () => { isRequestInFlightRef.current = false; setIsSyncing(false); retryCountRef.current = 0; clearPendingUpdate(); onUpdate?.(); if (pendingQuantityRef.current !== null) { const nextQuantity = pendingQuantityRef.current; pendingQuantityRef.current = null; setTimeout(() => syncToServerRef.current?.(nextQuantity), 100); } }, onError: (error) => { console.error("Update failed:", error); isRequestInFlightRef.current = false; if (retryCountRef.current >= 3) { setLocalQuantity(item.quantity); clearPendingUpdate(); } retrySyncRef.current?.(quantity); }, }, ); } }, [ item.product_id, item.quantity, updateQuantity, removeItem, onUpdate, clearPendingUpdate, ], ); syncToServerRef.current = syncToServer; useEffect(() => { if (!isInitializedRef.current) { return; } if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); } if (localQuantity === item.quantity) { return; } if (localQuantity <= 0 && item.quantity > 0) { // Delete operation } else if (localQuantity <= 0) { return; } savePendingUpdate(localQuantity); debounceTimerRef.current = setTimeout(() => { syncToServerRef.current?.(localQuantity); }, 800); return () => { if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); } }; }, [localQuantity, item.quantity, savePendingUpdate]); useEffect(() => { return () => { if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current); if (retryTimerRef.current) clearTimeout(retryTimerRef.current); }; }, []); const handleQuantityIncrease = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); if (localQuantity >= availableStock) { setShowStockModal(true); return; } setLocalQuantity((prev) => prev + 1); }; const handleQuantityDecrease = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); if (localQuantity <= 1) { handleDelete(); return; } setLocalQuantity((prev) => prev - 1); }; const handleDelete = () => { setLocalQuantity(0); clearPendingUpdate(); }; const getImageSrc = () => { if (item.product.image) return item.product.image; if (item.product.images && item.product.images.length > 0) return item.product.images[0]; return "/placeholder.svg"; }; return ( <>
{/* Product Image & Info */}
{item.product.name}

{item.product.name}

{/*

{item.seller?.name || "Store"}

*/} {/* {availableStock <= 5 && (

{t("only_left", { count: availableStock })}

)} */}
{/* Price & Quantity */}

{t("unit_price")}{" "} {item.price_formatted}

{item.discount_formatted && item.discount_formatted !== "0 TMT" && (

{t("discount")} {item.discount_formatted}

)}
{t("total_price")} {( parseFloat(item.product.price_amount || "0") * localQuantity ).toFixed(2)}{" "} TMT
{/* Quantity Controls */}
{isSyncing ? (
) : ( localQuantity )} {syncError && ( )}
{/* Stock Limit Modal */}
{t("stock_limit_title")} {t("stock_limit_message", { product: item.product.name, stock: availableStock, })}
); }