"use client"; import { useState, useCallback, useMemo, useRef, useEffect } from "react"; import { Skeleton } from "@/components/ui/skeleton"; import { useProductsBySlug, useRelatedProducts, useSubmitReview, } from "@/features/products/hooks/useProducts"; import { useAddToCart, useUpdateCartItemQuantity, useRemoveFromCart, useCart, cartEvents, } from "@/features/cart/hooks/useCart"; import { useTranslations } from "next-intl"; import { toast } from "sonner"; import { ProductImageGallery } from "./ProductImageGallery"; import { ProductInfoCard } from "./ProductInfoCard"; import { ProductPurchaseCard } from "./ProductPurchaseCard"; import { ProductReviewsSection } from "./ProductReviewsSection"; import { RelatedProductsSection } from "./RelatedProductsSection"; import { ReviewModal } from "./ReviewModal"; import { StockLimitModal } from "./StockLimitModal"; import { useIsFavorite, useToggleFavorite, } from "@/features/favorites/hooks/useFavorites"; interface ProductDetailProps { slug: string; } const PENDING_PRODUCT_UPDATES_KEY = "pendingProductUpdates"; interface PendingUpdate { quantity: number; timestamp: number; retryCount: number; } export default function ProductPageContent({ slug }: ProductDetailProps) { const [localQuantity, setLocalQuantity] = useState(1); const [isSyncing, setIsSyncing] = useState(false); const [syncError, setSyncError] = useState(false); const [showStockModal, setShowStockModal] = useState(false); const [showReviewModal, setShowReviewModal] = useState(false); const [isInitialized, setIsInitialized] = useState(false); const t = useTranslations(); const debounceTimerRef = useRef(undefined); const isRequestInFlightRef = useRef(false); const pendingQuantityRef = useRef(null); const retryCountRef = useRef(0); const retryTimerRef = useRef(undefined); const syncToServerRef = useRef<((quantity: number) => void) | null>(null); const retrySyncRef = useRef<((quantity: number) => void) | null>(null); const shouldSyncFromCartRef = useRef(true); const lastSyncedQuantityRef = useRef(null); const { data: product, isLoading: productLoading, error, refetch: refetchProduct, } = useProductsBySlug(slug); const { isFavorite, isLoading: isFavLoading } = useIsFavorite( product?.id || 0 ); const cartOptions = useMemo( () => ({ refetchOnMount: true, refetchOnWindowFocus: true, staleTime: 0, }), [] ); const { mutate: toggleFavoriteMutation } = useToggleFavorite(); const { data: cartData, refetch: refetchCart, isFetching: isCartFetching, } = useCart(cartOptions); const { data: relatedProducts } = useRelatedProducts(product?.id || 0, { enabled: !!product?.id, }); const addToCartMutation = useAddToCart(); const updateCartMutation = useUpdateCartItemQuantity(); const removeFromCartMutation = useRemoveFromCart(); const submitReviewMutation = useSubmitReview(); const cartItem = useMemo(() => { const item = cartData?.data?.find( (item: any) => item.product?.id === product?.id ); return item; }, [cartData, product, isInitialized]); const isInCart = !!cartItem; const availableStock = product?.stock || 0; const imageUrls = useMemo( () => product?.media?.map( (m) => m.images_800x800 || m.images_720x720 || m.thumbnail ) || [], [product] ); const reviews = useMemo(() => product?.reviews_resources || [], [product]); const averageRating = useMemo( () => product?.reviews?.rating ? Number.parseFloat(product.reviews.rating) : 0, [product] ); const transformedRelatedProducts = useMemo(() => { if (!relatedProducts) return []; return relatedProducts.map((p) => ({ id: p.id, slug: p.slug, name: p.name, price_amount: p.price_amount, old_price_amount: p.old_price_amount ?? undefined, struct_price_text: `${p.price_amount} TMT`, discount: null, discount_text: null, stock: p.stock, media: p.media, labels: [], price_color: undefined, })); }, [relatedProducts]); useEffect(() => { if (!product?.id || isInitialized) return; if (cartItem?.product_quantity) { const serverQuantity = cartItem.product_quantity; setLocalQuantity(serverQuantity); lastSyncedQuantityRef.current = serverQuantity; } setIsInitialized(true); }, [product?.id, cartItem, isInitialized]); useEffect(() => { setLocalQuantity(cartItem?.product_quantity || 1); }, [cartItem]); const savePendingUpdate = useCallback( (quantity: number) => { if (!product?.id) return; try { const stored = sessionStorage.getItem(PENDING_PRODUCT_UPDATES_KEY); const pending: Record = stored ? JSON.parse(stored) : {}; pending[product.id] = { quantity, timestamp: Date.now(), retryCount: retryCountRef.current, }; sessionStorage.setItem( PENDING_PRODUCT_UPDATES_KEY, JSON.stringify(pending) ); } catch (error) { console.error("Failed to save pending update:", error); } }, [product?.id] ); const clearPendingUpdate = useCallback(() => { if (!product?.id) return; try { const stored = sessionStorage.getItem(PENDING_PRODUCT_UPDATES_KEY); if (stored) { const pending: Record = JSON.parse(stored); delete pending[product.id]; if (Object.keys(pending).length === 0) { sessionStorage.removeItem(PENDING_PRODUCT_UPDATES_KEY); } else { sessionStorage.setItem( PENDING_PRODUCT_UPDATES_KEY, JSON.stringify(pending) ); } } } catch (error) { console.error("Failed to clear pending update:", error); } }, [product?.id]); const retrySync = useCallback( (quantity: number) => { const maxRetries = 4; const retryCount = retryCountRef.current; if (retryCount >= maxRetries) { setSyncError(true); setIsSyncing(false); shouldSyncFromCartRef.current = true; toast.error(t("error"), { description: t("update_quantity_failed"), }); return; } const delay = Math.min(1000 * Math.pow(2, retryCount), 16000); retryCountRef.current++; retryTimerRef.current = setTimeout(() => { syncToServerRef.current?.(quantity); }, delay); }, [t] ); retrySyncRef.current = retrySync; const syncToServer = useCallback( async (quantity: number) => { if (!product?.id) return; if (isRequestInFlightRef.current) { pendingQuantityRef.current = quantity; return; } isRequestInFlightRef.current = true; setIsSyncing(true); setSyncError(false); try { if (quantity === 0) { await removeFromCartMutation.mutateAsync(product.id); toast.success(t("removed_from_cart")); } else if (isInCart) { await updateCartMutation.mutateAsync({ productId: product.id, quantity: quantity, }); } else { await addToCartMutation.mutateAsync({ productId: product.id, quantity: quantity, }); } retryCountRef.current = 0; clearPendingUpdate(); if (pendingQuantityRef.current !== null) { const nextQuantity = pendingQuantityRef.current; pendingQuantityRef.current = null; setTimeout(() => syncToServerRef.current?.(nextQuantity), 100); } } catch (error) { setLocalQuantity(cartItem?.product_quantity || 1); toast.error(t("failed_to_update_quantity"), { description: "Please try again", }); retrySyncRef.current?.(quantity); } finally { isRequestInFlightRef.current = false; setIsSyncing(false); } }, [ product?.id, isInCart, updateCartMutation, addToCartMutation, removeFromCartMutation, cartItem, clearPendingUpdate, t, ] ); syncToServerRef.current = syncToServer; useEffect(() => { if (!isInCart || !product?.id) return; if (localQuantity === (cartItem?.product_quantity || 1)) { return; } if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); } debounceTimerRef.current = setTimeout(() => { syncToServerRef.current?.(localQuantity); }, 800); return () => { if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); } }; }, [localQuantity, isInCart, product?.id, cartItem?.product_quantity]); useEffect(() => { return () => { if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current); if (retryTimerRef.current) clearTimeout(retryTimerRef.current); }; }, []); const handleAddToCart = useCallback(async () => { if (!product?.id) return; if (localQuantity > availableStock) { setShowStockModal(true); return; } setIsSyncing(true); shouldSyncFromCartRef.current = false; try { await addToCartMutation.mutateAsync({ productId: product.id, quantity: localQuantity, }); lastSyncedQuantityRef.current = localQuantity; setTimeout(() => { shouldSyncFromCartRef.current = true; }, 150); setIsSyncing(false); toast.success(t("added_to_cart"), { description: `${product.name} ${t("added_to_cart_description")}`, }); } catch (error) { console.error("Add to cart error:", error); setIsSyncing(false); shouldSyncFromCartRef.current = true; toast.error(t("error"), { description: t("add_to_cart_failed"), }); } }, [product, localQuantity, availableStock, addToCartMutation, t]); const handleQuantityIncrease = useCallback(() => { if (localQuantity >= availableStock) { setShowStockModal(true); return; } setLocalQuantity((prev) => { const newVal = prev + 1; return newVal; }); }, [localQuantity, availableStock]); const handleQuantityDecrease = useCallback(() => { if (localQuantity <= 0) return; setLocalQuantity((prev) => { const newVal = prev - 1; return newVal; }); }, [localQuantity]); const handleToggleFavorite = useCallback( (e?: React.MouseEvent) => { e?.preventDefault(); e?.stopPropagation(); if (!product?.id) { toast.error(t("error"), { description: "Product ID not found", }); return; } toggleFavoriteMutation( { productId: product.id, isFavorite, }, { onSuccess: (data) => { toast.success( data?.wasAdded ? t("added_to_favorites") : t("removed_from_favorites") ); }, onError: () => { toast.error(t("error"), { description: "Try again later", }); }, } ); }, [product?.id, isFavorite, toggleFavoriteMutation, t] ); const handleSubmitReview = useCallback( async (rating: number, text: string) => { if (!product?.id || rating === 0 || !text.trim()) { toast.error(t("error"), { description: "Please provide rating and review text", }); return; } try { await submitReviewMutation.mutateAsync({ productId: product.id, rating: rating, title: text, source: "site", }); await refetchProduct(); toast.success("Review submitted successfully!"); setShowReviewModal(false); } catch (error) { toast.error(t("error"), { description: "Failed to submit review", }); } }, [product?.id, submitReviewMutation, refetchProduct, t] ); const loadingSkeleton = useMemo( () => (
{[1, 2, 3].map((i) => ( ))}
), [] ); if (productLoading) return loadingSkeleton; if (error || !product) { return (

{t("product_not_found")}

{t("product_not_found_description")}

); } return ( <>
{/* setShowReviewModal(true)} /> */}
); }