diff options
Diffstat (limited to 'src-migrate')
| -rw-r--r-- | src-migrate/modules/cart/components/ItemAction.tsx | 139 | ||||
| -rw-r--r-- | src-migrate/modules/cart/components/ItemSelect.tsx | 2 | ||||
| -rw-r--r-- | src-migrate/pages/shop/cart/index.tsx | 36 | ||||
| -rw-r--r-- | src-migrate/utils/cart.js | 68 |
4 files changed, 174 insertions, 71 deletions
diff --git a/src-migrate/modules/cart/components/ItemAction.tsx b/src-migrate/modules/cart/components/ItemAction.tsx index 7220e362..eea0cbe9 100644 --- a/src-migrate/modules/cart/components/ItemAction.tsx +++ b/src-migrate/modules/cart/components/ItemAction.tsx @@ -1,74 +1,121 @@ -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, +} 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(); // TAMBAHKAN cart dan updateCartItem - const limitQty = item.limit_qty?.transaction || 0 + const limitQty = item.limit_qty?.transaction || 0; + // PERBAIKI FUNCTION INI 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 { + // Step 1: Delete from server + await deleteUserCart(auth.id, [item.cart_id]); + + // Step 2: Clean up cookies IMMEDIATELY + removeSelectedItemsFromCookie([item.id]); + removeCartItemsFromCookie([item.cart_id]); + + // Step 3: 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); + } + + // Step 4: Reload from server to ensure consistency + 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 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 + if (typeof auth !== 'object' || isNaN(debounceQty)) return; - setIsLoadQuantity(true) + 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() + }); + await loadCart(auth.id); + setIsLoadQuantity(false); + }; + 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 +153,7 @@ const CartItemAction = ({ item }: Props) => { </Tooltip> </div> </div> - ) -} + ); +}; -export default CartItemAction
\ No newline at end of file +export default CartItemAction; diff --git a/src-migrate/modules/cart/components/ItemSelect.tsx b/src-migrate/modules/cart/components/ItemSelect.tsx index 70b656ec..f580f81d 100644 --- a/src-migrate/modules/cart/components/ItemSelect.tsx +++ b/src-migrate/modules/cart/components/ItemSelect.tsx @@ -109,7 +109,7 @@ const CartItemSelect = ({ item }: Props) => { id: item.id, qty: item.quantity, selected: newSelectedState, - purchase_tax_id: item.purchase_tax_id || null, // Ensure null for numeric fields + // purchase_tax_id: item.purchase_tax_id || null, // Ensure null for numeric fields }); // Reload cart to ensure consistency diff --git a/src-migrate/pages/shop/cart/index.tsx b/src-migrate/pages/shop/cart/index.tsx index eefe8d09..798ad318 100644 --- a/src-migrate/pages/shop/cart/index.tsx +++ b/src-migrate/pages/shop/cart/index.tsx @@ -329,24 +329,52 @@ const CartPage = () => { if (typeof auth !== 'object' || !cart) return; setIsLoadDelete(true); - checkboxUpdateState.startUpdate('delete_operation'); // Use special ID for delete + checkboxUpdateState.startUpdate('delete_operation'); try { const itemsToDelete = cart.products.filter((item) => item.selected); const itemIdsToDelete = itemsToDelete.map((item) => item.id); + const cartIdsToDelete = itemsToDelete.map((item) => item.cart_id); + // Step 1: Delete from server first for (const item of itemsToDelete) { await deleteUserCart(auth.id, [item.cart_id]); } - // Remove deleted items from cookie - removeSelectedItemsFromCookie(itemIdsToDelete); + // Step 2: Update local cart state immediately (optimistic update) + const updatedProducts = cart.products.filter((item) => !item.selected); + const updatedCart = { + ...cart, + products: updatedProducts, + product_total: updatedProducts.length, + }; + updateCartItem(updatedCart); - await loadCart(auth.id); + // Step 3: Clean up cookies AFTER state update + removeSelectedItemsFromCookie(itemIdsToDelete); + removeCartItemsFromCookie(cartIdsToDelete); + + // Step 4: Reload from server to ensure consistency (but don't wait for it to complete UI update) + loadCart(auth.id) + .then(() => { + console.log('Cart reloaded from server'); + }) + .catch((error) => { + console.error('Error reloading cart:', error); + // If reload fails, at least we have the optimistic update + }); + + // Step 5: Trigger context refresh setRefreshCart(true); + + // Success feedback + toast.success('Item berhasil dihapus'); } catch (error) { console.error('Failed to delete cart items:', error); toast.error('Gagal menghapus item'); + + // If deletion failed, reload cart to restore proper state + loadCart(auth.id); } finally { setIsLoadDelete(false); checkboxUpdateState.endUpdate('delete_operation'); diff --git a/src-migrate/utils/cart.js b/src-migrate/utils/cart.js index f474cbde..1ddc5446 100644 --- a/src-migrate/utils/cart.js +++ b/src-migrate/utils/cart.js @@ -297,26 +297,10 @@ export const removeCartItemsFromCookie = (cartIds) => { * Hapus item selected dari cookie berdasarkan product IDs * @param {Array} productIds Array product IDs untuk dihapus */ -export const removeSelectedItemsFromCookie = (productIds) => { - try { - const selectedItems = getSelectedItemsFromCookie(); - - // Hapus dari selectedItems - productIds.forEach((productId) => { - if (selectedItems[productId] !== undefined) { - delete selectedItems[productId]; - } - }); - - // Simpan kembali ke cookie - setSelectedItemsToCookie(selectedItems); - - return { selectedItems }; - } catch (error) { - console.error('Error removing selected items from cookie:', error); - return {}; - } -}; +/** + * Hapus item selected dari cookie berdasarkan product IDs dan juga hapus dari cart data + * @param {Array} productIds Array product IDs untuk dihapus + */ /** * Force reset semua selected items ke unselected state @@ -386,3 +370,47 @@ export const syncSelectedItemsWithCookie = (cartProducts) => { // Export the checkbox update state for use in components export { checkboxUpdateState }; + +/** + * Hapus item selected dari cookie berdasarkan product IDs dan juga hapus dari cart data + * @param {Array} productIds Array product IDs untuk dihapus + */ +/** + * Hapus item selected dari cookie berdasarkan product IDs dan juga hapus dari cart data + * @param {Array} productIds Array product IDs untuk dihapus + */ +export const removeSelectedItemsFromCookie = (productIds) => { + try { + const selectedItems = getSelectedItemsFromCookie(); + const cartData = getCartDataFromCookie(); + const cartIdsToRemove = []; + + // Find cart IDs that match the product IDs + for (const cartId in cartData) { + if (productIds.includes(cartData[cartId].id)) { + cartIdsToRemove.push(cartId); + } + } + + // Remove from selectedItems + productIds.forEach((productId) => { + if (selectedItems[productId] !== undefined) { + delete selectedItems[productId]; + } + }); + + // Remove from cartData + cartIdsToRemove.forEach((cartId) => { + delete cartData[cartId]; + }); + + // Save both cookies + setSelectedItemsToCookie(selectedItems); + setCartDataToCookie(cartData); + + return { selectedItems, cartData }; + } catch (error) { + console.error('Error removing selected items from cookie:', error); + return {}; + } +}; |
