From 693935a480f4f531b8af52c141fd36352440f05a Mon Sep 17 00:00:00 2001 From: lytrankieio123 Date: Tue, 19 Oct 2021 18:48:10 +0700 Subject: [PATCH] :sparkles: feat: apply coupon code for order :%s --- framework/vendure/schema.d.ts | 31 ++++++ .../mutations/apply-coupon-code-mutation.ts | 21 ++++ .../ButtonCommon/ButtonCommon.module.scss | 2 +- src/components/hooks/order/index.ts | 1 + .../hooks/order/useApplyCouponCode.tsx | 41 ++++++++ .../CheckoutBill/CheckoutBill.module.scss | 5 - .../checkout/CheckoutBill/CheckoutBill.tsx | 9 +- .../FormPromotionCode.module.scss | 19 ++++ .../FormPromotionCode/FormPromotionCode.tsx | 97 +++++++++++++++++++ .../checkout/CheckoutInfo/CheckoutInfo.tsx | 2 +- .../CustomerInfoForm/CustomerInfoForm.tsx | 11 ++- .../ShippingInfoForm/ShippingInfoForm.tsx | 5 +- .../CheckoutPage/CheckoutPage.module.scss | 5 +- src/utils/language.utils.ts | 3 +- 14 files changed, 229 insertions(+), 23 deletions(-) create mode 100644 framework/vendure/utils/mutations/apply-coupon-code-mutation.ts create mode 100644 src/components/hooks/order/useApplyCouponCode.tsx create mode 100644 src/components/modules/checkout/CheckoutBill/FormPromotionCode/FormPromotionCode.module.scss create mode 100644 src/components/modules/checkout/CheckoutBill/FormPromotionCode/FormPromotionCode.tsx diff --git a/framework/vendure/schema.d.ts b/framework/vendure/schema.d.ts index 16614ca65..ae4467159 100644 --- a/framework/vendure/schema.d.ts +++ b/framework/vendure/schema.d.ts @@ -3053,6 +3053,7 @@ export type CartFragment = { __typename?: 'Order' } & Pick< | 'currencyCode' > & { shippingAddress?: Maybe<{ __typename?: 'OrderAddress' } & Pick> + discounts?: Maybe<{ __typename?: 'Discount' } & Pick> customer?: Maybe<{ __typename?: 'Customer' } & Pick> lines: Array< { __typename?: 'OrderLine' } & Pick< @@ -3156,6 +3157,36 @@ export type AdjustOrderLineMutation = { __typename?: 'Mutation' } & { >) } + +export type ApplyCouponCodeMutationVariables = Exact<{ + couponCode: Scalars['String']; +}>; + +export type ApplyCouponCodeMutation = { + applyCouponCode: + | TestOrderFragmentFragment + | Pick + | Pick + | Pick; +}; + +export type ApplyCouponCodeMutation = { __typename?: 'Mutation' } & { + applyCouponCode: + | ({ __typename: 'Order' } & CartFragment) + | ({ __typename: 'CouponCodeExpiredError' } & Pick< + CouponCodeExpiredError, + 'errorCode' | 'message' + >) + | ({ __typename: 'CouponCodeInvalidError' } & Pick< + CouponCodeInvalidError, + 'errorCode' | 'message' + >) + | ({ __typename: 'CouponCodeLimitError' } & Pick< + CouponCodeLimitError, + 'errorCode' | 'message' + >) +} + export type LoginMutationVariables = Exact<{ username: Scalars['String'] password: Scalars['String'] diff --git a/framework/vendure/utils/mutations/apply-coupon-code-mutation.ts b/framework/vendure/utils/mutations/apply-coupon-code-mutation.ts new file mode 100644 index 000000000..c5b1a9a90 --- /dev/null +++ b/framework/vendure/utils/mutations/apply-coupon-code-mutation.ts @@ -0,0 +1,21 @@ +export const applyCouponCodeMutation = /* GraphQL */ ` +mutation applyCouponCode($couponCode: String!) { + applyCouponCode(couponCode: $couponCode) { + __typename + ... on Order { + id + createdAt + updatedAt + discounts { + type + amount + amountWithTax + } + } + ... on ErrorResult { + errorCode + message + } + } +} +` diff --git a/src/components/common/ButtonCommon/ButtonCommon.module.scss b/src/components/common/ButtonCommon/ButtonCommon.module.scss index 318180ede..69c07c670 100644 --- a/src/components/common/ButtonCommon/ButtonCommon.module.scss +++ b/src/components/common/ButtonCommon/ButtonCommon.module.scss @@ -1,7 +1,7 @@ @import "../../../styles/utilities"; .buttonCommon { - @apply shape-common; + @apply shape-common h-full; &:hover { .inner { @apply shadow-md; diff --git a/src/components/hooks/order/index.ts b/src/components/hooks/order/index.ts index 0aef836be..bf6383122 100644 --- a/src/components/hooks/order/index.ts +++ b/src/components/hooks/order/index.ts @@ -1,3 +1,4 @@ export { default as useSetCustomerForOrder } from './useSetCustomerForOrder' export { default as useSetOrderShippingAddress } from './useSetOrderShippingAddress' +export { default as useApplyCouponCode } from './useApplyCouponCode' diff --git a/src/components/hooks/order/useApplyCouponCode.tsx b/src/components/hooks/order/useApplyCouponCode.tsx new file mode 100644 index 000000000..be2c77878 --- /dev/null +++ b/src/components/hooks/order/useApplyCouponCode.tsx @@ -0,0 +1,41 @@ +import { ApplyCouponCodeMutation } from '@framework/schema' +import { applyCouponCodeMutation } from '@framework/utils/mutations/apply-coupon-code-mutation' +import { useState } from 'react' +import { CommonError } from 'src/domains/interfaces/CommonError' +import rawFetcher from 'src/utils/rawFetcher' +import { useGetActiveOrder } from '../cart' + + +const useApplyCouponCode = () => { + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const { mutate } = useGetActiveOrder() + + const applyCouponCode = (couponCode: string, + fCallBack: (isSuccess: boolean, message?: string) => void + ) => { + setError(null) + setLoading(true) + rawFetcher({ + query: applyCouponCodeMutation, + variables: { couponCode }, + }) + .then(({ data }) => { + if (data.applyCouponCode.__typename === 'Order') { + fCallBack(true) + mutate() + } else { + fCallBack(false, data.applyCouponCode.message) + } + }) + .catch((error) => { + setError(error) + fCallBack(false, error.message) + }) + .finally(() => setLoading(false)) + } + + return { loading, applyCouponCode, error } +} + +export default useApplyCouponCode diff --git a/src/components/modules/checkout/CheckoutBill/CheckoutBill.module.scss b/src/components/modules/checkout/CheckoutBill/CheckoutBill.module.scss index 84dea0f06..8cd564917 100644 --- a/src/components/modules/checkout/CheckoutBill/CheckoutBill.module.scss +++ b/src/components/modules/checkout/CheckoutBill/CheckoutBill.module.scss @@ -18,11 +18,6 @@ min-height: 52.8rem; } .bot { - .promo { - // padding: 3.2rem; - @apply bg-gray flex justify-between items-center; - min-height: 6.4rem; - } .price { margin-top: 3.2rem; .line { diff --git a/src/components/modules/checkout/CheckoutBill/CheckoutBill.tsx b/src/components/modules/checkout/CheckoutBill/CheckoutBill.tsx index 259397980..a7f593764 100644 --- a/src/components/modules/checkout/CheckoutBill/CheckoutBill.tsx +++ b/src/components/modules/checkout/CheckoutBill/CheckoutBill.tsx @@ -1,8 +1,8 @@ import React from 'react' -import s from './CheckoutBill.module.scss' import { CardItemCheckout } from '../../../common' import { CardItemCheckoutProps } from '../../../common/CardItemCheckout/CardItemCheckout' -import { IconCirclePlus } from 'src/components/icons' +import s from './CheckoutBill.module.scss' +import FormPromotionCode from './FormPromotionCode/FormPromotionCode' interface CheckoutBillProps { data: CardItemCheckoutProps[] @@ -20,10 +20,7 @@ const CheckoutBill = ({ data }: CheckoutBillProps) => { })}
-
- Apply Promotion Code - -
+
Subtotal diff --git a/src/components/modules/checkout/CheckoutBill/FormPromotionCode/FormPromotionCode.module.scss b/src/components/modules/checkout/CheckoutBill/FormPromotionCode/FormPromotionCode.module.scss new file mode 100644 index 000000000..1391a5e00 --- /dev/null +++ b/src/components/modules/checkout/CheckoutBill/FormPromotionCode/FormPromotionCode.module.scss @@ -0,0 +1,19 @@ +.promo { + @apply bg-gray flex justify-between items-center; + min-height: 6.4rem; + .modalPromotion { + min-width: 40rem; + .bottom { + @apply flex justify-end items-center; + margin-top: 3.2rem; + button { + &:first-child { + margin-right: 0.8rem; + @screen md { + margin-right: 3.2rem; + } + } + } + } + } +} diff --git a/src/components/modules/checkout/CheckoutBill/FormPromotionCode/FormPromotionCode.tsx b/src/components/modules/checkout/CheckoutBill/FormPromotionCode/FormPromotionCode.tsx new file mode 100644 index 000000000..5fc3c95ee --- /dev/null +++ b/src/components/modules/checkout/CheckoutBill/FormPromotionCode/FormPromotionCode.tsx @@ -0,0 +1,97 @@ +import { Form, Formik } from 'formik'; +import React, { useEffect, useRef } from 'react'; +import { ButtonCommon, InputFiledInForm, ModalCommon } from 'src/components/common'; +import { useMessage } from 'src/components/contexts'; +import { useModalCommon } from 'src/components/hooks'; +import { useApplyCouponCode } from 'src/components/hooks/order'; +import { IconCirclePlus } from 'src/components/icons'; +import { LANGUAGE } from 'src/utils/language.utils'; +import { CustomInputCommon } from 'src/utils/type.utils'; +import * as Yup from 'yup'; +import s from './FormPromotionCode.module.scss'; + +const displayingErrorMessagesSchema = Yup.object().shape({ + couponCode: Yup.string().required(LANGUAGE.MESSAGE.REQUIRED), +}) + +const FormPromotionCode = () => { + const { visible, openModal, closeModal } = useModalCommon({ initialValue: false }) + const { showMessageError, showMessageSuccess } = useMessage() + const { applyCouponCode, loading } = useApplyCouponCode() + const inputRef = useRef(null) + + useEffect(() => { + setTimeout(() => { + if (visible) { + inputRef.current?.focus() + } + }, 500); + }, [visible]) + + const handleSubmit = (values: { couponCode: string }) => { + applyCouponCode(values.couponCode, onSubmitCalBack) + } + + const onSubmitCalBack = (isSuccess: boolean, msg?: string) => { + // TODO: + if (isSuccess) { + showMessageSuccess("Applied coupon code successfully.", 5000) + closeModal() + } else { + showMessageError(msg) + } + } + + return ( +
+ Apply Promotion Code + + +
+ + {({ errors, touched, isValid, submitForm }) => ( +
+
+ + +
+
+ + {LANGUAGE.BUTTON_LABEL.CANCEL} + + + Apply promotion code + +
+
+ )} +
+
+
+
+ ); +}; + +export default FormPromotionCode; \ No newline at end of file diff --git a/src/components/modules/checkout/CheckoutInfo/CheckoutInfo.tsx b/src/components/modules/checkout/CheckoutInfo/CheckoutInfo.tsx index 753d9e8cf..65321167e 100644 --- a/src/components/modules/checkout/CheckoutInfo/CheckoutInfo.tsx +++ b/src/components/modules/checkout/CheckoutInfo/CheckoutInfo.tsx @@ -132,7 +132,7 @@ const CheckoutInfo = ({ onViewCart }: CheckoutInfoProps) => { {/* TODO: remove */} test create order test get activeStep order - + TOTAL: {order?.totalPrice}
diff --git a/src/components/modules/checkout/CheckoutInfo/components/CustomerInfoForm/CustomerInfoForm.tsx b/src/components/modules/checkout/CheckoutInfo/components/CustomerInfoForm/CustomerInfoForm.tsx index 9ea2f2f1b..fb18b79e3 100644 --- a/src/components/modules/checkout/CheckoutInfo/components/CustomerInfoForm/CustomerInfoForm.tsx +++ b/src/components/modules/checkout/CheckoutInfo/components/CustomerInfoForm/CustomerInfoForm.tsx @@ -1,5 +1,5 @@ import { Form, Formik } from 'formik' -import React, { useRef, useState } from 'react' +import React, { useEffect, useRef, useState } from 'react' import { ButtonCommon, InputFiledInForm } from 'src/components/common' import ModalAuthenticate from 'src/components/common/ModalAuthenticate/ModalAuthenticate' import { useMessage } from 'src/components/contexts' @@ -16,6 +16,8 @@ import ModalConfirmLogin from './ModalConfirmLogin/ModalConfirmLogin' interface Props { id: number onConfirm: (id: number) => void + activeStep: number + } const displayingErrorMessagesSchema = Yup.object().shape({ @@ -24,7 +26,7 @@ const displayingErrorMessagesSchema = Yup.object().shape({ emailAddress: Yup.string().email(LANGUAGE.MESSAGE.INVALID_EMAIL).required(LANGUAGE.MESSAGE.REQUIRED), }) -const CustomerInfoForm = ({ id, onConfirm }: Props) => { +const CustomerInfoForm = ({ id, onConfirm, activeStep }: Props) => { const firstNameRef = useRef(null) const { setCustomerForOrder, loading } = useSetCustomerForOrder() const { showMessageError } = useMessage() @@ -32,6 +34,11 @@ const CustomerInfoForm = ({ id, onConfirm }: Props) => { const { visible: visibleModalConfirmLogin, closeModal: closeModalConfirmLogin, openModal: openModalConfirmLogin } = useModalCommon({ initialValue: false }) const { visible: visibleModalAuthen, closeModal: closeModalAuthen, openModal: openModalAuthen } = useModalCommon({ initialValue: false }) + useEffect(() => { + setTimeout(() => { + firstNameRef.current?.focus() + }, 500); + }, [activeStep]) const handleSubmit = (values: { firstName: string, lastName: string, emailAddress: string }) => { const { firstName, lastName, emailAddress } = values diff --git a/src/components/modules/checkout/CheckoutInfo/components/ShippingInfoForm/ShippingInfoForm.tsx b/src/components/modules/checkout/CheckoutInfo/components/ShippingInfoForm/ShippingInfoForm.tsx index f2439a6d7..f1931d7fb 100644 --- a/src/components/modules/checkout/CheckoutInfo/components/ShippingInfoForm/ShippingInfoForm.tsx +++ b/src/components/modules/checkout/CheckoutInfo/components/ShippingInfoForm/ShippingInfoForm.tsx @@ -41,7 +41,7 @@ const provinceOptions = [ const ShippingInfoForm = ({ onConfirm, id, activeStep }: ShippingInfoFormProps) => { const addressRef = useRef(null) - const { setOrderShippingAddress } = useSetOrderShippingAddress() + const { setOrderShippingAddress, loading } = useSetOrderShippingAddress() const { showMessageError } = useMessage() useEffect(() => { @@ -171,8 +171,7 @@ const ShippingInfoForm = ({ onConfirm, id, activeStep }: ShippingInfoFormProps)
- {/* */} - + Continue to Payment
diff --git a/src/components/modules/checkout/CheckoutPage/CheckoutPage.module.scss b/src/components/modules/checkout/CheckoutPage/CheckoutPage.module.scss index dabbde19e..a1e68d863 100644 --- a/src/components/modules/checkout/CheckoutPage/CheckoutPage.module.scss +++ b/src/components/modules/checkout/CheckoutPage/CheckoutPage.module.scss @@ -46,10 +46,7 @@ color:var(--text-base); } } - button{ - margin-top: 2rem; - width: 100%; - } + } } } diff --git a/src/utils/language.utils.ts b/src/utils/language.utils.ts index 2005f900d..7a8abd6b4 100644 --- a/src/utils/language.utils.ts +++ b/src/utils/language.utils.ts @@ -5,7 +5,8 @@ export const LANGUAGE = { CONFIRM:'Confirm', ADD_TO_CARD: 'Add to Cart', PREORDER: 'Pre-Order Now', - SIGNIN :'Sign In' + SIGNIN :'Sign In', + CANCEL: 'Cancel', }, PLACE_HOLDER: { SEARCH: 'Search',