diff options
| author | Miqdad <ahmadmiqdad27@gmail.com> | 2025-05-31 21:39:53 +0700 |
|---|---|---|
| committer | Miqdad <ahmadmiqdad27@gmail.com> | 2025-05-31 21:39:53 +0700 |
| commit | 77f976d3bd09d9e00d4d55bbd40579b439405d96 (patch) | |
| tree | a8959bba3d8a51570c439789f92653409a0065ae /src-migrate/modules/cart/components/ItemAction.tsx | |
| parent | ca05a70e98e9066882de6394ffbd89db7af2cb9d (diff) | |
| parent | 2a1dea70b8f0062fe8eebeb7139a7b77a24e220b (diff) | |
Merge branch 'new-release' of https://bitbucket.org/altafixco/next-indoteknik into biteship-merge
# Conflicts:
# src/lib/checkout/components/Checkout.jsx
# src/lib/transaction/components/Transaction.jsx
Diffstat (limited to 'src-migrate/modules/cart/components/ItemAction.tsx')
| -rw-r--r-- | src-migrate/modules/cart/components/ItemAction.tsx | 246 |
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 |
