summaryrefslogtreecommitdiff
path: root/src-migrate
diff options
context:
space:
mode:
Diffstat (limited to 'src-migrate')
-rw-r--r--src-migrate/modules/cart/components/ItemAction.tsx65
-rw-r--r--src-migrate/modules/cart/components/ItemSelect.tsx22
-rw-r--r--src-migrate/modules/cart/stores/useCartStore.ts98
-rw-r--r--src-migrate/pages/shop/cart/index.tsx121
4 files changed, 177 insertions, 129 deletions
diff --git a/src-migrate/modules/cart/components/ItemAction.tsx b/src-migrate/modules/cart/components/ItemAction.tsx
index 4dcebd9e..b06e8e75 100644
--- a/src-migrate/modules/cart/components/ItemAction.tsx
+++ b/src-migrate/modules/cart/components/ItemAction.tsx
@@ -22,11 +22,11 @@ import {
import { toast } from 'react-hot-toast';
-type Props = {
+interface Props {
item: CartItem;
-};
+}
-const CartItemAction = ({ item }: Props) => {
+const CartItemAction: React.FC<Props> = ({ item }) => {
const auth = getAuth();
const { setRefreshCart } = useProductCartContext();
const [isLoadDelete, setIsLoadDelete] = useState<boolean>(false);
@@ -36,9 +36,9 @@ const CartItemAction = ({ item }: Props) => {
const { loadCart, cart, updateCartItem } = useCartStore();
- const limitQty = item.limit_qty?.transaction || 0;
+ const limitQty: number = item.limit_qty?.transaction || 0;
- const handleDelete = async () => {
+ const handleDelete = async (): Promise<void> => {
if (typeof auth !== 'object') return;
setIsLoadDelete(true);
@@ -80,25 +80,29 @@ const CartItemAction = ({ item }: Props) => {
}
};
- const updateQuantityInCookie = (productId, cartId, newQuantity) => {
+ const updateQuantityInCookie = (
+ productId: number,
+ cartId: string | number,
+ newQuantity: number
+ ): boolean => {
try {
- const cartData = getCartDataFromCookie();
+ const cartData = getCartDataFromCookie() as Record<string, any>;
let itemFound = false;
+ const cartIdString = String(cartId);
// Find item by cart_id key or search within objects
- if (cartData[cartId]) {
- cartData[cartId].quantity = newQuantity;
+ if (cartData[cartIdString]) {
+ cartData[cartIdString].quantity = newQuantity;
itemFound = true;
} else {
// Search by product id or cart_id within objects
- for (const key in cartData) {
+ Object.keys(cartData).forEach((key) => {
const item = cartData[key];
- if (item.id === productId || item.cart_id === cartId) {
+ if (item.id === productId || String(item.cart_id) === cartIdString) {
item.quantity = newQuantity;
itemFound = true;
- break;
}
- }
+ });
}
if (itemFound) {
@@ -113,12 +117,12 @@ const CartItemAction = ({ item }: Props) => {
}
};
- const decreaseQty = () => {
- setQuantity((quantity) => (quantity -= 1));
+ const decreaseQty = (): void => {
+ setQuantity((prevQuantity) => prevQuantity - 1);
};
- const increaseQty = () => {
- setQuantity((quantity) => (quantity += 1));
+ const increaseQty = (): void => {
+ setQuantity((prevQuantity) => prevQuantity + 1);
};
const debounceQty = useDebounce(quantity, 1000);
@@ -129,7 +133,7 @@ const CartItemAction = ({ item }: Props) => {
}, [debounceQty, limitQty]);
useEffect(() => {
- const updateCart = async () => {
+ const updateCart = async (): Promise<void> => {
if (typeof auth !== 'object' || isNaN(debounceQty)) return;
if (debounceQty === item.quantity) return;
@@ -167,20 +171,22 @@ const CartItemAction = ({ item }: Props) => {
await loadCart(auth.id);
// Re-update cookie if server reload overwrote it
- const currentCookieData = getCartDataFromCookie();
+ const currentCookieData = getCartDataFromCookie() as Record<
+ string,
+ any
+ >;
let needsReUpdate = false;
- for (const key in currentCookieData) {
+ Object.keys(currentCookieData).forEach((key) => {
const cookieItem = currentCookieData[key];
if (
(cookieItem.id === item.id ||
- cookieItem.cart_id === item.cart_id) &&
+ String(cookieItem.cart_id) === String(item.cart_id)) &&
cookieItem.quantity !== debounceQty
) {
needsReUpdate = true;
- break;
}
- }
+ });
if (needsReUpdate) {
updateQuantityInCookie(item.id, item.cart_id, debounceQty);
@@ -202,6 +208,13 @@ const CartItemAction = ({ item }: Props) => {
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [debounceQty]);
+ const handleQuantityInputChange = (
+ e: React.ChangeEvent<HTMLInputElement>
+ ): void => {
+ const value = parseInt(e.target.value);
+ setQuantity(isNaN(value) ? 1 : value);
+ };
+
return (
<div className={style.actionSection}>
<button
@@ -230,8 +243,8 @@ const CartItemAction = ({ item }: Props) => {
<input
type='number'
- className={style.quantity.toString()}
- onChange={(e) => setQuantity(parseInt(e.target.value))}
+ className={style.quantity}
+ onChange={handleQuantityInputChange}
value={quantity}
/>
@@ -249,4 +262,4 @@ const CartItemAction = ({ item }: Props) => {
);
};
-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 72ab49aa..f95ee36c 100644
--- a/src-migrate/modules/cart/components/ItemSelect.tsx
+++ b/src-migrate/modules/cart/components/ItemSelect.tsx
@@ -11,11 +11,11 @@ import {
checkboxUpdateState,
} from '~/utils/cart';
-type Props = {
+interface Props {
item: CartItem;
-};
+}
-const CartItemSelect = ({ item }: Props) => {
+const CartItemSelect: React.FC<Props> = ({ item }) => {
const auth = getAuth();
const { updateCartItem, cart, loadCart } = useCartStore();
const [isUpdating, setIsUpdating] = useState<boolean>(false);
@@ -23,7 +23,7 @@ const CartItemSelect = ({ item }: Props) => {
// Subscribe to global checkbox update state
useEffect(() => {
- const handleUpdateStateChange = (isUpdating) => {
+ const handleUpdateStateChange = (isUpdating: boolean): void => {
// This component doesn't need to react to global state changes
// Individual checkboxes are managed independently
};
@@ -36,7 +36,10 @@ const CartItemSelect = ({ item }: Props) => {
useEffect(() => {
if (isUpdating) return;
- const selectedItems = getSelectedItemsFromCookie();
+ const selectedItems = getSelectedItemsFromCookie() as Record<
+ number,
+ boolean
+ >;
const storedState = selectedItems[item.id];
if (storedState !== undefined) {
@@ -62,7 +65,7 @@ const CartItemSelect = ({ item }: Props) => {
}, [item.id, item.selected, localSelected, cart, updateCartItem, isUpdating]);
const handleChange = useCallback(
- async (e: React.ChangeEvent<HTMLInputElement>) => {
+ async (e: React.ChangeEvent<HTMLInputElement>): Promise<void> => {
if (typeof auth !== 'object' || !cart || isUpdating) return;
const newSelectedState = e.target.checked;
@@ -70,7 +73,7 @@ const CartItemSelect = ({ item }: Props) => {
// Update local state immediately
setLocalSelected(newSelectedState);
setIsUpdating(true);
- checkboxUpdateState.startUpdate(item.id);
+ checkboxUpdateState.startUpdate();
try {
// Update cookie immediately for responsive UI
@@ -107,14 +110,13 @@ const CartItemSelect = ({ item }: Props) => {
loadCart(auth.id);
} finally {
setIsUpdating(false);
- checkboxUpdateState.endUpdate(item.id);
+ checkboxUpdateState.endUpdate();
}
},
[auth, cart, item, isUpdating, updateCartItem, loadCart]
);
- const isDisabled =
- isUpdating || checkboxUpdateState.isCheckboxUpdating(item.id);
+ const isDisabled: boolean = isUpdating;
return (
<div className='w-6 my-auto relative'>
diff --git a/src-migrate/modules/cart/stores/useCartStore.ts b/src-migrate/modules/cart/stores/useCartStore.ts
index dc47b011..69cf0384 100644
--- a/src-migrate/modules/cart/stores/useCartStore.ts
+++ b/src-migrate/modules/cart/stores/useCartStore.ts
@@ -8,24 +8,32 @@ import {
forceResetAllSelectedItems,
} from '~/utils/cart';
-type State = {
+interface Summary {
+ subtotal: number;
+ discount: number;
+ total: number;
+ tax: number;
+ grandTotal: number;
+}
+
+interface SyncResult {
+ cartData?: Record<string, any>;
+ selectedItems?: Record<number, boolean>;
+ needsUpdate: boolean;
+}
+
+interface State {
cart: CartProps | null;
isLoadCart: boolean;
- summary: {
- subtotal: number;
- discount: number;
- total: number;
- tax: number;
- grandTotal: number;
- };
-};
+ summary: Summary;
+}
-type Action = {
+interface Action {
loadCart: (userId: number) => Promise<void>;
updateCartItem: (updateCart: CartProps) => void;
forceResetSelection: () => void;
clearCart: () => void;
-};
+}
export const useCartStore = create<State & Action>((set, get) => ({
cart: null,
@@ -38,7 +46,7 @@ export const useCartStore = create<State & Action>((set, get) => ({
grandTotal: 0,
},
- loadCart: async (userId) => {
+ loadCart: async (userId: number): Promise<void> => {
if (get().isLoadCart) return;
set({ isLoadCart: true });
@@ -47,12 +55,15 @@ export const useCartStore = create<State & Action>((set, get) => ({
const cart: CartProps = (await getUserCart(userId)) as CartProps;
// Sync with cookie data
- const syncResult = syncCartWithCookie(cart);
+ const syncResult = syncCartWithCookie(cart) as SyncResult;
if (syncResult?.needsUpdate && cart.products) {
- const selectedItems = getSelectedItemsFromCookie();
+ const selectedItems = getSelectedItemsFromCookie() as Record<
+ number,
+ boolean
+ >;
- const updatedCart = {
+ const updatedCart: CartProps = {
...cart,
products: cart.products.map((item) => ({
...item,
@@ -81,7 +92,7 @@ export const useCartStore = create<State & Action>((set, get) => ({
}
},
- updateCartItem: (updatedCart) => {
+ updateCartItem: (updatedCart: CartProps): void => {
set({ cart: updatedCart });
syncCartWithCookie(updatedCart);
@@ -89,13 +100,13 @@ export const useCartStore = create<State & Action>((set, get) => ({
set({ summary });
},
- forceResetSelection: () => {
+ forceResetSelection: (): void => {
const { cart } = get();
if (!cart) return;
forceResetAllSelectedItems();
- const updatedCart = {
+ const updatedCart: CartProps = {
...cart,
products: cart.products.map((item) => ({ ...item, selected: false })),
};
@@ -106,7 +117,7 @@ export const useCartStore = create<State & Action>((set, get) => ({
set({ summary });
},
- clearCart: () => {
+ clearCart: (): void => {
set({
cart: null,
summary: {
@@ -121,13 +132,15 @@ export const useCartStore = create<State & Action>((set, get) => ({
}));
// Helper function for cookie fallback
-const handleFallbackFromCookie = async () => {
+const handleFallbackFromCookie = async (): Promise<void> => {
try {
- const cartData = getCartDataFromCookie();
+ const cartData = getCartDataFromCookie() as Record<string, any>;
if (Object.keys(cartData).length === 0) return;
- const products = Object.values(cartData).map(transformCookieItemToProduct);
+ const products: CartItem[] = Object.values(cartData).map(
+ transformCookieItemToProduct
+ );
const fallbackCart: CartProps = {
product_total: products.length,
@@ -145,22 +158,43 @@ const handleFallbackFromCookie = async () => {
// Helper function to transform cookie item to product format
const transformCookieItemToProduct = (item: any): CartItem => ({
+ image_program: item.image_program || '',
cart_id: item.cart_id,
- id: item.id,
- cart_type: item.cart_type,
- product_id: item.product?.id,
- product_name: item.product?.name,
- program_line_id: item.program_line?.id,
- program_line_name: item.program_line?.name,
quantity: item.quantity,
selected: item.selected,
- price: item.price,
+ can_buy: true,
+ cart_type: item.cart_type,
+ id: item.id,
+ name: item.product?.name || item.program_line?.name || '',
+ stock: 0,
+ is_in_bu: false,
+ on_hand_qty: 0,
+ available_quantity: 0,
+ weight: 0,
+ attributes: [],
+ parent: {
+ id: 0,
+ name: '',
+ image: '',
+ },
+ price: item.price || {
+ price: 0,
+ discount_percentage: 0,
+ price_discount: 0,
+ },
+ manufacture: {
+ id: 0,
+ name: '',
+ },
+ has_flashsale: false,
+ subtotal: 0,
+ code: item.code,
+ image: item.image,
package_price: item.package_price,
- source: item.source,
});
// Helper function to compute cart summary
-const computeSummary = (cart: CartProps) => {
+const computeSummary = (cart: CartProps): Summary => {
if (!cart?.products) {
return { subtotal: 0, discount: 0, total: 0, grandTotal: 0, tax: 0 };
}
@@ -186,4 +220,4 @@ const computeSummary = (cart: CartProps) => {
const tax = grandTotal - total;
return { subtotal, discount, total, grandTotal, tax };
-}; \ No newline at end of file
+};
diff --git a/src-migrate/pages/shop/cart/index.tsx b/src-migrate/pages/shop/cart/index.tsx
index 03854d79..795dfa72 100644
--- a/src-migrate/pages/shop/cart/index.tsx
+++ b/src-migrate/pages/shop/cart/index.tsx
@@ -29,24 +29,26 @@ import {
const SELECT_ALL_ID = 'select_all_checkbox';
-const CartPage = () => {
+const CartPage: React.FC = () => {
const router = useRouter();
const auth = getAuth();
- const [isStepApproval, setIsStepApproval] = useState(false);
+ const [isStepApproval, setIsStepApproval] = useState<boolean>(false);
const [isLoadDelete, setIsLoadDelete] = useState<boolean>(false);
const { loadCart, cart, summary, updateCartItem } = useCartStore();
const device = useDevice();
const { setRefreshCart } = useProductCartContext();
- const [isTop, setIsTop] = useState(true);
- const [isUpdating, setIsUpdating] = useState(false);
- const [isAnyCheckboxUpdating, setIsAnyCheckboxUpdating] = useState(false);
- const [isAnyQuantityUpdating, setIsAnyQuantityUpdating] = useState(false);
+ const [isTop, setIsTop] = useState<boolean>(true);
+ const [isUpdating, setIsUpdating] = useState<boolean>(false);
+ const [isAnyCheckboxUpdating, setIsAnyCheckboxUpdating] =
+ useState<boolean>(false);
+ const [isAnyQuantityUpdating, setIsAnyQuantityUpdating] =
+ useState<boolean>(false);
// Subscribe to update state changes
useEffect(() => {
- const handleCheckboxUpdate = (isUpdating) =>
+ const handleCheckboxUpdate = (isUpdating: boolean): void =>
setIsAnyCheckboxUpdating(isUpdating);
- const handleQuantityUpdate = (isUpdating) =>
+ const handleQuantityUpdate = (isUpdating: boolean): void =>
setIsAnyQuantityUpdating(isUpdating);
checkboxUpdateState.addListener(handleCheckboxUpdate);
@@ -60,7 +62,7 @@ const CartPage = () => {
// Handle scroll for sticky header styling
useEffect(() => {
- const handleScroll = () => setIsTop(window.scrollY < 200);
+ const handleScroll = (): void => setIsTop(window.scrollY < 200);
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
@@ -68,36 +70,40 @@ const CartPage = () => {
// Initialize cart and sync with cookies
useEffect(() => {
- const initializeCart = async () => {
+ const initializeCart = async (): Promise<void> => {
if (typeof auth === 'object' && !cart) {
await loadCart(auth.id);
setIsStepApproval(auth?.feature?.soApproval);
-
- if (cart?.products) {
- const { items, needsUpdate } = syncSelectedItemsWithCookie(
- cart.products
- );
-
- if (needsUpdate) {
- const updatedCart = {
- ...cart,
- products: cart.products.map((item) => ({
- ...item,
- selected:
- items[item.id] !== undefined ? items[item.id] : item.selected,
- })),
- };
- updateCartItem(updatedCart);
- }
- }
}
};
initializeCart();
- }, [auth, cart, loadCart, updateCartItem]);
+ }, [auth, cart, loadCart]);
+
+ // Separate effect to sync with cookies after cart is loaded
+ useEffect(() => {
+ if (cart?.products) {
+ const { items, needsUpdate } = syncSelectedItemsWithCookie(cart.products);
+ const typedItems = items as Record<number, boolean>;
+
+ if (needsUpdate) {
+ const updatedCart = {
+ ...cart,
+ products: cart.products.map((item) => ({
+ ...item,
+ selected:
+ typedItems[item.id] !== undefined
+ ? typedItems[item.id]
+ : item.selected,
+ })),
+ };
+ updateCartItem(updatedCart);
+ }
+ }
+ }, [cart, updateCartItem]);
// Computed values
- const hasSelectedPromo = useMemo(() => {
+ const hasSelectedPromo = useMemo((): boolean => {
return (
cart?.products?.some(
(item) => item.cart_type === 'promotion' && item.selected
@@ -105,11 +111,11 @@ const CartPage = () => {
);
}, [cart]);
- const hasSelected = useMemo(() => {
+ const hasSelected = useMemo((): boolean => {
return cart?.products?.some((item) => item.selected) || false;
}, [cart]);
- const hasSelectNoPrice = useMemo(() => {
+ const hasSelectNoPrice = useMemo((): boolean => {
return (
cart?.products?.some(
(item) => item.selected && item.price.price_discount === 0
@@ -117,22 +123,22 @@ const CartPage = () => {
);
}, [cart]);
- const hasSelectedAll = useMemo(() => {
+ const hasSelectedAll = useMemo((): boolean => {
if (!cart?.products?.length) return false;
return cart.products.every((item) => item.selected);
}, [cart]);
// Button states
- const areButtonsDisabled =
+ const areButtonsDisabled: boolean =
isUpdating ||
isLoadDelete ||
isAnyCheckboxUpdating ||
isAnyQuantityUpdating;
- const isSelectAllDisabled =
- isUpdating || checkboxUpdateState.isCheckboxUpdating(SELECT_ALL_ID);
+ const isSelectAllDisabled: boolean =
+ isUpdating || checkboxUpdateState.isCheckboxUpdating();
// Handlers
- const handleCheckout = () => {
+ const handleCheckout = (): void => {
if (areButtonsDisabled) {
toast.error('Harap tunggu pembaruan selesai');
return;
@@ -140,7 +146,7 @@ const CartPage = () => {
router.push('/shop/checkout');
};
- const handleQuotation = () => {
+ const handleQuotation = (): void => {
if (areButtonsDisabled) {
toast.error('Harap tunggu pembaruan selesai');
return;
@@ -152,12 +158,14 @@ const CartPage = () => {
}
};
- const handleSelectAll = async (e: React.ChangeEvent<HTMLInputElement>) => {
+ const handleSelectAll = async (
+ e: React.ChangeEvent<HTMLInputElement>
+ ): Promise<void> => {
if (!cart || isUpdating || typeof auth !== 'object') return;
const newSelectedState = !hasSelectedAll;
setIsUpdating(true);
- checkboxUpdateState.startUpdate(SELECT_ALL_ID);
+ checkboxUpdateState.startUpdate();
try {
// Update UI immediately
@@ -182,7 +190,6 @@ const CartPage = () => {
id: item.id,
qty: item.quantity,
selected: newSelectedState,
- purchase_tax_id: item.purchase_tax_id || null,
})
);
@@ -208,15 +215,15 @@ const CartPage = () => {
);
} finally {
setIsUpdating(false);
- checkboxUpdateState.endUpdate(SELECT_ALL_ID);
+ checkboxUpdateState.endUpdate();
}
};
- const handleDelete = async () => {
+ const handleDelete = async (): Promise<void> => {
if (typeof auth !== 'object' || !cart) return;
setIsLoadDelete(true);
- checkboxUpdateState.startUpdate('delete_operation');
+ checkboxUpdateState.startUpdate();
try {
const itemsToDelete = cart.products.filter((item) => item.selected);
@@ -239,7 +246,7 @@ const CartPage = () => {
// Clean up cookies
removeSelectedItemsFromCookie(itemIdsToDelete);
- removeCartItemsFromCookie(cartIdsToDelete);
+ removeCartItemsFromCookie(cartIdsToDelete.map(String));
// Reload from server
loadCart(auth.id).catch((error) =>
@@ -254,12 +261,12 @@ const CartPage = () => {
loadCart(auth.id);
} finally {
setIsLoadDelete(false);
- checkboxUpdateState.endUpdate('delete_operation');
+ checkboxUpdateState.endUpdate();
}
};
// Tooltip messages
- const getTooltipMessage = () => {
+ const getTooltipMessage = (): string => {
if (isAnyQuantityUpdating) return 'Harap tunggu update quantity selesai';
if (isAnyCheckboxUpdating) return 'Harap tunggu pembaruan checkbox selesai';
if (isLoadDelete) return 'Harap tunggu penghapusan selesai';
@@ -267,7 +274,7 @@ const CartPage = () => {
return '';
};
- const getQuotationTooltip = () => {
+ const getQuotationTooltip = (): string => {
const baseMessage = getTooltipMessage();
if (baseMessage) return baseMessage;
if (hasSelectedPromo) return 'Barang promo tidak dapat dibuat quotation';
@@ -275,7 +282,7 @@ const CartPage = () => {
return '';
};
- const getCheckoutTooltip = () => {
+ const getCheckoutTooltip = (): string => {
const baseMessage = getTooltipMessage();
if (baseMessage) return baseMessage;
if (!hasSelected) return 'Tidak ada item yang dipilih';
@@ -283,7 +290,7 @@ const CartPage = () => {
return '';
};
- const getDeleteTooltip = () => {
+ const getDeleteTooltip = (): string => {
const baseMessage = getTooltipMessage();
if (baseMessage) return baseMessage;
if (!hasSelected) return 'Tidak ada item yang dipilih';
@@ -395,17 +402,9 @@ const CartPage = () => {
>
<div className={style.summary}>
{device.isMobile ? (
- <CartSummaryMobile
- {...summary}
- isLoaded={!!cart}
- products={cart?.products}
- />
+ <CartSummaryMobile {...summary} isLoaded={!!cart} />
) : (
- <CartSummary
- {...summary}
- isLoaded={!!cart}
- products={cart?.products}
- />
+ <CartSummary {...summary} isLoaded={!!cart} />
)}
<div
@@ -452,4 +451,4 @@ const CartPage = () => {
);
};
-export default CartPage; \ No newline at end of file
+export default CartPage;