summaryrefslogtreecommitdiff
path: root/src-migrate/modules/cart
diff options
context:
space:
mode:
authortrisusilo48 <tri.susilo@altama.co.id>2024-07-10 15:58:51 +0700
committertrisusilo48 <tri.susilo@altama.co.id>2024-07-10 15:58:51 +0700
commit2e3c726bc8217f3960cfecec44b81303b03de72b (patch)
tree1b85ced7f61f3e4c3f1f27b577b37aa161615065 /src-migrate/modules/cart
parent2b3bd9c0a454dbad69ce29cee877bfb1fca5dfa6 (diff)
parenta99bf6480eea556e53b85e6db45f3b8c2361e693 (diff)
Merge branch 'release' into development
# Conflicts: # src/pages/shop/product/variant/[slug].jsx
Diffstat (limited to 'src-migrate/modules/cart')
-rw-r--r--src-migrate/modules/cart/components/CartSummaryMobile.tsx111
-rw-r--r--src-migrate/modules/cart/components/Item.tsx156
-rw-r--r--src-migrate/modules/cart/components/ItemAction.tsx111
-rw-r--r--src-migrate/modules/cart/components/ItemPromo.tsx44
-rw-r--r--src-migrate/modules/cart/components/ItemSelect.tsx54
-rw-r--r--src-migrate/modules/cart/components/Summary.tsx75
-rw-r--r--src-migrate/modules/cart/stores/useCartStore.ts64
-rw-r--r--src-migrate/modules/cart/styles/item-action.module.css32
-rw-r--r--src-migrate/modules/cart/styles/item-promo.module.css31
-rw-r--r--src-migrate/modules/cart/styles/item.module.css60
-rw-r--r--src-migrate/modules/cart/styles/summary.module.css21
11 files changed, 759 insertions, 0 deletions
diff --git a/src-migrate/modules/cart/components/CartSummaryMobile.tsx b/src-migrate/modules/cart/components/CartSummaryMobile.tsx
new file mode 100644
index 00000000..d9f72e0e
--- /dev/null
+++ b/src-migrate/modules/cart/components/CartSummaryMobile.tsx
@@ -0,0 +1,111 @@
+import style from '../styles/summary.module.css';
+
+import React, { useState } from 'react';
+import formatCurrency from '~/libs/formatCurrency';
+import clsxm from '~/libs/clsxm';
+import { Button, Skeleton } from '@chakra-ui/react';
+import _ from 'lodash';
+import { ChevronDownIcon } from '@heroicons/react/24/outline';
+import BottomPopup from '@/core/components/elements/Popup/BottomPopup';
+import useDevice from '@/core/hooks/useDevice';
+
+type Props = {
+ total?: number;
+ discount?: number;
+ subtotal?: number;
+ tax?: number;
+ shipping?: number;
+ grandTotal?: number;
+ isLoaded: boolean;
+};
+
+const CartSummaryMobile = ({
+ total,
+ discount,
+ subtotal,
+ tax,
+ shipping,
+ grandTotal,
+ isLoaded = false,
+}: Props) => {
+ const [showPopup, setShowPopup] = useState(false);
+ return (
+ <>
+ <BottomPopup
+ className=' !h-[35%]'
+ title='Ringkasan Pensanan'
+ active={showPopup}
+ close={() => setShowPopup(false)}
+ >
+ <div className='mt-4'>
+ <div className='flex flex-col gap-y-3'>
+ <Skeleton isLoaded={isLoaded} className={style.line}>
+ <span className={style.label}>Total Belanja</span>
+ <span className={style.value}>
+ Rp {formatCurrency(subtotal || 0)}
+ </span>
+ </Skeleton>
+
+ <Skeleton isLoaded={isLoaded} className={style.line}>
+ <span className={style.label}>Total Diskon</span>
+ <span className={clsxm(style.value, style.discount)}>
+ - Rp {formatCurrency(discount || 0)}
+ </span>
+ </Skeleton>
+
+ <div className={style.divider} />
+
+ <Skeleton isLoaded={isLoaded} className={style.line}>
+ <span className={style.label}>Subtotal</span>
+ <span className={style.value}>
+ Rp {formatCurrency(total || 0)}
+ </span>
+ </Skeleton>
+
+ <Skeleton isLoaded={isLoaded} className={style.line}>
+ <span className={style.label}>Tax 11%</span>
+ <span className={style.value}>Rp {formatCurrency(tax || 0)}</span>
+ </Skeleton>
+
+ <Skeleton isLoaded={isLoaded} className={style.line}>
+ <span className={style.label}>Biaya Kirim</span>
+ <span className={style.value}>
+ Rp {formatCurrency(shipping || 0)}
+ </span>
+ </Skeleton>
+
+ <div className={style.divider} />
+ <Skeleton isLoaded={isLoaded} className={style.line}>
+ <span className={clsxm(style.label, style.grandTotal)}>
+ Grand Total
+ </span>
+ <span className={style.value}>
+ Rp {formatCurrency(grandTotal || 0)}
+ </span>
+ </Skeleton>
+ </div>
+ </div>
+ </BottomPopup>
+ <div className='flex flex-col gap-y-3'>
+ <Skeleton isLoaded={isLoaded} className={style.line}>
+ <span className={clsxm(style.label, style.grandTotal)}>
+ Grand Total
+ </span>
+ <button
+ onClick={() => setShowPopup(true)}
+ className='bg-gray-300 w-6 h-6 items-center justify-center cursor-pointer hover:bg-red-400 md:hidden '
+ >
+ <ChevronDownIcon className='h-6 w-6 text-white' />
+ </button>
+ </Skeleton>
+ <Skeleton isLoaded={isLoaded} className={style.line}>
+ <span className={style.value}>
+ Rp {formatCurrency(grandTotal || 0)}
+ </span>
+ </Skeleton>
+ </div>
+ </>
+ );
+};
+
+export default CartSummaryMobile;
diff --git a/src-migrate/modules/cart/components/Item.tsx b/src-migrate/modules/cart/components/Item.tsx
new file mode 100644
index 00000000..6ded6373
--- /dev/null
+++ b/src-migrate/modules/cart/components/Item.tsx
@@ -0,0 +1,156 @@
+import style from '../styles/item.module.css'
+
+import { Skeleton, SkeletonProps, Tooltip } from '@chakra-ui/react'
+import { InfoIcon } from 'lucide-react'
+import Image from 'next/image'
+import Link from 'next/link'
+
+import { PROMO_CATEGORY } from '~/constants/promotion'
+import formatCurrency from '~/libs/formatCurrency'
+import { createSlug } from '~/libs/slug'
+import { CartItem as CartItemProps } from '~/types/cart'
+
+import CartItemAction from './ItemAction'
+import CartItemPromo from './ItemPromo'
+import CartItemSelect from './ItemSelect'
+
+type Props = {
+ item: CartItemProps
+ editable?: boolean
+}
+
+const CartItem = ({ item, editable = true }: Props) => {
+ return (
+ <div className={style.wrapper}>
+ {item.cart_type === 'promotion' && (
+ <div className={style.header}>
+ {item.promotion_type?.value && (
+ <Tooltip label={PROMO_CATEGORY[item.promotion_type?.value].description} placement="top" bgColor='red.600' p={2} rounded={6}>
+ <div className={style.badgeType}>
+ Paket {PROMO_CATEGORY[item.promotion_type?.value].alias}
+ <InfoIcon size={14} />
+ </div>
+ </Tooltip>
+ )}
+ <div className='w-2' />
+ <div>
+ Selamat! Pembelian anda lebih hemat {' '}
+ <span className={style.savingAmt}>
+ Rp{formatCurrency((item.package_price || 0) * item.quantity - item.subtotal)}
+ </span>
+ </div>
+ </div>
+ )}
+
+ <div className={style.mainProdWrapper}>
+ {editable && <CartItemSelect item={item} />}
+ <div className='w-4' />
+
+ <CartItem.Image item={item} />
+
+ <div className={style.details}>
+ <CartItem.Name item={item} />
+
+ <div className='mt-2 flex justify-between w-full'>
+ <div className='flex flex-col gap-y-1'>
+ {item.cart_type === 'promotion' && (
+ <div className={style.discPriceSection}>
+ <span className={style.priceBefore}>
+ Rp {formatCurrency((item.package_price || 0))}
+ </span>
+ <span className={style.price}>
+ Rp {formatCurrency(item.price.price)}
+ </span>
+ </div>
+ )}
+
+ {item.cart_type === 'product' && (
+ <div className={style.discPriceSection}>
+ {item.price.discount_percentage > 0 && (
+ <span className={style.priceBefore}>
+ Rp {formatCurrency((item.price.price || 0))}
+ </span>
+ )}
+
+ <div className={style.price}>
+ {item.price.price_discount > 0 && `Rp ${formatCurrency(item.price.price_discount)}`}
+ {item.price.price_discount === 0 && '-'}
+ </div>
+ </div>
+ )}
+
+ <div>{item.cart_type === 'product' && item.code}</div>
+ <div>{Math.round(item.weight * 10) / 10} Kg</div>
+ </div>
+
+ {editable && <CartItemAction item={item} />}
+ {!editable && <div className={style.quantity}>{item.quantity}</div>}
+ </div>
+ </div>
+
+ </div>
+
+ <div className="flex flex-col">
+ {item.products?.map((product) => <CartItemPromo key={product.id} product={product} />)}
+ {item.free_products?.map((product) => <CartItemPromo key={product.id} product={product} />)}
+ </div>
+ </div>
+ )
+}
+
+CartItem.Image = function CartItemImage({ item }: { item: CartItemProps }) {
+ const image = item?.image || item?.parent?.image
+
+ return (
+ <>
+ {item.cart_type === 'promotion' && (
+ <div className={style.image}>
+ {image && <Image src={image} alt={item.name} width={128} height={128} />}
+ {!image && <div className={style.noImage}>No Image</div>}
+ </div>
+ )}
+
+ {item.cart_type === 'product' && (
+ <Link
+ href={createSlug('/shop/product/', item.parent.name, item.parent.id.toString())}
+ className={style.image}
+ >
+ {image && <Image src={image} alt={item.name} width={128} height={128} />}
+ {!image && <div className={style.noImage}>No Image</div>}
+ </Link>
+ )}
+ </>
+ )
+}
+
+CartItem.Name = function CartItemName({ item }: { item: CartItemProps }) {
+ return (
+ <>
+ {item.cart_type === 'promotion' && (
+ <div className={style.name}>{item.name}</div>
+ )}
+
+ {item.cart_type === 'product' && (
+ <Link
+ href={createSlug('/shop/product/', item.parent.name, item.parent.id.toString())}
+ className={style.name}
+ >
+ {item.name}
+ </Link>
+ )}
+ </>
+ )
+}
+
+CartItem.Skeleton = function CartItemSkeleton(props: SkeletonProps & { count: number }) {
+ return Array.from({ length: props.count }).map((_, index) => (
+ <Skeleton key={index}
+ height='100px'
+ width='100%'
+ rounded='md'
+ {...props}
+ />
+ ))
+}
+
+export default CartItem \ No newline at end of file
diff --git a/src-migrate/modules/cart/components/ItemAction.tsx b/src-migrate/modules/cart/components/ItemAction.tsx
new file mode 100644
index 00000000..e73d507b
--- /dev/null
+++ b/src-migrate/modules/cart/components/ItemAction.tsx
@@ -0,0 +1,111 @@
+import style from '../styles/item-action.module.css'
+
+import React, { useEffect, useState } from '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 { useDebounce } from 'usehooks-ts'
+import { useCartStore } from '../stores/useCartStore'
+
+
+type Props = {
+ item: CartItem
+}
+
+const CartItemAction = ({ item }: Props) => {
+ const auth = getAuth()
+
+ const [isLoadDelete, setIsLoadDelete] = useState<boolean>(false)
+ const [isLoadQuantity, setIsLoadQuantity] = useState<boolean>(false)
+
+ const [quantity, setQuantity] = useState<number>(item.quantity)
+
+ const { loadCart } = useCartStore()
+
+ 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)
+ }
+
+ 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])
+
+ 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()
+ //eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [debounceQty])
+
+ return (
+ <div className={style.actionSection}>
+ <button className={style.deleteButton} onClick={handleDelete} disabled={isLoadDelete}>
+ {isLoadDelete && <Spinner size='xs' />}
+ {!isLoadDelete && <Trash2Icon size={16} />}
+ </button>
+
+ <div className={style.quantitySection}>
+ {isLoadQuantity && (
+ <div className={style.quantityLoading}>
+ <Spinner size='sm' />
+ </div>
+ )}
+
+ <button
+ className={style.quantityControl}
+ onClick={decreaseQty}
+ disabled={quantity <= 1}
+ >
+ <MinusIcon size={16} />
+ </button>
+
+ <input
+ type='number'
+ className={style.quantity.toString()}
+ onChange={(e) => setQuantity(parseInt(e.target.value))}
+ value={quantity}
+ />
+
+ <Tooltip label={limitQty > 0 ? `Max. ${limitQty}` : ''}>
+ <button
+ className={style.quantityControl}
+ onClick={increaseQty}
+ disabled={limitQty > 0 && quantity >= limitQty}
+ >
+ <PlusIcon size={16} />
+ </button>
+ </Tooltip>
+ </div>
+ </div>
+ )
+}
+
+export default CartItemAction \ No newline at end of file
diff --git a/src-migrate/modules/cart/components/ItemPromo.tsx b/src-migrate/modules/cart/components/ItemPromo.tsx
new file mode 100644
index 00000000..878e17ac
--- /dev/null
+++ b/src-migrate/modules/cart/components/ItemPromo.tsx
@@ -0,0 +1,44 @@
+import style from '../styles/item-promo.module.css'
+
+import Image from 'next/image'
+import Link from 'next/link'
+import { createSlug } from '~/libs/slug'
+
+import { CartProduct } from '~/types/cart'
+
+type Props = {
+ product: CartProduct
+}
+
+const CartItemPromo = ({ product }: Props) => {
+ return (
+ <div key={product.id} className={style.wrapper}>
+ <Link href={createSlug('/shop/product/', product.parent.name, product.parent.id.toString())} className={style.imageWrapper}>
+ {product?.image && <Image src={product.image} alt={product.name} width={128} height={128} className={style.image} />}
+ </Link>
+
+ <div className={style.details}>
+ <Link href={createSlug('/shop/product/', product.parent.name, product.parent.id.toString())} className={style.name}>
+ {product.display_name}
+ </Link>
+
+ <div className='flex w-full'>
+ <div className="flex flex-col">
+ <div className={style.code}>{product.code}</div>
+ <div>
+ <span className={style.weightLabel}>Berat Barang: </span>
+ <span>{product.package_weight} Kg</span>
+ </div>
+ </div>
+
+ <div className={style.quantity}>
+ {product.qty}
+ </div>
+ </div>
+ </div>
+
+ </div>
+ )
+}
+
+export default CartItemPromo \ No newline at end of file
diff --git a/src-migrate/modules/cart/components/ItemSelect.tsx b/src-migrate/modules/cart/components/ItemSelect.tsx
new file mode 100644
index 00000000..b904a1de
--- /dev/null
+++ b/src-migrate/modules/cart/components/ItemSelect.tsx
@@ -0,0 +1,54 @@
+import { Checkbox, Spinner } from '@chakra-ui/react'
+import React, { useState } from 'react'
+
+import { getAuth } from '~/libs/auth'
+import { CartItem } from '~/types/cart'
+import { upsertUserCart } from '~/services/cart'
+
+import { useCartStore } from '../stores/useCartStore'
+
+type Props = {
+ item: CartItem
+}
+
+const CartItemSelect = ({ item }: Props) => {
+ const auth = getAuth()
+ const { loadCart } = 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)
+ }
+
+ return (
+ <div className='w-6 my-auto'>
+ {isLoad && (
+ <Spinner className='my-auto' size='sm' />
+ )}
+
+ {!isLoad && (
+ <Checkbox
+ borderColor='gray.600'
+ colorScheme='red'
+ size='lg'
+ isChecked={item.selected}
+ onChange={handleChange}
+ />
+ )}
+ </div>
+ )
+}
+
+export default CartItemSelect \ No newline at end of file
diff --git a/src-migrate/modules/cart/components/Summary.tsx b/src-migrate/modules/cart/components/Summary.tsx
new file mode 100644
index 00000000..2e55c8df
--- /dev/null
+++ b/src-migrate/modules/cart/components/Summary.tsx
@@ -0,0 +1,75 @@
+import style from '../styles/summary.module.css'
+
+import React from 'react'
+import formatCurrency from '~/libs/formatCurrency'
+import clsxm from '~/libs/clsxm'
+import { Skeleton } from '@chakra-ui/react'
+import _ from 'lodash'
+
+type Props = {
+ total?: number
+ discount?: number
+ subtotal?: number
+ tax?: number
+ shipping?: number
+ grandTotal?: number
+ isLoaded: boolean
+}
+
+const CartSummary = ({
+ total,
+ discount,
+ subtotal,
+ tax,
+ shipping,
+ grandTotal,
+ isLoaded = false,
+}: Props) => {
+ return (
+ <>
+ <div className='text-h-sm font-medium'>Ringkasan Pesanan</div>
+
+ <div className="h-6" />
+
+ <div className='flex flex-col gap-y-3'>
+ <Skeleton isLoaded={isLoaded} className={style.line}>
+ <span className={style.label}>Total Belanja</span>
+ <span className={style.value}>Rp {formatCurrency(subtotal || 0)}</span>
+ </Skeleton>
+
+ <Skeleton isLoaded={isLoaded} className={style.line}>
+ <span className={style.label}>Total Diskon</span>
+ <span className={clsxm(style.value, style.discount)}>- Rp {formatCurrency(discount || 0)}</span>
+ </Skeleton>
+
+ <div className={style.divider} />
+
+ <Skeleton isLoaded={isLoaded} className={style.line}>
+ <span className={style.label}>Subtotal</span>
+ <span className={style.value}>Rp {formatCurrency(total || 0)}</span>
+ </Skeleton>
+
+ <Skeleton isLoaded={isLoaded} className={style.line}>
+ <span className={style.label}>Tax 11%</span>
+ <span className={style.value}>Rp {formatCurrency(tax || 0)}</span>
+ </Skeleton>
+
+ <Skeleton isLoaded={isLoaded} className={style.line}>
+ <span className={style.label}>Biaya Kirim</span>
+ <span className={style.value}>Rp {formatCurrency(shipping || 0)}</span>
+ </Skeleton>
+
+ <div className={style.divider} />
+
+ <Skeleton isLoaded={isLoaded} className={style.line}>
+ <span className={clsxm(style.label, style.grandTotal)}>
+ Grand Total
+ </span>
+ <span className={style.value}>Rp {formatCurrency(grandTotal || 0)}</span>
+ </Skeleton>
+ </div>
+ </>
+ )
+}
+
+export default CartSummary \ No newline at end of file
diff --git a/src-migrate/modules/cart/stores/useCartStore.ts b/src-migrate/modules/cart/stores/useCartStore.ts
new file mode 100644
index 00000000..3d9a0aed
--- /dev/null
+++ b/src-migrate/modules/cart/stores/useCartStore.ts
@@ -0,0 +1,64 @@
+import { create } from 'zustand';
+import { CartProps } from '~/types/cart';
+import { getUserCart } from '~/services/cart';
+
+type State = {
+ cart: CartProps | null;
+ isLoadCart: boolean;
+ summary: {
+ subtotal: number;
+ discount: number;
+ total: number;
+ tax: number;
+ grandTotal: number;
+ };
+};
+
+type Action = {
+ loadCart: (userId: number) => Promise<void>;
+};
+
+export const useCartStore = create<State & Action>((set, get) => ({
+ cart: null,
+ isLoadCart: false,
+ summary: {
+ subtotal: 0,
+ discount: 0,
+ total: 0,
+ tax: 0,
+ grandTotal: 0,
+ },
+ loadCart: async (userId) => {
+ if (get().isLoadCart === true) return;
+
+ set({ isLoadCart: true });
+ const cart: CartProps = (await getUserCart(userId)) as CartProps;
+ set({ cart });
+ set({ isLoadCart: false });
+
+ const summary = computeSummary(cart);
+ set({ summary });
+ },
+}));
+
+const computeSummary = (cart: CartProps) => {
+ let subtotal = 0;
+ let discount = 0;
+ for (const item of cart.products) {
+ if (!item.selected) continue;
+
+ let price = 0;
+ if (item.cart_type === 'promotion')
+ price = (item?.package_price || 0) * item.quantity;
+ else if (item.cart_type === 'product')
+ price = item.price.price * item.quantity;
+
+ subtotal += price;
+ discount += price - item.price.price_discount * item.quantity;
+ }
+ let total = subtotal - discount;
+ let tax = Math.round(total * 0.11);
+ let grandTotal = total + tax;
+
+ return { subtotal, discount, total, tax, grandTotal };
+};
diff --git a/src-migrate/modules/cart/styles/item-action.module.css b/src-migrate/modules/cart/styles/item-action.module.css
new file mode 100644
index 00000000..e4db7fa5
--- /dev/null
+++ b/src-migrate/modules/cart/styles/item-action.module.css
@@ -0,0 +1,32 @@
+.actionSection {
+ @apply flex ml-auto h-fit my-auto;
+}
+
+.deleteButton {
+ @apply bg-red-100 disabled:bg-gray-100
+ text-red-700 disabled:text-gray-500
+ hover:bg-red-200
+ disabled:cursor-not-allowed
+ transition-all
+ p-2.5 rounded;
+}
+
+.quantitySection {
+ @apply relative flex border border-gray-300 rounded ml-4 items-center text-red-700;
+}
+
+.quantityLoading {
+ @apply absolute flex items-center justify-center text-white rounded w-full h-full bg-gray-900/50 z-10;
+}
+
+.quantityControl {
+ @apply h-full w-8 flex items-center justify-center hover:bg-gray-100
+ disabled:text-gray-500
+ disabled:bg-transparent
+ disabled:cursor-not-allowed
+ transition;
+}
+
+.quantity {
+ @apply text-gray-900 font-medium max-w-[28px] outline-none text-center;
+}
diff --git a/src-migrate/modules/cart/styles/item-promo.module.css b/src-migrate/modules/cart/styles/item-promo.module.css
new file mode 100644
index 00000000..15bf8146
--- /dev/null
+++ b/src-migrate/modules/cart/styles/item-promo.module.css
@@ -0,0 +1,31 @@
+.wrapper {
+ @apply md:ml-16 ml-8 mt-4 flex;
+}
+
+.imageWrapper {
+ @apply md:h-24 md:w-24 md:min-w-[96px]
+ h-20 w-20 min-w-[80px]
+ border border-gray-300 rounded
+ p-2.5;
+}
+
+.image {
+ @apply w-full h-full object-contain;
+}
+
+.details {
+ @apply ml-4 w-full flex flex-col gap-y-1;
+}
+
+.name {
+ @apply font-medium;
+}
+
+.code,
+.weightLabel {
+ @apply text-gray-600;
+}
+
+.quantity {
+ @apply w-12 min-w-[42px] py-2.5 bg-gray-100 border border-gray-300 h-fit my-auto rounded-md ml-auto font-medium text-center;
+}
diff --git a/src-migrate/modules/cart/styles/item.module.css b/src-migrate/modules/cart/styles/item.module.css
new file mode 100644
index 00000000..dfbbf5e8
--- /dev/null
+++ b/src-migrate/modules/cart/styles/item.module.css
@@ -0,0 +1,60 @@
+.wrapper {
+ @apply border-b border-gray-300 pb-8;
+}
+
+.header {
+ @apply mb-4 flex items-center text-caption-1 leading-6;
+}
+
+.badgeType {
+ @apply min-w-fit p-2 flex items-center gap-x-1.5 rounded-md border border-danger-500 text-danger-500;
+}
+
+.mainProdWrapper {
+ @apply flex;
+}
+
+.image {
+ @apply md:h-32 md:w-32 md:min-w-[128px]
+ w-24 h-24 min-w-[96px] rounded flex p-2 border border-gray-300;
+}
+
+.noImage {
+ @apply m-auto font-semibold text-gray-400;
+}
+
+.details {
+ @apply ml-4 flex flex-col gap-y-1 w-full;
+}
+
+.name {
+ @apply font-medium;
+}
+
+.spacing2 {
+ @apply h-2;
+}
+
+.discPriceSection {
+ @apply flex flex-col md:flex-row gap-x-2.5;
+}
+
+.priceBefore {
+ @apply line-through text-gray-500;
+}
+
+.price {
+ @apply text-red-600 font-medium;
+}
+
+.savingAmt {
+ @apply text-success-600;
+}
+
+.weightLabel {
+ @apply text-gray-500;
+}
+
+.quantity {
+ @apply py-2.5 bg-red-100 border border-red-300 text-red-800 h-fit my-auto rounded-md ml-auto font-medium w-12 text-center;
+}
diff --git a/src-migrate/modules/cart/styles/summary.module.css b/src-migrate/modules/cart/styles/summary.module.css
new file mode 100644
index 00000000..48ccec28
--- /dev/null
+++ b/src-migrate/modules/cart/styles/summary.module.css
@@ -0,0 +1,21 @@
+.line {
+ @apply flex justify-between;
+}
+
+.label,
+.value {
+ @apply text-gray-700;
+}
+
+.value,
+.grandTotal {
+ @apply font-medium;
+}
+
+.discount {
+ @apply text-red-700;
+}
+
+.divider {
+ @apply my-0.5 h-0.5 bg-gray-200;
+}