diff options
Diffstat (limited to 'src-migrate')
| -rw-r--r-- | src-migrate/libs/auth.ts | 5 | ||||
| -rw-r--r-- | src-migrate/modules/cart/components/Item.tsx | 9 | ||||
| -rw-r--r-- | src-migrate/modules/cart/components/ItemSelect.tsx | 28 | ||||
| -rw-r--r-- | src-migrate/modules/cart/stores/useCartStore.ts | 12 | ||||
| -rw-r--r-- | src-migrate/modules/product-detail/components/Information.tsx | 2 | ||||
| -rw-r--r-- | src-migrate/modules/product-detail/stores/useProductDetail.ts | 2 | ||||
| -rw-r--r-- | src-migrate/modules/promo/components/Hero.tsx | 10 | ||||
| -rw-r--r-- | src-migrate/modules/promo/components/HeroDiskon.tsx | 22 | ||||
| -rw-r--r-- | src-migrate/modules/promo/components/PromoList.tsx | 4 | ||||
| -rw-r--r-- | src-migrate/modules/promo/components/Voucher.tsx | 16 | ||||
| -rw-r--r-- | src-migrate/pages/shop/cart/index.tsx | 96 | ||||
| -rw-r--r-- | src-migrate/types/cart.ts | 2 | ||||
| -rw-r--r-- | src-migrate/types/product.ts | 1 |
13 files changed, 145 insertions, 64 deletions
diff --git a/src-migrate/libs/auth.ts b/src-migrate/libs/auth.ts index 86ce26e1..e8516747 100644 --- a/src-migrate/libs/auth.ts +++ b/src-migrate/libs/auth.ts @@ -1,5 +1,8 @@ import { deleteCookie, getCookie, setCookie } from 'cookies-next'; import { AuthProps } from '~/types/auth'; +// @ts-ignore +import camelcaseObjectDeep from 'camelcase-object-deep'; + const COOKIE_KEY = 'auth'; @@ -14,7 +17,7 @@ export const getAuth = (): AuthProps | boolean => { }; export const setAuth = (user: AuthProps): boolean => { - setCookie(COOKIE_KEY, JSON.stringify(user)); + setCookie(COOKIE_KEY, JSON.stringify(camelcaseObjectDeep(user))); return true; }; diff --git a/src-migrate/modules/cart/components/Item.tsx b/src-migrate/modules/cart/components/Item.tsx index 47893498..6ffbb524 100644 --- a/src-migrate/modules/cart/components/Item.tsx +++ b/src-migrate/modules/cart/components/Item.tsx @@ -17,11 +17,11 @@ import CartItemSelect from './ItemSelect' type Props = { item: CartItemProps editable?: boolean + selfPicking?: boolean pilihSemuaCart?: boolean } -const CartItem = ({ item, editable = true,}: Props) => { - +const CartItem = ({ item, editable = true, selfPicking}: Props) => { return ( <div className={style.wrapper}> {item.cart_type === 'promotion' && ( @@ -53,6 +53,11 @@ const CartItem = ({ item, editable = true,}: Props) => { <CartItem.Image item={item} /> <div className={style.details}> + {(item.is_in_bu) && (item.on_hand_qty >= item.quantity) && ( + <div className='text-[10px] text-red-500 italic'> + *Barang ini bisa di pickup maksimal pukul 16.00 + </div> + )} <CartItem.Name item={item} /> <div className='mt-2 flex justify-between w-full'> diff --git a/src-migrate/modules/cart/components/ItemSelect.tsx b/src-migrate/modules/cart/components/ItemSelect.tsx index b904a1de..d4a1b537 100644 --- a/src-migrate/modules/cart/components/ItemSelect.tsx +++ b/src-migrate/modules/cart/components/ItemSelect.tsx @@ -13,23 +13,25 @@ type Props = { const CartItemSelect = ({ item }: Props) => { const auth = getAuth() - const { loadCart } = useCartStore() + const { updateCartItem, cart } = useCartStore() const [isLoad, setIsLoad] = useState<boolean>(false) const handleChange = async (e: React.ChangeEvent<HTMLInputElement>) => { - if (typeof auth !== 'object') return - - setIsLoad(true) - await upsertUserCart({ - userId: auth.id, - type: item.cart_type, - id: item.id, - qty: item.quantity, - selected: e.target.checked - }) - await loadCart(auth.id) - setIsLoad(false) + if (typeof auth !== 'object' || !cart) return + + setIsLoad(true); + const updatedCartItems = cart.products.map(cartItem => + cartItem.id === item.id + ? { ...cartItem, selected: e.target.checked } + : cartItem + ); + + // Update the entire cart + const updatedCart = { ...cart, products: updatedCartItems }; + updateCartItem(updatedCart); + + setIsLoad(false); } return ( diff --git a/src-migrate/modules/cart/stores/useCartStore.ts b/src-migrate/modules/cart/stores/useCartStore.ts index 3d9a0aed..3b50ec32 100644 --- a/src-migrate/modules/cart/stores/useCartStore.ts +++ b/src-migrate/modules/cart/stores/useCartStore.ts @@ -1,5 +1,5 @@ import { create } from 'zustand'; -import { CartProps } from '~/types/cart'; +import { CartItem, CartProps } from '~/types/cart'; import { getUserCart } from '~/services/cart'; type State = { @@ -16,6 +16,7 @@ type State = { type Action = { loadCart: (userId: number) => Promise<void>; + updateCartItem: (updateCart: CartProps) => void; }; export const useCartStore = create<State & Action>((set, get) => ({ @@ -39,6 +40,15 @@ export const useCartStore = create<State & Action>((set, get) => ({ const summary = computeSummary(cart); set({ summary }); }, + updateCartItem: (updatedCart) => { + const cart = get().cart; + if (!cart) return; + + set({ cart: updatedCart }); + const summary = computeSummary(updatedCart); + set({ summary }); + }, + })); const computeSummary = (cart: CartProps) => { diff --git a/src-migrate/modules/product-detail/components/Information.tsx b/src-migrate/modules/product-detail/components/Information.tsx index 52eb6b88..75ae3c41 100644 --- a/src-migrate/modules/product-detail/components/Information.tsx +++ b/src-migrate/modules/product-detail/components/Information.tsx @@ -19,7 +19,7 @@ type Props = { const Information = ({ product }: Props) => { const querySLA = useQuery<IProductVariantSLA>({ - queryKey: ['variant-sla', product.variants[0].id], + queryKey: ['variant-sla', product.variants[0]?.id], queryFn: () => getVariantSLA(product.variants[0].id), enabled: product.variant_total === 1 }) diff --git a/src-migrate/modules/product-detail/stores/useProductDetail.ts b/src-migrate/modules/product-detail/stores/useProductDetail.ts index 2da8835d..eb409930 100644 --- a/src-migrate/modules/product-detail/stores/useProductDetail.ts +++ b/src-migrate/modules/product-detail/stores/useProductDetail.ts @@ -23,7 +23,7 @@ export const useProductDetail = create<State & Action>((set, get) => ({ askAdminUrl: '', isApproval : false, setActive: (variant) => { - set({ activeVariantId: variant.id, activePrice: variant.price }); + set({ activeVariantId: variant?.id, activePrice: variant?.price }); }, setQuantityInput: (value: string) => { set({ quantityInput: value }); diff --git a/src-migrate/modules/promo/components/Hero.tsx b/src-migrate/modules/promo/components/Hero.tsx index c5f0afad..97cbe0b7 100644 --- a/src-migrate/modules/promo/components/Hero.tsx +++ b/src-migrate/modules/promo/components/Hero.tsx @@ -60,12 +60,12 @@ const Hero = () => { <DesktopView> <div className={style['wrapper']}> <Swiper {...swiperBanner}> - {banners.map((banner, index) => ( + {banners?.map((banner, index) => ( <SwiperSlide key={index} className='flex flex-row'> <div className={style['desc-section']}> - <div className={style['title']}>{banner.headlineBanner? banner.headlineBanner : "Pasti Hemat & Untung Selama Belanja di Indoteknik.com!"}</div> + <div className={style['title']}>{banner?.headlineBanner? banner?.headlineBanner : "Pasti Hemat & Untung Selama Belanja di Indoteknik.com!"}</div> <div className='h-4' /> - <div className={style['subtitle']}>{banner.descriptionBanner? banner.descriptionBanner : "Cari paket yang kami sediakan dengan penawaran harga & Nikmati kemudahan dalam setiap transaksi dengan fitur lengkap Pembayaran hingga barang sampai!"}</div> + <div className={style['subtitle']}>{banner?.descriptionBanner? banner?.descriptionBanner : "Cari paket yang kami sediakan dengan penawaran harga & Nikmati kemudahan dalam setiap transaksi dengan fitur lengkap Pembayaran hingga barang sampai!"}</div> </div> <div className={style['banner-section']}> <Image @@ -89,8 +89,8 @@ const Hero = () => { width={439} height={150} quality={100} - src={banner.image} - alt={banner.name} + src={banner?.image} + alt={banner?.name} className='w-full h-full object-cover object-center rounded-2xl' /> </SwiperSlide> diff --git a/src-migrate/modules/promo/components/HeroDiskon.tsx b/src-migrate/modules/promo/components/HeroDiskon.tsx index 6d38c763..8b8edcc0 100644 --- a/src-migrate/modules/promo/components/HeroDiskon.tsx +++ b/src-migrate/modules/promo/components/HeroDiskon.tsx @@ -39,7 +39,7 @@ const Hero = () => { queryFn: () => getBanner({ type: 'banner-promotion' }) }) - const banners = useMemo(() => bannerQuery.data || [], [bannerQuery.data]); + const banners = useMemo(() => bannerQuery?.data || [], [bannerQuery?.data]); useEffect(() => { if (banners.length > 1) { @@ -56,8 +56,8 @@ const Hero = () => { {banners.map((banner, index) => ( <SwiperSlide key={index}> <Image - src={banner.image} - alt={banner.name} + src={banner?.image} + alt={banner?.name} width={666} height={480} className='w-[446px] h-[446px] object-fill object-center rounded-2xl' @@ -71,8 +71,8 @@ const Hero = () => { {banners.map((banner, index) => ( <SwiperSlide key={index}> <Image - src={banner.image} - alt={banner.name} + src={banner?.image} + alt={banner?.name} width={666} height={450} className='w-[400px] h-[217px] object-cover object-center rounded-2xl ' @@ -86,8 +86,8 @@ const Hero = () => { {banners.map((banner, index) => ( <SwiperSlide key={index}> <Image - src={banner.image} - alt={banner.name} + src={banner?.image} + alt={banner?.name} width={666} height={450} className='w-[400px] h-[217px] object-cover object-center rounded-2xl' @@ -101,8 +101,8 @@ const Hero = () => { {banners.map((banner, index) => ( <SwiperSlide key={index}> <Image - src={banner.image} - alt={banner.name} + src={banner?.image} + alt={banner?.name} width={666} height={450} className='w-[400px] h-[217px] object-cover object-center rounded-2xl' @@ -116,8 +116,8 @@ const Hero = () => { {banners.map((banner, index) => ( <SwiperSlide key={index}> <Image - src={banner.image} - alt={banner.name} + src={banner?.image} + alt={banner?.name} width={666} height={450} className='w-[400px] h-[217px] object-cover object-center rounded-2xl' diff --git a/src-migrate/modules/promo/components/PromoList.tsx b/src-migrate/modules/promo/components/PromoList.tsx index 42725034..69a5ef48 100644 --- a/src-migrate/modules/promo/components/PromoList.tsx +++ b/src-migrate/modules/promo/components/PromoList.tsx @@ -59,7 +59,7 @@ const PromoList: React.FC<PromoListProps> = ({ selectedPromo }) => { const items = await fetchPromoItemsSolr(`type_value_s:${slug}`, 0, 10); setPromoItems(items); - const promoDataPromises = items.map(async (item) => { + const promoDataPromises = items?.map(async (item) => { try { const response = await fetchPromoItemsSolr(`id:${item.id}`, 0, 10); return response; @@ -69,7 +69,7 @@ const PromoList: React.FC<PromoListProps> = ({ selectedPromo }) => { }); const promoDataArray = await Promise.all(promoDataPromises); - const mergedPromoData = promoDataArray.reduce((accumulator, currentValue) => accumulator.concat(currentValue), []); + const mergedPromoData = promoDataArray?.reduce((accumulator, currentValue) => accumulator.concat(currentValue), []); setPromoData(mergedPromoData); } catch (error) { diff --git a/src-migrate/modules/promo/components/Voucher.tsx b/src-migrate/modules/promo/components/Voucher.tsx index e5877e51..510fe746 100644 --- a/src-migrate/modules/promo/components/Voucher.tsx +++ b/src-migrate/modules/promo/components/Voucher.tsx @@ -56,7 +56,7 @@ const VoucherComponent = () => { spaceBetween: 2, }; - const dataVouchers = useMemo(() => voucherQuery.data || [], [voucherQuery.data]); + const dataVouchers = useMemo(() => voucherQuery?.data || [], [voucherQuery?.data]); const vouchers = auth?.id? listVouchers : dataVouchers; @@ -121,30 +121,30 @@ const VoucherComponent = () => { <div className='h-6' /> - {voucherQuery.isLoading && ( + {voucherQuery?.isLoading && ( <div className='grid grid-cols-3 gap-x-4 animate-pulse'> {Array.from({ length: 3 }).map((_, index) => ( <div key={index} className='w-full h-[160px] bg-gray-200 rounded-xl' /> ))} </div> )} - {!voucherQuery.isLoading && ( + {!voucherQuery?.isLoading && ( <div className={style['voucher-section']}> <Swiper {...swiperVoucher}> {vouchers?.map((voucher) => ( <SwiperSlide key={voucher.id} className='pb-2'> <div className={style['voucher-card']}> - <Image src={voucher.image} alt={voucher.name} width={128} height={128} className={style['voucher-image']} /> + <Image src={voucher?.image} alt={voucher?.name} width={128} height={128} className={style['voucher-image']} /> <div className={style['voucher-content']}> - <div className={style['voucher-title']}>{voucher.name}</div> - <div className={style['voucher-desc']}>{voucher.description}</div> + <div className={style['voucher-title']}>{voucher?.name}</div> + <div className={style['voucher-desc']}>{voucher?.description}</div> <div className={style['voucher-bottom']}> <div> <div className={style['voucher-code-desc']}>Kode Promo</div> - <div className={style['voucher-code']}>{voucher.code}</div> + <div className={style['voucher-code']}>{voucher?.code}</div> </div> - <button className={style['voucher-copy']} onClick={() => copyText(voucher.code)}>Salin</button> + <button className={style['voucher-copy']} onClick={() => copyText(voucher?.code)}>Salin</button> </div> </div> </div> diff --git a/src-migrate/pages/shop/cart/index.tsx b/src-migrate/pages/shop/cart/index.tsx index 73b002b6..5e3e042a 100644 --- a/src-migrate/pages/shop/cart/index.tsx +++ b/src-migrate/pages/shop/cart/index.tsx @@ -1,6 +1,6 @@ import style from './cart.module.css'; -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import Link from 'next/link'; import { Button, Checkbox, Spinner, Tooltip } from '@chakra-ui/react'; import { toast } from 'react-hot-toast'; @@ -28,10 +28,12 @@ const CartPage = () => { const [buttonSelectNow, setButtonSelectNow] = useState(true); const [isLoad, setIsLoad] = useState<boolean>(false) const [isLoadDelete, setIsLoadDelete] = useState<boolean>(false) - const { loadCart, cart, summary } = useCartStore(); + const { loadCart, cart, summary, updateCartItem } = useCartStore(); const useDivvice = useDevice(); const { setRefreshCart } = useProductCartContext() const [isTop, setIsTop] = useState(true); + const [hasChanged, setHasChanged] = useState(false); + const prevCartRef = useRef<CartItem[] | null>(null); useEffect(() => { const handleScroll = () => { @@ -51,6 +53,34 @@ const CartPage = () => { } }, [auth, loadCart, cart, isButtonChek]); + useEffect(() => { + if (typeof auth === 'object' && !cart) { + loadCart(auth.id); + setIsStepApproval(auth?.feature?.soApproval); + } + }, [auth, loadCart, cart, isButtonChek]); + + useEffect(() => { + const hasSelectedChanged = () => { + if (prevCartRef.current && cart) { + const prevCart = prevCartRef.current; + return cart.products.some((item, index) => + prevCart[index] && prevCart[index].selected !== item.selected + ); + } + return false; + }; + + if (hasSelectedChanged()) { + setHasChanged(true) + // Perform necessary actions here if selection has changed + }else{ + setHasChanged(false) + } + + prevCartRef.current = cart ? [...cart.products] : null; + }, [cart]); + const hasSelectedPromo = useMemo(() => { if (!cart) return false; return cart.products.some(item => item.cart_type === 'promotion' && item.selected); @@ -71,6 +101,31 @@ const CartPage = () => { return cart.products.every(item => item.selected); }, [cart]); + + useEffect(() => { + const updateCartItems = async () => { + if (typeof auth === 'object' && cart) { + const upsertPromises = cart.products.map(item => + upsertUserCart({ + userId: auth.id, + type: item.cart_type, + id: item.id, + qty: item.quantity, + selected: item.selected + }) + ); + try { + await Promise.all(upsertPromises); + await loadCart(auth.id); + } catch (error) { + console.error('Failed to update cart items:', error); + } + } + }; + + updateCartItems(); + }, [hasChanged]); + const handleCheckout = () => { router.push('/shop/checkout'); } @@ -84,23 +139,26 @@ const CartPage = () => { } const handleChange = async (e: React.ChangeEvent<HTMLInputElement>) => { - if (typeof auth !== 'object' || !cart) return; - setIsLoad(true) - const newSelected = e.target.checked; - setIsSelectedAll(newSelected); - - for (const item of cart.products) { - await upsertUserCart({ - userId: auth.id, - type: item.cart_type, - id: item.id, - qty: item.quantity, - selected: newSelected - }); + + + if (cart) { + const updatedCart = { + ...cart, + products: cart.products.map(item => ({ + ...item, + selected: !hasSelectedAll + })) + }; + + updateCartItem(updatedCart); + if(hasSelectedAll){ + setIsSelectedAll(false); + }else{ + setIsSelectedAll(true); + } } - await loadCart(auth.id); - setIsLoad(false) - } + }; + const handleDelete = async () => { if (typeof auth !== 'object' || !cart) return; @@ -136,7 +194,7 @@ const CartPage = () => { /> )} <p className='p-2 text-caption-2'> - {hasSelectedAll ? "Unchek all" : "Select all"} + {hasSelectedAll ? "Uncheck all" : "Select all"} </p> </div> <div className='delate all flex items-center object-center'> diff --git a/src-migrate/types/cart.ts b/src-migrate/types/cart.ts index 4e3c8b99..a3115103 100644 --- a/src-migrate/types/cart.ts +++ b/src-migrate/types/cart.ts @@ -32,6 +32,8 @@ export type CartItem = { id: number; name: string; stock: number; + is_in_bu: boolean; + on_hand_qty: number; weight: number; attributes: string[]; parent: { diff --git a/src-migrate/types/product.ts b/src-migrate/types/product.ts index 681cdc8e..31ea0ce1 100644 --- a/src-migrate/types/product.ts +++ b/src-migrate/types/product.ts @@ -12,6 +12,7 @@ export interface IProduct { variant_total: number; description: string; isSni: boolean; + is_in_bu: boolean; isTkdn: boolean; categories: { id: string; |
