added baha tassyklamak

This commit is contained in:
Jelaletdin12
2026-04-26 22:07:09 +05:00
parent 7060d05ae9
commit cc89455967
8 changed files with 792 additions and 547 deletions

View File

@@ -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}>