add product to cart, update product in cart, remove product form cart

This commit is contained in:
DatNguyen
2021-10-06 16:59:37 +07:00
parent 7ade05a5af
commit a8aa755b79
17 changed files with 195 additions and 48 deletions

View File

@@ -17,6 +17,7 @@ export type LineItem = {
quantity: number quantity: number
discounts: Discount[] discounts: Discount[]
// A human-friendly unique string automatically generated from the products name // A human-friendly unique string automatically generated from the products name
slug: string
path: string path: string
variant: ProductVariant variant: ProductVariant
options?: SelectedOption[] options?: SelectedOption[]

View File

@@ -60,6 +60,7 @@ export type ProductCard = {
// TODO: collection // TODO: collection
category?: string, category?: string,
isNotSell?: boolean isNotSell?: boolean
productVariantId?:string
} }
export type SearchProductsBody = { export type SearchProductsBody = {

View File

@@ -3038,7 +3038,7 @@ export type SearchResultFragment = { __typename?: 'SearchResult' } & Pick<
SearchResult, SearchResult,
'productId' | 'sku' | 'productName' | 'description' | 'slug' | 'sku' | 'currencyCode' 'productId' | 'sku' | 'productName' | 'description' | 'slug' | 'sku' | 'currencyCode'
| 'productAsset' | 'price' | 'priceWithTax' | 'currencyCode' | 'productAsset' | 'price' | 'priceWithTax' | 'currencyCode'
| 'collectionIds' | 'collectionIds' | 'productVariantId'
> & { > & {
productAsset?: Maybe< productAsset?: Maybe<
{ __typename?: 'SearchResultAsset' } & Pick< { __typename?: 'SearchResultAsset' } & Pick<

View File

@@ -7,6 +7,7 @@ export const searchResultFragment = /* GraphQL */ `
slug slug
sku sku
currencyCode currencyCode
productVariantId
productAsset { productAsset {
id id
preview preview

View File

@@ -11,6 +11,7 @@ export function normalizeSearchResult(item: SearchResultFragment): ProductCard {
imageSrc: item.productAsset?.preview ? item.productAsset?.preview + '?w=800&mode=crop' : '', imageSrc: item.productAsset?.preview ? item.productAsset?.preview + '?w=800&mode=crop' : '',
price: (item.priceWithTax as any).min / 100, price: (item.priceWithTax as any).min / 100,
currencyCode: item.currencyCode, currencyCode: item.currencyCode,
productVariantId: item.productVariantId?item.productVariantId:"",
// TODO: // TODO:
// oldPrice: item.price // oldPrice: item.price
@@ -35,7 +36,7 @@ export function normalizeCart(order: CartFragment): Cart {
id: l.id, id: l.id,
name: l.productVariant.name, name: l.productVariant.name,
quantity: l.quantity, quantity: l.quantity,
url: l.productVariant.product.slug, slug: l.productVariant.product.slug,
variantId: l.productVariant.id, variantId: l.productVariant.id,
productId: l.productVariant.productId, productId: l.productVariant.productId,
images: [{ url: l.featuredAsset?.preview + '?preset=thumb' || '' }], images: [{ url: l.featuredAsset?.preview + '?preset=thumb' || '' }],

View File

@@ -17,6 +17,7 @@ interface Props {
featuredProducts: ProductCard[], featuredProducts: ProductCard[],
} }
export default function Home({ freshProducts, featuredProducts, veggie }: Props) { export default function Home({ freshProducts, featuredProducts, veggie }: Props) {
// console.log("veggie",veggie)
return ( return (
<> <>
<HomeBanner /> <HomeBanner />

View File

@@ -1,3 +1,4 @@
import { normalizeCart } from '@framework/utils/normalize';
import React from 'react'; import React from 'react';
import { useCartDrawer } from 'src/components/contexts'; import { useCartDrawer } from 'src/components/contexts';
import useGetActiveOrder from 'src/components/hooks/cart/useGetActiveOrder'; import useGetActiveOrder from 'src/components/hooks/cart/useGetActiveOrder';
@@ -18,12 +19,12 @@ const CartDrawer = ({ }: Props) => {
const {order} = useGetActiveOrder() const {order} = useGetActiveOrder()
return ( return (
<DrawerCommon <DrawerCommon
title={`Your cart (${order?.totalQuantity})`} title={`Your cart (${order?.lineItems.length})`}
visible={cartVisible} visible={cartVisible}
onClose={closeCartDrawer}> onClose={closeCartDrawer}>
<div className={s.cartDrawer}> <div className={s.cartDrawer}>
<div className={s.body}> <div className={s.body}>
<ProductsInCart data={PRODUCT_CART_DATA_TEST} /> <ProductsInCart data={order?.lineItems||[]} currency={order?.currency||{code:"USA"}}/>
<CartRecommendation /> <CartRecommendation />
</div> </div>
<div> <div>

View File

@@ -1,25 +1,64 @@
import React from 'react'; import React, { useCallback, useState } from 'react'
import Link from 'next/link' import Link from 'next/link'
import { QuanittyInput } from 'src/components/common'; import { ModalConfirm, QuanittyInput } from 'src/components/common'
import { IconDelete } from 'src/components/icons'; import { IconDelete } from 'src/components/icons'
import { ROUTE } from 'src/utils/constanst.utils'; import { ROUTE } from 'src/utils/constanst.utils'
import { ProductProps } from 'src/utils/types.utils'; import ImgWithLink from '../../../ImgWithLink/ImgWithLink'
import ImgWithLink from '../../../ImgWithLink/ImgWithLink'; import LabelCommon from '../../../LabelCommon/LabelCommon'
import LabelCommon from '../../../LabelCommon/LabelCommon'; import s from './ProductCartItem.module.scss'
import s from './ProductCartItem.module.scss'; import { LineItem } from '@commerce/types/cart'
import { useUpdateProductInCart } from 'src/components/hooks/cart'
import { debounce } from 'lodash'
import useRemoveProductInCart from 'src/components/hooks/cart/useRemoveProductInCart'
export interface ProductCartItempProps extends ProductProps { export interface ProductCartItempProps extends LineItem {
quantity: number, currency: { code: string }
} }
const ProductCartItem = ({ name, slug, weight, price, oldPrice, discount, imageSrc, quantity }: ProductCartItempProps) => { const ProductCartItem = ({
slug,
discounts,
quantity,
variant,
name,
currency,
id
}: ProductCartItempProps) => {
const [visible, setVisible] = useState(false)
const {updateProduct} = useUpdateProductInCart()
const {removeProduct, loading} = useRemoveProductInCart()
const handleQuantityChangeCallback = (isSuccess:boolean,mess?:string) => {
if(!isSuccess){
console.log(mess)
}
}
const handleRemoveCallback = (isSuccess:boolean,mess?:string) => {
if(!isSuccess){
console.log(mess)
}else{
setVisible(false)
}
}
const handleQuantityChange = (value:number) => {
updateProduct({orderLineId:id,quantity:value},handleQuantityChangeCallback)
}
const debounceFn = useCallback(debounce(handleQuantityChange, 500), []);
const handleCancel = () => {
setVisible(false)
}
const handleOpen = () => {
setVisible(true)
}
const handleConfirm = () => {
removeProduct({orderLineId:id},handleRemoveCallback)
}
return ( return (
<div className={s.productCartItem}> <div className={s.productCartItem}>
<div className={s.info}> <div className={s.info}>
<Link href={`${ROUTE.PRODUCT_DETAIL}/${slug}`}> <Link href={`${ROUTE.PRODUCT_DETAIL}/${slug}`}>
<a href=""> <a href="">
<div className={s.imgWrap}> <div className={s.imgWrap}>
<ImgWithLink src={imageSrc} alt={name} /> <ImgWithLink src={variant?.image?.url ?? ''} alt={name} />
</div> </div>
</a> </a>
</Link> </Link>
@@ -27,30 +66,32 @@ const ProductCartItem = ({ name, slug, weight, price, oldPrice, discount, imageS
<Link href={`${ROUTE.PRODUCT_DETAIL}/${slug}`}> <Link href={`${ROUTE.PRODUCT_DETAIL}/${slug}`}>
<a> <a>
<div className={s.name}> <div className={s.name}>
{name} {weight ? `(${weight})` : ''} {name} {variant?.weight ? `(${variant.weight})` : ''}
</div> </div>
</a> </a>
</Link> </Link>
<div className={s.price}> <div className={s.price}>
{ {discounts.length > 0 && (
oldPrice && <div className={s.old}>
<div className={s.old}> {/* <span className={s.number}>{oldPrice}</span> */}
<span className={s.number}>{oldPrice}</span> <LabelCommon type="discount">{discounts[0]}</LabelCommon>
<LabelCommon type='discount'>{discount}</LabelCommon> </div>
</div> )}
} <div className={s.current}>{variant?.price} {currency?.code}</div>
<div className={s.current}>{price}</div>
</div> </div>
</div> </div>
</div> </div>
<div className={s.actions}> <div className={s.actions}>
<div className={s.iconDelete}> <div className={s.iconDelete} onClick={handleOpen}>
<IconDelete /> <IconDelete />
</div> </div>
<QuanittyInput size='small' initValue={quantity} /> <QuanittyInput size="small" initValue={quantity} onChange={debounceFn}/>
</div> </div>
<ModalConfirm visible={visible} onClose={handleCancel} onCancel={handleCancel} onOk={handleConfirm} loading={loading}>
Are you sure want to remove {name} form your cart
</ModalConfirm>
</div> </div>
) )
} }
export default ProductCartItem; export default ProductCartItem

View File

@@ -1,25 +1,21 @@
import { LineItem } from '@commerce/types/cart';
import React from 'react'; import React from 'react';
import ProductCartItem, { ProductCartItempProps } from '../ProductCartItem/ProductCartItem'; import ProductCartItem, { ProductCartItempProps } from '../ProductCartItem/ProductCartItem';
import s from './ProductsInCart.module.scss'; import s from './ProductsInCart.module.scss';
interface Props { interface Props {
data: ProductCartItempProps[] data: LineItem[]
currency: { code: string }
} }
const ProductsInCart = ({ data }: Props) => { const ProductsInCart = ({ data, currency }: Props) => {
return ( return (
<ul className={s.productsInCart}> <ul className={s.productsInCart}>
{ {
data.map(item => <li key={item.name}> data.map(item => <li key={item.name}>
<ProductCartItem <ProductCartItem
name={item.name} currency = {currency}
slug={item.slug} {...item}
weight={item.weight}
price={item.price}
oldPrice={item.oldPrice}
discount={item.discount}
imageSrc={item.imageSrc}
quantity={item.quantity}
/> />
</li>) </li>)
} }

View File

@@ -5,6 +5,7 @@ import s from './ModalConfirm.module.scss'
interface ModalConfirmProps extends ModalCommonProps { interface ModalConfirmProps extends ModalCommonProps {
okText?: String okText?: String
cancelText?: String cancelText?: String
loading?:boolean
onOk?: () => void onOk?: () => void
onCancel?: () => void onCancel?: () => void
} }
@@ -16,6 +17,7 @@ const ModalConfirm = ({
onCancel, onCancel,
children, children,
title = 'Confirm', title = 'Confirm',
loading,
...props ...props
}: ModalConfirmProps) => { }: ModalConfirmProps) => {
return ( return (
@@ -25,7 +27,7 @@ const ModalConfirm = ({
<div className="mr-4"> <div className="mr-4">
<ButtonCommon onClick={onCancel} type="light"> {cancelText}</ButtonCommon> <ButtonCommon onClick={onCancel} type="light"> {cancelText}</ButtonCommon>
</div> </div>
<ButtonCommon onClick={onOk}>{okText}</ButtonCommon> <ButtonCommon onClick={onOk} loading={loading}>{okText}</ButtonCommon>
</div> </div>
</ModalCommon> </ModalCommon>
) )

View File

@@ -10,13 +10,14 @@ import ItemWishList from '../ItemWishList/ItemWishList'
import LabelCommon from '../LabelCommon/LabelCommon' import LabelCommon from '../LabelCommon/LabelCommon'
import s from './ProductCard.module.scss' import s from './ProductCard.module.scss'
import ProductNotSell from './ProductNotSell/ProductNotSell' import ProductNotSell from './ProductNotSell/ProductNotSell'
import {useAddProductToCart} from "../../hooks/cart"
export interface ProductCardProps extends ProductCard { export interface ProductCardProps extends ProductCard {
buttonText?: string buttonText?: string
isSingleButton?: boolean, isSingleButton?: boolean,
} }
const ProductCardComponent = ({ const ProductCardComponent = ({
id,
category, category,
name, name,
slug, slug,
@@ -27,7 +28,18 @@ const ProductCardComponent = ({
imageSrc, imageSrc,
isNotSell, isNotSell,
isSingleButton, isSingleButton,
productVariantId,
}: ProductCardProps) => { }: ProductCardProps) => {
const {addProduct,loading,error} = useAddProductToCart()
const handleAddToCart = () => {
if(productVariantId){
addProduct({variantId:productVariantId,quantity:1},handleAddToCartCallback)
}
}
const handleAddToCartCallback = () => {
}
if (isNotSell) { if (isNotSell) {
return <div className={`${s.productCardWarpper} ${s.notSell}`}> return <div className={`${s.productCardWarpper} ${s.notSell}`}>
<ProductNotSell name={name} imageSrc={imageSrc} /> <ProductNotSell name={name} imageSrc={imageSrc} />
@@ -71,12 +83,12 @@ const ProductCardComponent = ({
{ {
isSingleButton ? isSingleButton ?
<div className={s.cardButton}> <div className={s.cardButton}>
<ButtonCommon type="light" icon={<IconBuy />} size='small'>Add to cart</ButtonCommon> <ButtonCommon type="light" icon={<IconBuy />} size='small' >Add to cart</ButtonCommon>
</div> </div>
: :
<> <>
<div className={s.cardIcon}> <div className={s.cardIcon} >
<ButtonIconBuy/> <ButtonIconBuy onClick={handleAddToCart} loading={loading}/>
</div> </div>
<div className={s.cardButton}> <div className={s.cardButton}>
<ButtonCommon type="light" size='small'>{buttonText}</ButtonCommon> <ButtonCommon type="light" size='small'>{buttonText}</ButtonCommon>

View File

@@ -26,10 +26,6 @@ const QuanittyInput = ({
}: QuanittyInputProps) => { }: QuanittyInputProps) => {
const [value, setValue] = useState<number>(0) const [value, setValue] = useState<number>(0)
useEffect(() => {
onChange && onChange(value)
}, [value])
useEffect(() => { useEffect(() => {
initValue && setValue(initValue) initValue && setValue(initValue)
}, [initValue]) }, [initValue])
@@ -37,16 +33,20 @@ const QuanittyInput = ({
const onPlusClick = () => { const onPlusClick = () => {
if (max && value + step > max) { if (max && value + step > max) {
setValue(max) setValue(max)
onChange && onChange(max)
} else { } else {
setValue(value + step) setValue(value + step)
onChange && onChange(value + step)
} }
} }
const onMinusClick = () => { const onMinusClick = () => {
if (min && value - step < min) { if (min && value - step < min) {
setValue(min) setValue(min)
onChange && onChange(min)
} else { } else {
setValue(value - step) setValue(value - step)
onChange && onChange(value - step)
} }
} }
@@ -54,10 +54,13 @@ const QuanittyInput = ({
let value = Number(e.target.value) || 0 let value = Number(e.target.value) || 0
if (min && value < min) { if (min && value < min) {
setValue(min) setValue(min)
onChange && onChange(min)
} else if (max && value > max) { } else if (max && value > max) {
setValue(max) setValue(max)
onChange && onChange(max)
} else { } else {
setValue(value) setValue(value)
onChange && onChange(value)
} }
} }

View File

@@ -1,2 +1,3 @@
export { default as useAddProductToCart } from './useAddProductToCart' export { default as useAddProductToCart } from './useAddProductToCart'
export { default as useUpdateProductInCart } from './useUpdateProductInCart'
export { default as useGetActiveOrder } from './useGetActiveOrder' export { default as useGetActiveOrder } from './useGetActiveOrder'

View File

@@ -1,5 +1,7 @@
import { Cart } from '@commerce/types/cart'
import { ActiveOrderQuery } from '@framework/schema' import { ActiveOrderQuery } from '@framework/schema'
import { cartFragment } from '@framework/utils/fragments/cart-fragment' import { cartFragment } from '@framework/utils/fragments/cart-fragment'
import { normalizeCart } from '@framework/utils/normalize'
import { gql } from 'graphql-request' import { gql } from 'graphql-request'
import gglFetcher from 'src/utils/gglFetcher' import gglFetcher from 'src/utils/gglFetcher'
import useSWR from 'swr' import useSWR from 'swr'
@@ -14,7 +16,8 @@ const query = gql`
const useGetActiveOrder = () => { const useGetActiveOrder = () => {
const { data, ...rest } = useSWR<ActiveOrderQuery>([query], gglFetcher) const { data, ...rest } = useSWR<ActiveOrderQuery>([query], gglFetcher)
return { order: data?.activeOrder, ...rest } return { order: data?.activeOrder ? normalizeCart(data!.activeOrder) : null, ...rest }
} }
export default useGetActiveOrder export default useGetActiveOrder

View File

@@ -0,0 +1,41 @@
import { useState } from 'react'
import { CommonError } from 'src/domains/interfaces/CommonError'
import rawFetcher from 'src/utils/rawFetcher'
import { AdjustOrderLineMutationVariables,AdjustOrderLineMutation, RemoveOrderLineMutation, RemoveOrderLineMutationVariables } from '@framework/schema'
import { errorMapping } from 'src/utils/errrorMapping'
import { useGetActiveOrder } from '.'
import { adjustOrderLineMutation } from '@framework/utils/mutations/adjust-order-line-mutation'
import { removeOrderLineMutation } from '@framework/utils/mutations/remove-order-line-mutation'
const useRemoveProductInCart = () => {
const [loading, setLoading] = useState(false)
const [error, setError] = useState<CommonError | null>(null)
const { mutate } = useGetActiveOrder()
const removeProduct = (options:RemoveOrderLineMutationVariables,
fCallBack: (isSuccess: boolean, message?: string) => void
) => {
setError(null)
setLoading(true)
rawFetcher<RemoveOrderLineMutation>({
query: removeOrderLineMutation ,
variables: options,
})
.then(({ data }) => {
if (data.removeOrderLine.__typename !== "Order") {
throw CommonError.create(errorMapping(data.removeOrderLine.message), data.removeOrderLine.errorCode)
}
mutate()
fCallBack(true)
})
.catch((error) => {
setError(error)
fCallBack(false, error.message)
})
.finally(() => setLoading(false))
}
return { loading, removeProduct, error }
}
export default useRemoveProductInCart

View File

@@ -0,0 +1,40 @@
import { useState } from 'react'
import { CommonError } from 'src/domains/interfaces/CommonError'
import rawFetcher from 'src/utils/rawFetcher'
import { AdjustOrderLineMutationVariables,AdjustOrderLineMutation } from '@framework/schema'
import { errorMapping } from 'src/utils/errrorMapping'
import { useGetActiveOrder } from '.'
import { adjustOrderLineMutation } from '@framework/utils/mutations/adjust-order-line-mutation'
const useUpdateProductInCart = () => {
const [loading, setLoading] = useState(false)
const [error, setError] = useState<CommonError | null>(null)
const { mutate } = useGetActiveOrder()
const updateProduct = (options:AdjustOrderLineMutationVariables,
fCallBack: (isSuccess: boolean, message?: string) => void
) => {
setError(null)
setLoading(true)
rawFetcher<AdjustOrderLineMutation>({
query: adjustOrderLineMutation ,
variables: options,
})
.then(({ data }) => {
if (data.adjustOrderLine.__typename !== "Order") {
throw CommonError.create(errorMapping(data.adjustOrderLine.message), data.adjustOrderLine.errorCode)
}
mutate()
fCallBack(true)
})
.catch((error) => {
setError(error)
fCallBack(false, error.message)
})
.finally(() => setLoading(false))
}
return { loading, updateProduct, error }
}
export default useUpdateProductInCart

View File

@@ -0,0 +1,2 @@