added baha tassyklamak
This commit is contained in:
@@ -2,13 +2,10 @@ import React, { useState, useRef, useEffect, useMemo } from "react";
|
||||
import styles from "./CartPage.module.scss";
|
||||
import { FaTrashAlt } from "react-icons/fa";
|
||||
import Checkout from "../../components/Checkout";
|
||||
import { ChevronDown, ChevronUp } from "lucide-react";
|
||||
import { Modal } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import EmptyCartState from "./emptyCart";
|
||||
import {
|
||||
useGetCartQuery,
|
||||
useAddToCartMutation,
|
||||
useRemoveFromCartMutation,
|
||||
useUpdateCartItemMutation,
|
||||
useCleanCartMutation,
|
||||
@@ -17,9 +14,9 @@ import { useCart } from "../../app/api/useCart";
|
||||
import { DecreaseIcon, IncreaseIcon } from "../../components/Icons";
|
||||
import Loader from "../../components/Loader/index";
|
||||
|
||||
const TruncatedDescription = ({ description, maxLength = 100 }) => {
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const isPriceZero = (price) => !price || parseFloat(price) === 0;
|
||||
|
||||
const TruncatedDescription = ({ description, maxLength = 100 }) => {
|
||||
const stripHtml = (html) => {
|
||||
const doc = new DOMParser().parseFromString(html, "text/html");
|
||||
return doc.body.textContent || "";
|
||||
@@ -32,11 +29,9 @@ const TruncatedDescription = ({ description, maxLength = 100 }) => {
|
||||
<div className={styles.truncatedDescription}>
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: isExpanded
|
||||
? description
|
||||
: shouldTruncate
|
||||
? description.substring(0, maxLength) + "..."
|
||||
: description,
|
||||
__html: shouldTruncate
|
||||
? description.substring(0, maxLength) + "..."
|
||||
: description,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -44,20 +39,16 @@ const TruncatedDescription = ({ description, maxLength = 100 }) => {
|
||||
};
|
||||
|
||||
const CartPage = () => {
|
||||
const { cartData, cartItems, isLoading, isError, error } = useCart();
|
||||
const { cartData, cartItems, isLoading } = useCart();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { t, i18n } = useTranslation();
|
||||
const [checkoutStores, setCheckoutStores] = useState({});
|
||||
const [addToCart] = useAddToCartMutation();
|
||||
const [removeFromCart] = useRemoveFromCartMutation();
|
||||
const [updateCartItem] = useUpdateCartItemMutation();
|
||||
const [cleanCart] = useCleanCartMutation();
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const expandedRef = useRef(null);
|
||||
const [deleteModalVisible, setDeleteModalVisible] = useState(false);
|
||||
const [emptyCartModalVisible, setEmptyCartModalVisible] = useState(false);
|
||||
const [itemToDelete, setItemToDelete] = useState(null);
|
||||
|
||||
const [localQuantities, setLocalQuantities] = useState({});
|
||||
const [pendingQuantities, setPendingQuantities] = useState({});
|
||||
const [loadingItems, setLoadingItems] = useState({});
|
||||
@@ -70,43 +61,35 @@ const CartPage = () => {
|
||||
width: 400,
|
||||
};
|
||||
|
||||
// Convert grouped data to stores array
|
||||
const stores = useMemo(() => {
|
||||
return Object.entries(cartData)
|
||||
.map(([storeSlug, items]) => {
|
||||
if (!items || !items.length) return null;
|
||||
|
||||
// Get store info from first item
|
||||
if (!items?.length) return null;
|
||||
const storeInfo = items[0]?.product?.channel?.[0];
|
||||
|
||||
return {
|
||||
id: storeInfo?.id || storeSlug,
|
||||
name: storeInfo?.name || storeSlug,
|
||||
slug: storeSlug,
|
||||
shipping_price: storeInfo?.shipping_price,
|
||||
items: items,
|
||||
items,
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
}, [cartData]);
|
||||
|
||||
// ✅ Initialize local quantities from cart items
|
||||
useEffect(() => {
|
||||
const newLocalQuantities = {};
|
||||
const newPendingQuantities = {};
|
||||
|
||||
const newLocal = {};
|
||||
const newPending = {};
|
||||
cartItems.forEach((item) => {
|
||||
const productId = item.product.id;
|
||||
const quantity = parseInt(item.product_quantity, 10) || 0;
|
||||
newLocalQuantities[productId] = quantity;
|
||||
newPendingQuantities[productId] = quantity;
|
||||
const id = item.product.id;
|
||||
const qty = parseInt(item.product_quantity, 10) || 0;
|
||||
newLocal[id] = qty;
|
||||
newPending[id] = qty;
|
||||
});
|
||||
|
||||
setLocalQuantities(newLocalQuantities);
|
||||
setPendingQuantities(newPendingQuantities);
|
||||
setLocalQuantities(newLocal);
|
||||
setPendingQuantities(newPending);
|
||||
}, [cartItems]);
|
||||
|
||||
// ✅ Debounced Cart Update - Her ürün için ayrı debounce
|
||||
useEffect(() => {
|
||||
const timers = {};
|
||||
|
||||
@@ -114,141 +97,94 @@ const CartPage = () => {
|
||||
const serverItem = cartItems.find(
|
||||
(item) => String(item.product.id) === String(productId),
|
||||
);
|
||||
const serverQuantity = serverItem
|
||||
const serverQty = serverItem
|
||||
? parseInt(serverItem.product_quantity, 10)
|
||||
: 0;
|
||||
const pendingQuantity = pendingQuantities[productId];
|
||||
const pendingQty = pendingQuantities[productId];
|
||||
|
||||
// Değişiklik yoksa veya 0 ise (Delete modalı tetikler) bir şey yapma
|
||||
if (
|
||||
pendingQuantity === undefined ||
|
||||
pendingQuantity === serverQuantity ||
|
||||
pendingQuantity <= 0
|
||||
) {
|
||||
pendingQty === undefined ||
|
||||
pendingQty === serverQty ||
|
||||
pendingQty <= 0
|
||||
)
|
||||
return;
|
||||
}
|
||||
|
||||
timers[productId] = setTimeout(async () => {
|
||||
try {
|
||||
setLoadingItems((prev) => ({ ...prev, [productId]: true }));
|
||||
await updateCartItem({
|
||||
productId,
|
||||
quantity: pendingQuantity,
|
||||
}).unwrap();
|
||||
} catch (error) {
|
||||
console.error("Failed to update cart:", error);
|
||||
// Hata durumunda rollback
|
||||
setLocalQuantities((prev) => ({
|
||||
...prev,
|
||||
[productId]: serverQuantity,
|
||||
}));
|
||||
setPendingQuantities((prev) => ({
|
||||
...prev,
|
||||
[productId]: serverQuantity,
|
||||
}));
|
||||
await updateCartItem({ productId, quantity: pendingQty }).unwrap();
|
||||
} catch {
|
||||
setLocalQuantities((prev) => ({ ...prev, [productId]: serverQty }));
|
||||
setPendingQuantities((prev) => ({ ...prev, [productId]: serverQty }));
|
||||
} finally {
|
||||
setLoadingItems((prev) => ({ ...prev, [productId]: false }));
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
|
||||
return () => {
|
||||
Object.values(timers).forEach((timer) => clearTimeout(timer));
|
||||
};
|
||||
return () => Object.values(timers).forEach(clearTimeout);
|
||||
}, [pendingQuantities, cartItems, updateCartItem]);
|
||||
|
||||
const handleQuantityIncrease = (productId) => (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if (loadingItems[productId]) return;
|
||||
|
||||
const item = cartItems.find((item) => item.product.id === productId);
|
||||
if (!item) return;
|
||||
const item = cartItems.find((i) => i.product.id === productId);
|
||||
if (!item || localQuantities[productId] >= item.product.stock) return;
|
||||
|
||||
if (localQuantities[productId] >= item.product.stock) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newQuantity = (localQuantities[productId] || 0) + 1;
|
||||
setLocalQuantities((prev) => ({
|
||||
...prev,
|
||||
[productId]: newQuantity,
|
||||
}));
|
||||
setPendingQuantities((prev) => ({
|
||||
...prev,
|
||||
[productId]: newQuantity,
|
||||
}));
|
||||
const newQty = (localQuantities[productId] || 0) + 1;
|
||||
setLocalQuantities((prev) => ({ ...prev, [productId]: newQty }));
|
||||
setPendingQuantities((prev) => ({ ...prev, [productId]: newQty }));
|
||||
};
|
||||
|
||||
const handleQuantityDecrease = (productId) => (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if (loadingItems[productId]) return;
|
||||
|
||||
const currentQuantity = localQuantities[productId] || 0;
|
||||
|
||||
if (currentQuantity <= 1) {
|
||||
const currentQty = localQuantities[productId] || 0;
|
||||
if (currentQty <= 1) {
|
||||
showDeleteConfirm(productId);
|
||||
return;
|
||||
}
|
||||
|
||||
const newQuantity = currentQuantity - 1;
|
||||
setLocalQuantities((prev) => ({
|
||||
...prev,
|
||||
[productId]: newQuantity,
|
||||
}));
|
||||
setPendingQuantities((prev) => ({
|
||||
...prev,
|
||||
[productId]: newQuantity,
|
||||
}));
|
||||
const newQty = currentQty - 1;
|
||||
setLocalQuantities((prev) => ({ ...prev, [productId]: newQty }));
|
||||
setPendingQuantities((prev) => ({ ...prev, [productId]: newQty }));
|
||||
};
|
||||
|
||||
const calculateStoreTotal = (storeItems) => {
|
||||
return storeItems.reduce((sum, item) => {
|
||||
const itemPrice = parseFloat(item.product.price_amount) || 0;
|
||||
const itemQuantity = parseInt(item.product_quantity, 10) || 0;
|
||||
return sum + itemPrice * itemQuantity;
|
||||
const getStoreShippingPrice = (store) =>
|
||||
store.shipping_price != null ? parseFloat(store.shipping_price) : 20;
|
||||
|
||||
// Store içinde fiyatsız ürün var mı?
|
||||
const storeHasZeroPriceItem = (storeItems) =>
|
||||
storeItems.some((item) => isPriceZero(item.product.price_amount));
|
||||
|
||||
const calculateStoreTotal = (storeItems) =>
|
||||
storeItems.reduce((sum, item) => {
|
||||
return (
|
||||
sum +
|
||||
(parseFloat(item.product.price_amount) || 0) *
|
||||
(parseInt(item.product_quantity, 10) || 0)
|
||||
);
|
||||
}, 0);
|
||||
};
|
||||
|
||||
const getStoreShippingPrice = (store) => {
|
||||
return store.shipping_price !== null && store.shipping_price !== undefined
|
||||
? parseFloat(store.shipping_price)
|
||||
: 20;
|
||||
};
|
||||
|
||||
const handleCheckout = (storeId) => {
|
||||
const handleCheckout = (storeId) =>
|
||||
setCheckoutStores((prev) => ({ ...prev, [storeId]: true }));
|
||||
};
|
||||
|
||||
const handleBackToCart = (storeId) => {
|
||||
const handleBackToCart = (storeId) =>
|
||||
setCheckoutStores((prev) => ({ ...prev, [storeId]: false }));
|
||||
};
|
||||
|
||||
const handleOrderSubmit = async (storeId, storeItems) => {
|
||||
const handleOrderSubmit = async (storeId) => {
|
||||
if (checkoutStores[storeId] && checkoutRefs.current[storeId]) {
|
||||
const success = await checkoutRefs.current[storeId]();
|
||||
if (success) {
|
||||
setCheckoutStores((prev) => ({ ...prev, [storeId]: false }));
|
||||
}
|
||||
if (success) setCheckoutStores((prev) => ({ ...prev, [storeId]: false }));
|
||||
} else {
|
||||
handleCheckout(storeId);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
if (expandedRef.current && !expandedRef.current.contains(event.target)) {
|
||||
setIsExpanded(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||
}, []);
|
||||
|
||||
const showDeleteConfirm = (productId) => {
|
||||
setItemToDelete(productId);
|
||||
setDeleteModalVisible(true);
|
||||
@@ -258,48 +194,41 @@ const CartPage = () => {
|
||||
if (itemToDelete) {
|
||||
try {
|
||||
await removeFromCart({ productId: itemToDelete }).unwrap();
|
||||
|
||||
setLocalQuantities((prev) => {
|
||||
const newState = { ...prev };
|
||||
delete newState[itemToDelete];
|
||||
return newState;
|
||||
const s = { ...prev };
|
||||
delete s[itemToDelete];
|
||||
return s;
|
||||
});
|
||||
setPendingQuantities((prev) => {
|
||||
const newState = { ...prev };
|
||||
delete newState[itemToDelete];
|
||||
return newState;
|
||||
const s = { ...prev };
|
||||
delete s[itemToDelete];
|
||||
return s;
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to remove item:", error);
|
||||
} catch (e) {
|
||||
console.error("Failed to remove item:", e);
|
||||
}
|
||||
}
|
||||
setDeleteModalVisible(false);
|
||||
setItemToDelete(null);
|
||||
};
|
||||
|
||||
const showEmptyCartConfirm = () => {
|
||||
setEmptyCartModalVisible(true);
|
||||
};
|
||||
|
||||
const handleEmptyCartConfirm = async () => {
|
||||
try {
|
||||
await cleanCart().unwrap();
|
||||
|
||||
setLocalQuantities({});
|
||||
setPendingQuantities({});
|
||||
setCheckoutStores({});
|
||||
} catch (error) {
|
||||
console.error("Failed to clean cart:", error);
|
||||
} catch (e) {
|
||||
console.error("Failed to clean cart:", e);
|
||||
}
|
||||
setEmptyCartModalVisible(false);
|
||||
};
|
||||
|
||||
const getTotalItemCount = () => {
|
||||
return cartItems.reduce(
|
||||
const getTotalItemCount = () =>
|
||||
cartItems.reduce(
|
||||
(sum, item) => sum + parseInt(item.product_quantity, 10),
|
||||
0,
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.cartContainer}>
|
||||
@@ -339,21 +268,20 @@ const CartPage = () => {
|
||||
<h2>
|
||||
{t("cart.basket")} ({getTotalItemCount()})
|
||||
</h2>
|
||||
<div>
|
||||
<button
|
||||
className={styles.deleteBtn}
|
||||
style={{ padding: "4px 12px" }}
|
||||
onClick={showEmptyCartConfirm}
|
||||
>
|
||||
<FaTrashAlt /> {t("cart.clearCart")}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
className={styles.deleteBtn}
|
||||
style={{ padding: "4px 12px" }}
|
||||
onClick={() => setEmptyCartModalVisible(true)}
|
||||
>
|
||||
<FaTrashAlt /> {t("cart.clearCart")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{stores.map((store) => {
|
||||
const shippingPrice = getStoreShippingPrice(store);
|
||||
const storeTotal = calculateStoreTotal(store.items);
|
||||
const totalWithShipping = storeTotal + shippingPrice;
|
||||
const hasZeroPrice = storeHasZeroPriceItem(store.items);
|
||||
|
||||
return (
|
||||
<div key={store.id} className={styles.storeSection}>
|
||||
@@ -363,8 +291,8 @@ const CartPage = () => {
|
||||
shippingPrice={shippingPrice}
|
||||
productIds={store.items.map((item) => item.product.id)}
|
||||
onBackToCart={() => handleBackToCart(store.id)}
|
||||
onPlaceOrder={(placeOrderFn) => {
|
||||
checkoutRefs.current[store.id] = placeOrderFn;
|
||||
onPlaceOrder={(fn) => {
|
||||
checkoutRefs.current[store.id] = fn;
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
@@ -391,10 +319,9 @@ const CartPage = () => {
|
||||
</div>
|
||||
<div className={styles.priceQuantity}>
|
||||
<span className={styles.price}>
|
||||
{(
|
||||
parseFloat(item.product.price_amount) || 0
|
||||
).toFixed(2)}{" "}
|
||||
m.
|
||||
{isPriceZero(item.product.price_amount)
|
||||
? "Bahasyny anyklamaly"
|
||||
: `${parseFloat(item.product.price_amount).toFixed(2)} m.`}
|
||||
</span>
|
||||
<div className={styles.quantityControls}>
|
||||
<button
|
||||
@@ -441,26 +368,46 @@ const CartPage = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ✅ Store Summary - fiyatsız ürün varsa "Baha anyklamak" */}
|
||||
<div className={styles.storeSummary}>
|
||||
<div className={styles.cartContent}>
|
||||
<h3>
|
||||
{store.name} - {t("cart.basket")}:
|
||||
</h3>
|
||||
<div className={styles.summaryRow}>
|
||||
<span>{t("cart.price")}:</span>
|
||||
<span>{storeTotal.toFixed(2)} m.</span>
|
||||
</div>
|
||||
<div className={styles.summaryRow}>
|
||||
<span>{t("cart.delivery")}:</span>
|
||||
<span>{shippingPrice.toFixed(2)} m.</span>
|
||||
</div>
|
||||
<div className={styles.summaryRow}>
|
||||
<span>{t("cart.total")}:</span>
|
||||
<span>{totalWithShipping.toFixed(2)} m.</span>
|
||||
</div>
|
||||
{hasZeroPrice ? (
|
||||
<>
|
||||
<div className={styles.summaryRow}>
|
||||
<span>{t("cart.price")}:</span>
|
||||
<span>Bahasyny anyklamaly</span>
|
||||
</div>
|
||||
<div className={styles.summaryRow}>
|
||||
<span>{t("cart.delivery")}:</span>
|
||||
<span>Bahasyny anyklamaly</span>
|
||||
</div>
|
||||
<div className={styles.summaryRow}>
|
||||
<span>{t("cart.total")}:</span>
|
||||
<span>Bahasyny anyklamaly</span>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className={styles.summaryRow}>
|
||||
<span>{t("cart.price")}:</span>
|
||||
<span>{storeTotal.toFixed(2)} m.</span>
|
||||
</div>
|
||||
<div className={styles.summaryRow}>
|
||||
<span>{t("cart.delivery")}:</span>
|
||||
<span>{shippingPrice.toFixed(2)} m.</span>
|
||||
</div>
|
||||
<div className={styles.summaryRow}>
|
||||
<span>{t("cart.total")}:</span>
|
||||
<span>{totalWithShipping.toFixed(2)} m.</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleOrderSubmit(store.id, store.items)}
|
||||
onClick={() => handleOrderSubmit(store.id)}
|
||||
className={styles.checkoutBtn}
|
||||
>
|
||||
{checkoutStores[store.id]
|
||||
@@ -472,7 +419,6 @@ const CartPage = () => {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Mobile sticky summary */}
|
||||
{/* <div className={styles.container}>
|
||||
<div className={styles.summaryCard} ref={expandedRef}>
|
||||
|
||||
Reference in New Issue
Block a user