mirror of
https://github.com/vercel/commerce.git
synced 2025-07-24 10:41:23 +00:00
✨ feat: apply coupon code for order
:%s
This commit is contained in:
31
framework/vendure/schema.d.ts
vendored
31
framework/vendure/schema.d.ts
vendored
@@ -3053,6 +3053,7 @@ export type CartFragment = { __typename?: 'Order' } & Pick<
|
|||||||
| 'currencyCode'
|
| 'currencyCode'
|
||||||
> & {
|
> & {
|
||||||
shippingAddress?: Maybe<{ __typename?: 'OrderAddress' } & Pick<OrderAddress, 'streetLine1' | 'fullName' | 'city' | 'province' | 'postalCode' |'countryCode' | 'phoneNumber'>>
|
shippingAddress?: Maybe<{ __typename?: 'OrderAddress' } & Pick<OrderAddress, 'streetLine1' | 'fullName' | 'city' | 'province' | 'postalCode' |'countryCode' | 'phoneNumber'>>
|
||||||
|
discounts?: Maybe<{ __typename?: 'Discount' } & Pick<Discount, 'type' | 'amount' | 'amountWithTax'>>
|
||||||
customer?: Maybe<{ __typename?: 'Customer' } & Pick<Customer, 'id' | 'firstName' | 'lastName' | 'emailAddress'>>
|
customer?: Maybe<{ __typename?: 'Customer' } & Pick<Customer, 'id' | 'firstName' | 'lastName' | 'emailAddress'>>
|
||||||
lines: Array<
|
lines: Array<
|
||||||
{ __typename?: 'OrderLine' } & Pick<
|
{ __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<CouponCodeExpiredError, 'errorCode' | 'message'>
|
||||||
|
| Pick<CouponCodeInvalidError, 'errorCode' | 'message'>
|
||||||
|
| Pick<CouponCodeLimitError, 'errorCode' | 'message'>;
|
||||||
|
};
|
||||||
|
|
||||||
|
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<{
|
export type LoginMutationVariables = Exact<{
|
||||||
username: Scalars['String']
|
username: Scalars['String']
|
||||||
password: Scalars['String']
|
password: Scalars['String']
|
||||||
|
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
@@ -1,7 +1,7 @@
|
|||||||
@import "../../../styles/utilities";
|
@import "../../../styles/utilities";
|
||||||
|
|
||||||
.buttonCommon {
|
.buttonCommon {
|
||||||
@apply shape-common;
|
@apply shape-common h-full;
|
||||||
&:hover {
|
&:hover {
|
||||||
.inner {
|
.inner {
|
||||||
@apply shadow-md;
|
@apply shadow-md;
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
export { default as useSetCustomerForOrder } from './useSetCustomerForOrder'
|
export { default as useSetCustomerForOrder } from './useSetCustomerForOrder'
|
||||||
export { default as useSetOrderShippingAddress } from './useSetOrderShippingAddress'
|
export { default as useSetOrderShippingAddress } from './useSetOrderShippingAddress'
|
||||||
|
export { default as useApplyCouponCode } from './useApplyCouponCode'
|
||||||
|
|
||||||
|
41
src/components/hooks/order/useApplyCouponCode.tsx
Normal file
41
src/components/hooks/order/useApplyCouponCode.tsx
Normal file
@@ -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<CommonError | null>(null)
|
||||||
|
const { mutate } = useGetActiveOrder()
|
||||||
|
|
||||||
|
const applyCouponCode = (couponCode: string,
|
||||||
|
fCallBack: (isSuccess: boolean, message?: string) => void
|
||||||
|
) => {
|
||||||
|
setError(null)
|
||||||
|
setLoading(true)
|
||||||
|
rawFetcher<ApplyCouponCodeMutation>({
|
||||||
|
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
|
@@ -18,11 +18,6 @@
|
|||||||
min-height: 52.8rem;
|
min-height: 52.8rem;
|
||||||
}
|
}
|
||||||
.bot {
|
.bot {
|
||||||
.promo {
|
|
||||||
// padding: 3.2rem;
|
|
||||||
@apply bg-gray flex justify-between items-center;
|
|
||||||
min-height: 6.4rem;
|
|
||||||
}
|
|
||||||
.price {
|
.price {
|
||||||
margin-top: 3.2rem;
|
margin-top: 3.2rem;
|
||||||
.line {
|
.line {
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import s from './CheckoutBill.module.scss'
|
|
||||||
import { CardItemCheckout } from '../../../common'
|
import { CardItemCheckout } from '../../../common'
|
||||||
import { CardItemCheckoutProps } from '../../../common/CardItemCheckout/CardItemCheckout'
|
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 {
|
interface CheckoutBillProps {
|
||||||
data: CardItemCheckoutProps[]
|
data: CardItemCheckoutProps[]
|
||||||
@@ -20,10 +20,7 @@ const CheckoutBill = ({ data }: CheckoutBillProps) => {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div className={s.bot}>
|
<div className={s.bot}>
|
||||||
<div className={s.promo}>
|
<FormPromotionCode/>
|
||||||
Apply Promotion Code
|
|
||||||
<IconCirclePlus />
|
|
||||||
</div>
|
|
||||||
<div className={s.price}>
|
<div className={s.price}>
|
||||||
<div className={s.line}>
|
<div className={s.line}>
|
||||||
Subtotal
|
Subtotal
|
||||||
|
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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<CustomInputCommon>(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 (
|
||||||
|
<div className={s.promo}>
|
||||||
|
Apply Promotion Code
|
||||||
|
<button className={s.buttonAdd} onClick={openModal}>
|
||||||
|
<IconCirclePlus />
|
||||||
|
</button>
|
||||||
|
<ModalCommon
|
||||||
|
visible={visible}
|
||||||
|
onClose={closeModal}
|
||||||
|
>
|
||||||
|
<div className={s.modalPromotion}>
|
||||||
|
<Formik
|
||||||
|
initialValues={{
|
||||||
|
couponCode: ''
|
||||||
|
}}
|
||||||
|
validationSchema={displayingErrorMessagesSchema}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
>
|
||||||
|
{({ errors, touched, isValid, submitForm }) => (
|
||||||
|
<Form className="u-form">
|
||||||
|
<div className="body">
|
||||||
|
|
||||||
|
<InputFiledInForm
|
||||||
|
name="couponCode"
|
||||||
|
placeholder="Coupon code"
|
||||||
|
error={
|
||||||
|
touched.couponCode && errors.couponCode
|
||||||
|
? errors.couponCode.toString()
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
isShowIconSuccess={touched.couponCode && !errors.couponCode}
|
||||||
|
onEnter={isValid ? submitForm : undefined}
|
||||||
|
ref={inputRef}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={s.bottom}>
|
||||||
|
<ButtonCommon disabled={loading} onClick={closeModal} type='light' size='small'>
|
||||||
|
{LANGUAGE.BUTTON_LABEL.CANCEL}
|
||||||
|
</ButtonCommon>
|
||||||
|
<ButtonCommon HTMLType='submit' loading={loading} size='small'>
|
||||||
|
Apply promotion code
|
||||||
|
</ButtonCommon>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</div>
|
||||||
|
</ModalCommon>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FormPromotionCode;
|
@@ -132,7 +132,7 @@ const CheckoutInfo = ({ onViewCart }: CheckoutInfoProps) => {
|
|||||||
{/* TODO: remove */}
|
{/* TODO: remove */}
|
||||||
<ButtonCommon onClick={createOrder}>test create order</ButtonCommon>
|
<ButtonCommon onClick={createOrder}>test create order</ButtonCommon>
|
||||||
<ButtonCommon onClick={createOrder}>test get activeStep order</ButtonCommon>
|
<ButtonCommon onClick={createOrder}>test get activeStep order</ButtonCommon>
|
||||||
|
TOTAL: {order?.totalPrice}
|
||||||
|
|
||||||
<div className={s.title}>
|
<div className={s.title}>
|
||||||
<Logo />
|
<Logo />
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Form, Formik } from 'formik'
|
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 { ButtonCommon, InputFiledInForm } from 'src/components/common'
|
||||||
import ModalAuthenticate from 'src/components/common/ModalAuthenticate/ModalAuthenticate'
|
import ModalAuthenticate from 'src/components/common/ModalAuthenticate/ModalAuthenticate'
|
||||||
import { useMessage } from 'src/components/contexts'
|
import { useMessage } from 'src/components/contexts'
|
||||||
@@ -16,6 +16,8 @@ import ModalConfirmLogin from './ModalConfirmLogin/ModalConfirmLogin'
|
|||||||
interface Props {
|
interface Props {
|
||||||
id: number
|
id: number
|
||||||
onConfirm: (id: number) => void
|
onConfirm: (id: number) => void
|
||||||
|
activeStep: number
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const displayingErrorMessagesSchema = Yup.object().shape({
|
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),
|
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<CustomInputCommon>(null)
|
const firstNameRef = useRef<CustomInputCommon>(null)
|
||||||
const { setCustomerForOrder, loading } = useSetCustomerForOrder()
|
const { setCustomerForOrder, loading } = useSetCustomerForOrder()
|
||||||
const { showMessageError } = useMessage()
|
const { showMessageError } = useMessage()
|
||||||
@@ -32,6 +34,11 @@ const CustomerInfoForm = ({ id, onConfirm }: Props) => {
|
|||||||
const { visible: visibleModalConfirmLogin, closeModal: closeModalConfirmLogin, openModal: openModalConfirmLogin } = useModalCommon({ initialValue: false })
|
const { visible: visibleModalConfirmLogin, closeModal: closeModalConfirmLogin, openModal: openModalConfirmLogin } = useModalCommon({ initialValue: false })
|
||||||
const { visible: visibleModalAuthen, closeModal: closeModalAuthen, openModal: openModalAuthen } = 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 handleSubmit = (values: { firstName: string, lastName: string, emailAddress: string }) => {
|
||||||
const { firstName, lastName, emailAddress } = values
|
const { firstName, lastName, emailAddress } = values
|
||||||
|
@@ -41,7 +41,7 @@ const provinceOptions = [
|
|||||||
|
|
||||||
const ShippingInfoForm = ({ onConfirm, id, activeStep }: ShippingInfoFormProps) => {
|
const ShippingInfoForm = ({ onConfirm, id, activeStep }: ShippingInfoFormProps) => {
|
||||||
const addressRef = useRef<CustomInputCommon>(null)
|
const addressRef = useRef<CustomInputCommon>(null)
|
||||||
const { setOrderShippingAddress } = useSetOrderShippingAddress()
|
const { setOrderShippingAddress, loading } = useSetOrderShippingAddress()
|
||||||
const { showMessageError } = useMessage()
|
const { showMessageError } = useMessage()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -171,8 +171,7 @@ const ShippingInfoForm = ({ onConfirm, id, activeStep }: ShippingInfoFormProps)
|
|||||||
</div>
|
</div>
|
||||||
<div className={s.bottom}>
|
<div className={s.bottom}>
|
||||||
<ChekoutNotePolicy />
|
<ChekoutNotePolicy />
|
||||||
{/* <ButtonCommon HTMLType='submit' loading={loading} size="large"> */}
|
<ButtonCommon HTMLType='submit' loading={loading} size="large">
|
||||||
<ButtonCommon HTMLType='submit' size="large">
|
|
||||||
Continue to Payment
|
Continue to Payment
|
||||||
</ButtonCommon>
|
</ButtonCommon>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -46,10 +46,7 @@
|
|||||||
color:var(--text-base);
|
color:var(--text-base);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
button{
|
|
||||||
margin-top: 2rem;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,8 @@ export const LANGUAGE = {
|
|||||||
CONFIRM:'Confirm',
|
CONFIRM:'Confirm',
|
||||||
ADD_TO_CARD: 'Add to Cart',
|
ADD_TO_CARD: 'Add to Cart',
|
||||||
PREORDER: 'Pre-Order Now',
|
PREORDER: 'Pre-Order Now',
|
||||||
SIGNIN :'Sign In'
|
SIGNIN :'Sign In',
|
||||||
|
CANCEL: 'Cancel',
|
||||||
},
|
},
|
||||||
PLACE_HOLDER: {
|
PLACE_HOLDER: {
|
||||||
SEARCH: 'Search',
|
SEARCH: 'Search',
|
||||||
|
Reference in New Issue
Block a user