summaryrefslogtreecommitdiff
path: root/src-migrate/modules/cart/components/ItemAction.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src-migrate/modules/cart/components/ItemAction.tsx')
-rw-r--r--src-migrate/modules/cart/components/ItemAction.tsx246
1 files changed, 193 insertions, 53 deletions
diff --git a/src-migrate/modules/cart/components/ItemAction.tsx b/src-migrate/modules/cart/components/ItemAction.tsx
index 7220e362..4dcebd9e 100644
--- a/src-migrate/modules/cart/components/ItemAction.tsx
+++ b/src-migrate/modules/cart/components/ItemAction.tsx
@@ -1,74 +1,214 @@
-import style from '../styles/item-action.module.css'
+import style from '../styles/item-action.module.css';
-import React, { useEffect, useState } from 'react'
+import React, { useEffect, useState } from 'react';
-import { Spinner, Tooltip } from '@chakra-ui/react'
-import { MinusIcon, PlusIcon, Trash2Icon } from 'lucide-react'
+import { Spinner, Tooltip } from '@chakra-ui/react';
+import { MinusIcon, PlusIcon, Trash2Icon } from 'lucide-react';
-import { CartItem } from '~/types/cart'
-import { getAuth } from '~/libs/auth'
-import { deleteUserCart, upsertUserCart } from '~/services/cart'
+import { CartItem } from '~/types/cart';
+import { getAuth } from '~/libs/auth';
+import { deleteUserCart, upsertUserCart } from '~/services/cart';
-import { useDebounce } from 'usehooks-ts'
-import { useCartStore } from '../stores/useCartStore'
-import { useProductCartContext } from '@/contexts/ProductCartContext'
+import { useDebounce } from 'usehooks-ts';
+import { useCartStore } from '../stores/useCartStore';
+import { useProductCartContext } from '@/contexts/ProductCartContext';
+import {
+ removeSelectedItemsFromCookie,
+ removeCartItemsFromCookie,
+ quantityUpdateState,
+ getCartDataFromCookie,
+ setCartDataToCookie,
+} from '~/utils/cart';
+
+import { toast } from 'react-hot-toast';
type Props = {
- item: CartItem
-}
+ item: CartItem;
+};
const CartItemAction = ({ item }: Props) => {
- const auth = getAuth()
- const { setRefreshCart } = useProductCartContext()
- const [isLoadDelete, setIsLoadDelete] = useState<boolean>(false)
- const [isLoadQuantity, setIsLoadQuantity] = useState<boolean>(false)
+ const auth = getAuth();
+ const { setRefreshCart } = useProductCartContext();
+ const [isLoadDelete, setIsLoadDelete] = useState<boolean>(false);
+ const [isLoadQuantity, setIsLoadQuantity] = useState<boolean>(false);
- const [quantity, setQuantity] = useState<number>(item.quantity)
+ const [quantity, setQuantity] = useState<number>(item.quantity);
- const { loadCart } = useCartStore()
+ const { loadCart, cart, updateCartItem } = useCartStore();
- const limitQty = item.limit_qty?.transaction || 0
+ const limitQty = item.limit_qty?.transaction || 0;
const handleDelete = async () => {
- if (typeof auth !== 'object') return
-
- setIsLoadDelete(true)
- await deleteUserCart(auth.id, [item.cart_id])
- await loadCart(auth.id)
- setIsLoadDelete(false)
- setRefreshCart(true)
- }
-
- const decreaseQty = () => { setQuantity((quantity) => quantity -= 1) }
- const increaseQty = () => { setQuantity((quantity) => quantity += 1) }
- const debounceQty = useDebounce(quantity, 1000)
+ if (typeof auth !== 'object') return;
+
+ setIsLoadDelete(true);
+
+ try {
+ // Delete from server
+ await deleteUserCart(auth.id, [item.cart_id]);
+
+ // Clean up cookies immediately
+ removeSelectedItemsFromCookie([item.id]);
+ removeCartItemsFromCookie([item.cart_id]);
+
+ // Update local cart state optimistically
+ if (cart) {
+ const updatedProducts = cart.products.filter(
+ (product) => product.id !== item.id
+ );
+ const updatedCart = {
+ ...cart,
+ products: updatedProducts,
+ product_total: updatedProducts.length,
+ };
+ updateCartItem(updatedCart);
+ }
+
+ // Reload from server and refresh context
+ await loadCart(auth.id);
+ setRefreshCart(true);
+
+ toast.success('Item berhasil dihapus');
+ } catch (error) {
+ console.error('Failed to delete cart item:', error);
+ toast.error('Gagal menghapus item');
+
+ // Reload on error
+ await loadCart(auth.id);
+ } finally {
+ setIsLoadDelete(false);
+ }
+ };
+
+ const updateQuantityInCookie = (productId, cartId, newQuantity) => {
+ try {
+ const cartData = getCartDataFromCookie();
+ let itemFound = false;
+
+ // Find item by cart_id key or search within objects
+ if (cartData[cartId]) {
+ cartData[cartId].quantity = newQuantity;
+ itemFound = true;
+ } else {
+ // Search by product id or cart_id within objects
+ for (const key in cartData) {
+ const item = cartData[key];
+ if (item.id === productId || item.cart_id === cartId) {
+ item.quantity = newQuantity;
+ itemFound = true;
+ break;
+ }
+ }
+ }
+
+ if (itemFound) {
+ setCartDataToCookie(cartData);
+ return true;
+ }
+
+ return false;
+ } catch (error) {
+ console.error('Error updating quantity in cookie:', error);
+ return false;
+ }
+ };
+
+ const decreaseQty = () => {
+ setQuantity((quantity) => (quantity -= 1));
+ };
+
+ const increaseQty = () => {
+ setQuantity((quantity) => (quantity += 1));
+ };
+
+ const debounceQty = useDebounce(quantity, 1000);
+
useEffect(() => {
- if (isNaN(debounceQty)) setQuantity(1)
- if (limitQty > 0 && debounceQty > limitQty) setQuantity(limitQty)
- }, [debounceQty, limitQty])
+ if (isNaN(debounceQty)) setQuantity(1);
+ if (limitQty > 0 && debounceQty > limitQty) setQuantity(limitQty);
+ }, [debounceQty, limitQty]);
useEffect(() => {
const updateCart = async () => {
- if (typeof auth !== 'object' || isNaN(debounceQty)) return
-
- setIsLoadQuantity(true)
- await upsertUserCart({
- userId: auth.id,
- type: item.cart_type,
- id: item.id,
- qty: debounceQty,
- selected: item.selected,
- })
- await loadCart(auth.id)
- setIsLoadQuantity(false)
- }
- updateCart()
+ if (typeof auth !== 'object' || isNaN(debounceQty)) return;
+ if (debounceQty === item.quantity) return;
+
+ quantityUpdateState.startUpdate(item.id);
+ setIsLoadQuantity(true);
+
+ try {
+ // Update cookie immediately for responsive UI
+ updateQuantityInCookie(item.id, item.cart_id, debounceQty);
+
+ // Update local cart state optimistically
+ if (cart) {
+ const updatedProducts = cart.products.map((product) =>
+ product.id === item.id
+ ? { ...product, quantity: debounceQty }
+ : product
+ );
+ const updatedCart = {
+ ...cart,
+ products: updatedProducts,
+ };
+ updateCartItem(updatedCart);
+ }
+
+ // Send update to server
+ await upsertUserCart({
+ userId: auth.id,
+ type: item.cart_type,
+ id: item.id,
+ qty: debounceQty,
+ selected: item.selected,
+ });
+
+ // Reload from server to ensure consistency
+ await loadCart(auth.id);
+
+ // Re-update cookie if server reload overwrote it
+ const currentCookieData = getCartDataFromCookie();
+ let needsReUpdate = false;
+
+ for (const key in currentCookieData) {
+ const cookieItem = currentCookieData[key];
+ if (
+ (cookieItem.id === item.id ||
+ cookieItem.cart_id === item.cart_id) &&
+ cookieItem.quantity !== debounceQty
+ ) {
+ needsReUpdate = true;
+ break;
+ }
+ }
+
+ if (needsReUpdate) {
+ updateQuantityInCookie(item.id, item.cart_id, debounceQty);
+ }
+ } catch (error) {
+ console.error('Error updating quantity:', error);
+ toast.error('Gagal mengupdate quantity');
+
+ // Revert changes on error
+ updateQuantityInCookie(item.id, item.cart_id, item.quantity);
+ loadCart(auth.id);
+ } finally {
+ setIsLoadQuantity(false);
+ quantityUpdateState.endUpdate(item.id);
+ }
+ };
+
+ updateCart();
//eslint-disable-next-line react-hooks/exhaustive-deps
- }, [debounceQty])
+ }, [debounceQty]);
return (
<div className={style.actionSection}>
- <button className={style.deleteButton} onClick={handleDelete} disabled={isLoadDelete}>
+ <button
+ className={style.deleteButton}
+ onClick={handleDelete}
+ disabled={isLoadDelete}
+ >
{isLoadDelete && <Spinner size='xs' />}
{!isLoadDelete && <Trash2Icon size={16} />}
</button>
@@ -106,7 +246,7 @@ const CartItemAction = ({ item }: Props) => {
</Tooltip>
</div>
</div>
- )
-}
+ );
+};
-export default CartItemAction \ No newline at end of file
+export default CartItemAction; \ No newline at end of file