mirror of
https://github.com/vercel/commerce.git
synced 2025-07-22 12:24:18 +00:00
✨ feat: add shipping address to order
:%s
This commit is contained in:
@@ -62,6 +62,11 @@ export type Cart = {
|
||||
id: string
|
||||
// ID of the customer to which the cart belongs.
|
||||
customerId?: string
|
||||
customer?: {
|
||||
firstName: string,
|
||||
lastName: string,
|
||||
emailAddress: string,
|
||||
}
|
||||
// The email assigned to this cart
|
||||
email?: string
|
||||
// The date and time when the cart was created.
|
||||
|
16
framework/vendure/schema.d.ts
vendored
16
framework/vendure/schema.d.ts
vendored
@@ -332,17 +332,9 @@ export type SetCustomerForOrderMutation = { __typename?: 'Mutation' } & {
|
||||
>)
|
||||
}
|
||||
|
||||
export type SetCustomerForOrderMutation = { __typename?: 'Mutation' } & {
|
||||
setCustomerForOrder:
|
||||
| ({ __typename: 'ActiveOrderCustomerFragment' } & Pick<ActiveOrderCustomerFragment, 'customer', 'lines'>)
|
||||
| ({ __typename: 'AlreadyLoggedInError' } & Pick<
|
||||
AlreadyLoggedInError,
|
||||
'errorCode' | 'message'
|
||||
>)
|
||||
| ({ __typename: 'EmailAddressConflictError' } & Pick<
|
||||
EmailAddressConflictError,
|
||||
'errorCode' | 'message'
|
||||
>)
|
||||
export type SetOrderShippingAddressMutation = { __typename?: 'Mutation' } & {
|
||||
setOrderShippingAddress:
|
||||
| ({ __typename: 'Order' } & Pick<Order, 'id' | 'total' | 'totalQuantity' | 'code' | 'shippingAddress'>)
|
||||
| ({ __typename: 'NoActiveOrderError' } & Pick<
|
||||
NoActiveOrderError,
|
||||
'errorCode' | 'message'
|
||||
@@ -3060,7 +3052,7 @@ export type CartFragment = { __typename?: 'Order' } & Pick<
|
||||
| 'totalWithTax'
|
||||
| 'currencyCode'
|
||||
> & {
|
||||
customer?: Maybe<{ __typename?: 'Customer' } & Pick<Customer, 'id'>>
|
||||
customer?: Maybe<{ __typename?: 'Customer' } & Pick<Customer, 'id' | 'firstName' | 'lastName' | 'emailAddress'>>
|
||||
lines: Array<
|
||||
{ __typename?: 'OrderLine' } & Pick<
|
||||
OrderLine,
|
||||
|
@@ -11,6 +11,9 @@ export const cartFragment = /* GraphQL */ `
|
||||
currencyCode
|
||||
customer {
|
||||
id
|
||||
firstName
|
||||
lastName
|
||||
emailAddress
|
||||
}
|
||||
lines {
|
||||
id
|
||||
|
@@ -0,0 +1,25 @@
|
||||
export const setOrderShippingAddressMutation = /* GraphQL */ `
|
||||
mutation setOrderShippingAddress($input: CreateAddressInput!) {
|
||||
setOrderShippingAddress(input: $input) {
|
||||
__typename
|
||||
... on Order {
|
||||
id
|
||||
createdAt
|
||||
updatedAt
|
||||
code
|
||||
shippingAddress {
|
||||
streetLine1
|
||||
city
|
||||
province
|
||||
postalCode
|
||||
countryCode
|
||||
phoneNumber
|
||||
}
|
||||
}
|
||||
... on ErrorResult {
|
||||
errorCode
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
@@ -45,6 +45,11 @@ export function normalizeCart(order: CartFragment): Cart {
|
||||
subtotalPrice: order.subTotalWithTax / 100,
|
||||
totalPrice: order.totalWithTax / 100,
|
||||
customerId: order.customer?.id,
|
||||
customer: {
|
||||
firstName: order.customer?.firstName || '',
|
||||
lastName: order.customer?.lastName || '',
|
||||
emailAddress: order.customer?.emailAddress || '',
|
||||
},
|
||||
lineItems: order.lines?.map((l) => ({
|
||||
id: l.id,
|
||||
name: l.productVariant.name,
|
||||
|
@@ -1,8 +1,6 @@
|
||||
import classNames from 'classnames'
|
||||
import { divide } from 'lodash'
|
||||
import React from 'react'
|
||||
import { IconDoneCheckout } from 'src/components/icons'
|
||||
import { CheckOutForm } from 'src/utils/types.utils'
|
||||
import s from './CheckoutCollapse.module.scss'
|
||||
interface CheckoutCollapseProps {
|
||||
visible: boolean
|
||||
@@ -14,6 +12,7 @@ interface CheckoutCollapseProps {
|
||||
onOpen?: (id:number) => void
|
||||
onEditClick?:(id:number) => void
|
||||
note?:string
|
||||
disableEdit?: boolean
|
||||
}
|
||||
|
||||
const CheckoutCollapse = ({
|
||||
@@ -25,14 +24,15 @@ const CheckoutCollapse = ({
|
||||
note,
|
||||
onOpen,
|
||||
onClose,
|
||||
onEditClick
|
||||
onEditClick,
|
||||
disableEdit,
|
||||
}: CheckoutCollapseProps) => {
|
||||
const handleTitleClick = () => {
|
||||
if(visible){
|
||||
onClose && onClose(id)
|
||||
}else{
|
||||
onOpen && onOpen(id)
|
||||
}
|
||||
} else if (!disableEdit) {
|
||||
onOpen && onOpen(id)
|
||||
}
|
||||
}
|
||||
const handleEdit = () => {
|
||||
onEditClick && onEditClick(id)
|
||||
@@ -48,7 +48,7 @@ const CheckoutCollapse = ({
|
||||
{title}
|
||||
</div>
|
||||
</div>
|
||||
{isEdit && <div className={s.edit} onClick={handleEdit}>{'Edit'}</div>}
|
||||
{!disableEdit && isEdit && <div className={s.edit} onClick={handleEdit}>{'Edit'}</div>}
|
||||
</div>
|
||||
{(!visible && isEdit) && (<div className={s.note}>{note}</div>) }
|
||||
<div className={classNames(s.body, { [`${s.show}`]: visible })}>{children}</div>
|
||||
|
@@ -12,9 +12,11 @@ interface Props {
|
||||
visible: boolean
|
||||
closeModal: () => void
|
||||
mode?: '' | 'register'
|
||||
initialEmail?: string
|
||||
disableRedirect ?: boolean
|
||||
}
|
||||
|
||||
const ModalAuthenticate = ({ visible, mode, closeModal }: Props) => {
|
||||
const ModalAuthenticate = ({ visible, mode, closeModal, initialEmail, disableRedirect }: Props) => {
|
||||
const [isLogin, setIsLogin] = useState<boolean>(true)
|
||||
const { customer } = useActiveCustomer()
|
||||
const router = useRouter()
|
||||
@@ -30,9 +32,11 @@ const ModalAuthenticate = ({ visible, mode, closeModal }: Props) => {
|
||||
useEffect(() => {
|
||||
if (visible && customer) {
|
||||
closeModal()
|
||||
router.push(ROUTE.ACCOUNT)
|
||||
if (!disableRedirect) {
|
||||
router.push(ROUTE.ACCOUNT)
|
||||
}
|
||||
}
|
||||
}, [customer, visible, closeModal, router])
|
||||
}, [customer, visible, closeModal, router, disableRedirect])
|
||||
|
||||
const onSwitch = () => {
|
||||
setIsLogin(!isLogin)
|
||||
@@ -51,7 +55,7 @@ const ModalAuthenticate = ({ visible, mode, closeModal }: Props) => {
|
||||
[s.register]: !isLogin,
|
||||
})}
|
||||
>
|
||||
<FormLogin isHide={!isLogin} onSwitch={onSwitch} />
|
||||
<FormLogin isHide={!isLogin} onSwitch={onSwitch} initialEmail={initialEmail} />
|
||||
<FormRegister isHide={isLogin} onSwitch={onSwitch} />
|
||||
</div>
|
||||
</section>
|
||||
|
@@ -15,6 +15,8 @@ import styles from './FormLogin.module.scss'
|
||||
interface Props {
|
||||
isHide: boolean
|
||||
onSwitch: () => void
|
||||
initialEmail?: string
|
||||
|
||||
}
|
||||
|
||||
const displayingErrorMessagesSchema = Yup.object().shape({
|
||||
@@ -24,7 +26,7 @@ const displayingErrorMessagesSchema = Yup.object().shape({
|
||||
.required(LANGUAGE.MESSAGE.REQUIRED),
|
||||
})
|
||||
|
||||
const FormLogin = ({ onSwitch, isHide }: Props) => {
|
||||
const FormLogin = ({ onSwitch, isHide, initialEmail = ''}: Props) => {
|
||||
const emailRef = useRef<CustomInputCommon>(null)
|
||||
const { loading, login } = useLogin()
|
||||
const { showMessageSuccess, showMessageError } = useMessage()
|
||||
@@ -54,7 +56,7 @@ const FormLogin = ({ onSwitch, isHide }: Props) => {
|
||||
<Formik
|
||||
initialValues={{
|
||||
password: '',
|
||||
email: '',
|
||||
email: initialEmail,
|
||||
}}
|
||||
validationSchema={displayingErrorMessagesSchema}
|
||||
onSubmit={onLogin}
|
||||
|
@@ -18,14 +18,15 @@ const ModalConfirm = ({
|
||||
children,
|
||||
title = 'Confirm',
|
||||
loading,
|
||||
onClose,
|
||||
...props
|
||||
}: ModalConfirmProps) => {
|
||||
return (
|
||||
<ModalCommon {...props} title={title}>
|
||||
<ModalCommon onClose={onClose} title={title} {...props}>
|
||||
{children}
|
||||
<div className={s.footer}>
|
||||
<div className="mr-4">
|
||||
<ButtonCommon onClick={onCancel} type="light"> {cancelText}</ButtonCommon>
|
||||
<ButtonCommon onClick={onCancel || onClose} type="light"> {cancelText}</ButtonCommon>
|
||||
</div>
|
||||
<ButtonCommon onClick={onOk} loading={loading}>{okText}</ButtonCommon>
|
||||
</div>
|
||||
|
@@ -0,0 +1,19 @@
|
||||
@import "../../../styles/form";
|
||||
|
||||
.inputWrap {
|
||||
@extend .formInputWrap;
|
||||
.inputInner {
|
||||
select {
|
||||
@apply w-full;
|
||||
background-color: var(--white);
|
||||
padding: 1.6rem 1.6rem;
|
||||
border: 1px solid var(--border-line);
|
||||
border-radius: 0.8rem;
|
||||
&:focus {
|
||||
outline: none;
|
||||
border: 1px solid var(--primary);
|
||||
@apply shadow-md;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
106
src/components/common/SelectFieldInForm/SelectFieldInForm.tsx
Normal file
106
src/components/common/SelectFieldInForm/SelectFieldInForm.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import classNames from "classnames"
|
||||
import { Field } from "formik"
|
||||
import { useMemo } from "react"
|
||||
import { IconCheck, IconError } from "src/components/icons"
|
||||
import s from './SelectFieldInForm.module.scss'
|
||||
|
||||
|
||||
interface Props {
|
||||
placeholder?: string
|
||||
styleType?: 'default' | 'custom'
|
||||
backgroundTransparent?: boolean
|
||||
icon?: React.ReactNode
|
||||
isIconSuffix?: boolean
|
||||
isShowIconSuccess?: boolean
|
||||
name: string
|
||||
error?: string
|
||||
options: any[]
|
||||
keyNameOption?: string[]
|
||||
keyValueOption?: string
|
||||
nameSeperator?: string
|
||||
|
||||
}
|
||||
|
||||
const SelectFieldInForm = ({
|
||||
name,
|
||||
placeholder,
|
||||
options,
|
||||
styleType = 'default',
|
||||
icon,
|
||||
backgroundTransparent = false,
|
||||
isIconSuffix = true,
|
||||
isShowIconSuccess,
|
||||
error,
|
||||
keyNameOption = ['name'],
|
||||
keyValueOption = 'value',
|
||||
nameSeperator = " ",
|
||||
|
||||
}: Props) => {
|
||||
const iconElement = useMemo(() => {
|
||||
if (error) {
|
||||
return (
|
||||
<span className={s.icon}>
|
||||
<IconError />{' '}
|
||||
</span>
|
||||
)
|
||||
} else if (isShowIconSuccess) {
|
||||
return (
|
||||
<span className={s.icon}>
|
||||
<IconCheck />{' '}
|
||||
</span>
|
||||
)
|
||||
} else if (icon) {
|
||||
return <span className={s.icon}>{icon} </span>
|
||||
}
|
||||
return <></>
|
||||
}, [icon, error, isShowIconSuccess])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames({
|
||||
[s.inputWrap]: true,
|
||||
[s[styleType]]: true,
|
||||
[s.bgTransparent]: backgroundTransparent,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={classNames({
|
||||
[s.inputInner]: true,
|
||||
[s.preserve]: isIconSuffix,
|
||||
[s.success]: isShowIconSuccess,
|
||||
[s.error]: !!error,
|
||||
})}
|
||||
>
|
||||
{iconElement}
|
||||
<Field
|
||||
as="select"
|
||||
name={name}
|
||||
placeholder={placeholder}
|
||||
>
|
||||
{
|
||||
options.map((item) => {
|
||||
let name = ''
|
||||
keyNameOption.map((key) => {
|
||||
if (name) {
|
||||
name += nameSeperator
|
||||
}
|
||||
name += item[key]
|
||||
})
|
||||
name = name.trim()
|
||||
return <option
|
||||
key={item[keyValueOption]}
|
||||
value={item[keyValueOption]}
|
||||
>
|
||||
{name}
|
||||
</option>
|
||||
})
|
||||
}
|
||||
</Field>
|
||||
|
||||
</div>
|
||||
{error && <div className={s.errorMessage}>{error}</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SelectFieldInForm
|
@@ -50,6 +50,7 @@ export { default as RecommendedRecipes} from './RecommendedRecipes/RecommendedRe
|
||||
export { default as LayoutCheckout} from './LayoutCheckout/LayoutCheckout'
|
||||
export { default as InputPasswordFiledInForm} from './InputPasswordFiledInForm/InputPasswordFiledInForm'
|
||||
export { default as InputFiledInForm} from './InputFiledInForm/InputFiledInForm'
|
||||
export { default as SelectFieldInForm} from './SelectFieldInForm/SelectFieldInForm'
|
||||
export { default as MessageCommon} from './MessageCommon/MessageCommon'
|
||||
export { default as FormForgot} from './ForgotPassword/FormForgot/FormForgot'
|
||||
export { default as FormResetPassword} from './ForgotPassword/FormResetPassword/FormResetPassword'
|
||||
|
@@ -15,8 +15,7 @@ const query = gql`
|
||||
|
||||
const useGetActiveOrder = () => {
|
||||
const { data, ...rest } = useSWR<ActiveOrderQuery>([query], gglFetcher)
|
||||
return { order: data?.activeOrder ? normalizeCart(data!.activeOrder) : null, ...rest }
|
||||
|
||||
return { order: data?.activeOrder ? normalizeCart(data!.activeOrder) : null, ...rest }
|
||||
}
|
||||
|
||||
export default useGetActiveOrder
|
||||
|
@@ -1,2 +1,3 @@
|
||||
export { default as useSetCustomerForOrder } from './useSetCustomerForOrder'
|
||||
export { default as useSetOrderShippingAddress } from './useSetOrderShippingAddress'
|
||||
|
||||
|
@@ -12,7 +12,7 @@ const useSetCustomerForOrder = () => {
|
||||
const { mutate } = useGetActiveOrder()
|
||||
|
||||
const setCustomerForOrder = (input: CreateCustomerInput,
|
||||
fCallBack: (isSuccess: boolean, message?: string) => void
|
||||
fCallBack: (isSuccess: boolean, message?: CommonError) => void
|
||||
) => {
|
||||
setError(null)
|
||||
setLoading(true)
|
||||
@@ -21,17 +21,17 @@ const useSetCustomerForOrder = () => {
|
||||
variables: { input },
|
||||
})
|
||||
.then(({ data }) => {
|
||||
if (data.setCustomerForOrder.__typename === 'ActiveOrderCustomerFragment') {
|
||||
if (data.setCustomerForOrder.__typename === 'Order') {
|
||||
fCallBack(true)
|
||||
mutate()
|
||||
} else {
|
||||
fCallBack(false, data.setCustomerForOrder.message)
|
||||
fCallBack(false, data.setCustomerForOrder)
|
||||
}
|
||||
|
||||
})
|
||||
.catch((error) => {
|
||||
setError(error)
|
||||
fCallBack(false, error.message)
|
||||
fCallBack(false, error)
|
||||
})
|
||||
.finally(() => setLoading(false))
|
||||
}
|
||||
|
43
src/components/hooks/order/useSetOrderShippingAddress.tsx
Normal file
43
src/components/hooks/order/useSetOrderShippingAddress.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { CreateAddressInput, SetOrderShippingAddressMutation } from '@framework/schema'
|
||||
import { setOrderShippingAddressMutation } from '@framework/utils/mutations/set-order-shipping-address-mutation'
|
||||
import { useState } from 'react'
|
||||
import { CommonError } from 'src/domains/interfaces/CommonError'
|
||||
import rawFetcher from 'src/utils/rawFetcher'
|
||||
import { useGetActiveOrder } from '../cart'
|
||||
|
||||
|
||||
const useSetOrderShippingAddress = () => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState<CommonError | null>(null)
|
||||
const { mutate } = useGetActiveOrder()
|
||||
|
||||
const setOrderShippingAddress = (input: CreateAddressInput,
|
||||
fCallBack: (isSuccess: boolean, message?: string) => void
|
||||
) => {
|
||||
setError(null)
|
||||
setLoading(true)
|
||||
rawFetcher<SetOrderShippingAddressMutation>({
|
||||
query: setOrderShippingAddressMutation,
|
||||
variables: { input },
|
||||
})
|
||||
.then(({ data }) => {
|
||||
console.log("data: ", data)
|
||||
if (data.setOrderShippingAddress.__typename === 'Order') {
|
||||
fCallBack(true)
|
||||
mutate()
|
||||
} else {
|
||||
fCallBack(false, data.setOrderShippingAddress.message)
|
||||
}
|
||||
|
||||
})
|
||||
.catch((error) => {
|
||||
setError(error)
|
||||
fCallBack(false, error.message)
|
||||
})
|
||||
.finally(() => setLoading(false))
|
||||
}
|
||||
|
||||
return { loading, setOrderShippingAddress, error }
|
||||
}
|
||||
|
||||
export default useSetOrderShippingAddress
|
@@ -1,6 +1,7 @@
|
||||
import React, { useState } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { ButtonCommon, Logo } from 'src/components/common'
|
||||
import CheckoutCollapse from 'src/components/common/CheckoutCollapse/CheckoutCollapse'
|
||||
import { useActiveCustomer } from 'src/components/hooks/auth'
|
||||
import { useAddProductToCart, useGetActiveOrder } from 'src/components/hooks/cart'
|
||||
import { removeItem } from 'src/utils/funtion.utils'
|
||||
import { CheckOutForm } from 'src/utils/types.utils'
|
||||
@@ -12,39 +13,79 @@ interface CheckoutInfoProps {
|
||||
onViewCart: () => void
|
||||
}
|
||||
|
||||
enum CheckoutStep {
|
||||
CustomerInfo = 1,
|
||||
ShippingInfo = 2,
|
||||
PaymentInfo = 3,
|
||||
}
|
||||
|
||||
const CheckoutInfo = ({ onViewCart }: CheckoutInfoProps) => {
|
||||
const [active, setActive] = useState(1)
|
||||
const [done, setDone] = useState<number[]>([])
|
||||
const [activeStep, setActiveStep] = useState(1)
|
||||
const [doneSteps, setDoneSteps] = useState<CheckoutStep[]>([])
|
||||
const [info, setInfo] = useState<CheckOutForm>({})
|
||||
const { order } = useGetActiveOrder()
|
||||
const { customer } = useActiveCustomer()
|
||||
|
||||
const onEdit = (id: number) => {
|
||||
setActive(id)
|
||||
setDone(removeItem<number>(done, id))
|
||||
}
|
||||
useEffect(() => {
|
||||
if (customer) {
|
||||
if (!doneSteps.includes(CheckoutStep.CustomerInfo)) {
|
||||
|
||||
const onConfirm = (id: number, formInfo: CheckOutForm) => {
|
||||
if (id + 1 > formList.length) {
|
||||
console.log({ ...info, ...formInfo })
|
||||
} else {
|
||||
if (done.length > 0) {
|
||||
for (let i = id + 1; i <= formList.length; i++) {
|
||||
if (!done.includes(i)) {
|
||||
setActive(i)
|
||||
if (doneSteps.length > 0) {
|
||||
for (let i = CheckoutStep.CustomerInfo + 1; i <= Object.keys(CheckoutStep).length; i++) {
|
||||
if (!doneSteps.includes(i)) {
|
||||
setActiveStep(i)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setActiveStep(CheckoutStep.CustomerInfo + 1)
|
||||
}
|
||||
} else {
|
||||
setActive(id + 1)
|
||||
|
||||
setDoneSteps([...doneSteps, CheckoutStep.CustomerInfo])
|
||||
}
|
||||
setDone([...done, id])
|
||||
}
|
||||
setInfo({ ...info, ...formInfo })
|
||||
}, [customer, doneSteps])
|
||||
|
||||
|
||||
const onEdit = (id: CheckoutStep) => {
|
||||
setActiveStep(id)
|
||||
setDoneSteps(removeItem<number>(doneSteps, id))
|
||||
}
|
||||
|
||||
const getNote = (id: number) => {
|
||||
const updateActiveStep = (step: CheckoutStep) => {
|
||||
if (doneSteps.length > 0) {
|
||||
for (let i = step + 1; i <= Object.keys(CheckoutStep).length; i++) {
|
||||
if (!doneSteps.includes(i)) {
|
||||
setActiveStep(i)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setActiveStep(step + 1)
|
||||
}
|
||||
}
|
||||
|
||||
const onConfirm = (step: CheckoutStep) => {
|
||||
if (step + 1 > formList.length) {
|
||||
// TODO: checkout
|
||||
console.log("finish: ", order)
|
||||
} else {
|
||||
updateActiveStep(step)
|
||||
setDoneSteps([...doneSteps, step])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const getNote = (id: CheckoutStep) => {
|
||||
switch (id) {
|
||||
case 1:
|
||||
return `${info.name}, ${info.email}`
|
||||
case 2:
|
||||
case CheckoutStep.CustomerInfo:
|
||||
// console.log("order info; ", order?.customer)
|
||||
if (order?.customer) {
|
||||
return `${order?.customer?.firstName} ${order?.customer?.lastName}, ${order?.customer?.emailAddress}`
|
||||
} else if (customer) {
|
||||
return `${customer.firstName} ${customer.lastName}, ${customer.emailAddress}`
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
case CheckoutStep.ShippingInfo:
|
||||
return `${info.address}, ${info.state}, ${info.city}, ${info.code}, ${info.phone}, `
|
||||
default:
|
||||
return ""
|
||||
@@ -53,19 +94,19 @@ const CheckoutInfo = ({ onViewCart }: CheckoutInfoProps) => {
|
||||
|
||||
const formList = [
|
||||
{
|
||||
id: 1,
|
||||
id: CheckoutStep.CustomerInfo,
|
||||
title: 'Customer Information',
|
||||
form: <CustomerInfoForm onConfirm={onConfirm} id={1} />,
|
||||
form: <CustomerInfoForm onConfirm={onConfirm} id={CheckoutStep.CustomerInfo}/>,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
id: CheckoutStep.ShippingInfo,
|
||||
title: 'Shipping Information',
|
||||
form: <ShippingInfoForm onConfirm={onConfirm} id={2} />,
|
||||
form: <ShippingInfoForm onConfirm={onConfirm} id={CheckoutStep.ShippingInfo} activeStep={activeStep}/>,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
id: CheckoutStep.PaymentInfo,
|
||||
title: 'Payment Information',
|
||||
form: <PaymentInfoForm onConfirm={onConfirm} id={3} />,
|
||||
form: <PaymentInfoForm onConfirm={onConfirm} id={CheckoutStep.PaymentInfo} />,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -77,15 +118,16 @@ const CheckoutInfo = ({ onViewCart }: CheckoutInfoProps) => {
|
||||
}
|
||||
const handleAddToCartCallback = (isSuccess: boolean, message?: string) => {
|
||||
// console.log("after create order: ", isSuccess, message)
|
||||
|
||||
}
|
||||
|
||||
const {order} = useGetActiveOrder()
|
||||
|
||||
return (
|
||||
<div className={s.warpper}>
|
||||
{/* TODO: remove */}
|
||||
<ButtonCommon onClick={createOrder}>test create order</ButtonCommon>
|
||||
<ButtonCommon onClick={createOrder}>test get active order</ButtonCommon>
|
||||
<ButtonCommon onClick={createOrder}>test get activeStep order</ButtonCommon>
|
||||
|
||||
|
||||
<div className={s.title}>
|
||||
<Logo />
|
||||
<div className={s.viewCart} onClick={onViewCart}>View cart</div>
|
||||
@@ -95,11 +137,12 @@ const CheckoutInfo = ({ onViewCart }: CheckoutInfoProps) => {
|
||||
return <CheckoutCollapse
|
||||
key={item.title}
|
||||
id={item.id}
|
||||
visible={item.id === active}
|
||||
visible={item.id === activeStep}
|
||||
title={item.title}
|
||||
onEditClick={onEdit}
|
||||
isEdit={done.includes(item.id)}
|
||||
isEdit={doneSteps.includes(item.id)}
|
||||
note={note}
|
||||
disableEdit={customer && item.id === CheckoutStep.CustomerInfo}
|
||||
>
|
||||
{item.form}
|
||||
</CheckoutCollapse>
|
||||
|
@@ -1,16 +1,22 @@
|
||||
import { Form, Formik } from 'formik'
|
||||
import React, { useRef } from 'react'
|
||||
import React, { 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'
|
||||
import { useModalCommon } from 'src/components/hooks'
|
||||
import { useSetCustomerForOrder } from 'src/components/hooks/order'
|
||||
import { ErrorCode } from 'src/domains/enums/ErrorCode'
|
||||
import { CommonError } from 'src/domains/interfaces/CommonError'
|
||||
import { LANGUAGE } from 'src/utils/language.utils'
|
||||
import { CustomInputCommon } from 'src/utils/type.utils'
|
||||
import { CheckOutForm } from 'src/utils/types.utils'
|
||||
import * as Yup from 'yup'
|
||||
import ChekoutNotePolicy from '../ChekoutNotePolicy/ChekoutNotePolicy'
|
||||
import s from './CustomerInfoForm.module.scss'
|
||||
import ModalConfirmLogin from './ModalConfirmLogin/ModalConfirmLogin'
|
||||
interface Props {
|
||||
isHide: boolean
|
||||
onSwitch: () => void
|
||||
id: number
|
||||
onConfirm: (id: number) => void
|
||||
}
|
||||
|
||||
const displayingErrorMessagesSchema = Yup.object().shape({
|
||||
@@ -19,32 +25,36 @@ const displayingErrorMessagesSchema = Yup.object().shape({
|
||||
emailAddress: Yup.string().email(LANGUAGE.MESSAGE.INVALID_EMAIL).required(LANGUAGE.MESSAGE.REQUIRED),
|
||||
})
|
||||
|
||||
const CustomerInfoForm = ({ onSwitch, isHide }: Props) => {
|
||||
const CustomerInfoForm = ({ id, onConfirm }: Props) => {
|
||||
const firstNameRef = useRef<CustomInputCommon>(null)
|
||||
const emailRef = useRef<CustomInputCommon>(null)
|
||||
const { setCustomerForOrder, loading } = useSetCustomerForOrder()
|
||||
const { showMessageError } = useMessage()
|
||||
const [emailAddress, setEmailAddress] = useState<string>('')
|
||||
const { visible: visibleModalConfirmLogin, closeModal: closeModalConfirmLogin, openModal: openModalConfirmLogin } = useModalCommon({ initialValue: false })
|
||||
const { visible: visibleModalAuthen, closeModal: closeModalAuthen, openModal: openModalAuthen } = useModalCommon({ initialValue: false })
|
||||
|
||||
|
||||
const handleSubmit = (values: { firstName: string, lastName: string, emailAddress: string }) => {
|
||||
console.log('on submit: ', values)
|
||||
const { firstName, lastName, emailAddress } = values
|
||||
setEmailAddress(emailAddress)
|
||||
setCustomerForOrder({ firstName, lastName, emailAddress }, onSubmitCalBack)
|
||||
// onConfirm &&
|
||||
// onConfirm(id, {
|
||||
// name: nameRef?.current?.getValue().toString(),
|
||||
// email: emailRef.current?.getValue().toString(),
|
||||
// })
|
||||
}
|
||||
const onSubmitCalBack = (isSuccess: boolean, msg?: string) => {
|
||||
const onSubmitCalBack = (isSuccess: boolean, error?: CommonError) => {
|
||||
// TODO:
|
||||
console.log("result: ", isSuccess, msg)
|
||||
if (isSuccess) {
|
||||
|
||||
onConfirm(id)
|
||||
} else {
|
||||
console.log("error here")
|
||||
showMessageError(msg)
|
||||
if (error?.errorCode === ErrorCode.EmailAddressConflictError) {
|
||||
// show modal common
|
||||
openModalConfirmLogin()
|
||||
} else {
|
||||
showMessageError(error?.message)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
const handleCloseModalConfirmLogin = () => {
|
||||
closeModalConfirmLogin()
|
||||
openModalAuthen()
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -58,7 +68,6 @@ const CustomerInfoForm = ({ onSwitch, isHide }: Props) => {
|
||||
}}
|
||||
validationSchema={displayingErrorMessagesSchema}
|
||||
onSubmit={handleSubmit}
|
||||
|
||||
>
|
||||
{({ errors, touched, isValid, submitForm }) => (
|
||||
<Form className="u-form">
|
||||
@@ -90,7 +99,6 @@ const CustomerInfoForm = ({ onSwitch, isHide }: Props) => {
|
||||
<InputFiledInForm
|
||||
name="emailAddress"
|
||||
placeholder="Email Address"
|
||||
ref={emailRef}
|
||||
error={
|
||||
touched.emailAddress && errors.emailAddress
|
||||
? errors.emailAddress.toString()
|
||||
@@ -111,6 +119,8 @@ const CustomerInfoForm = ({ onSwitch, isHide }: Props) => {
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
<ModalConfirmLogin visible={visibleModalConfirmLogin} closeModal={closeModalConfirmLogin} handleOk={handleCloseModalConfirmLogin} email={emailAddress} />
|
||||
<ModalAuthenticate visible={visibleModalAuthen} closeModal={closeModalAuthen} initialEmail={emailAddress} disableRedirect={true} />
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import { ModalConfirm } from 'src/components/common';
|
||||
import { LANGUAGE } from 'src/utils/language.utils';
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
closeModal: () => void
|
||||
handleOk: () => void
|
||||
email: string
|
||||
}
|
||||
|
||||
const ModalConfirmLogin = ({ visible, closeModal, handleOk, email }: Props) => {
|
||||
return (
|
||||
<div>
|
||||
<ModalConfirm
|
||||
visible={visible}
|
||||
onClose={closeModal}
|
||||
onOk={handleOk}
|
||||
okText={LANGUAGE.BUTTON_LABEL.SIGNIN}
|
||||
cancelText="Change email address"
|
||||
>
|
||||
<div>
|
||||
<p> Account already exists for email {email} </p>
|
||||
<p>Please signin to continue or use another email</p>
|
||||
</div>
|
||||
</ModalConfirm>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModalConfirmLogin;
|
@@ -1,17 +1,37 @@
|
||||
import React, { useRef } from 'react'
|
||||
import { ButtonCommon, Inputcommon, SelectCommon } from 'src/components/common'
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { ButtonCommon, Inputcommon, InputFiledInForm, SelectCommon, SelectFieldInForm } from 'src/components/common'
|
||||
import s from './ShippingInfoForm.module.scss'
|
||||
import Link from 'next/link'
|
||||
import { CustomInputCommon } from 'src/utils/type.utils'
|
||||
import { Shipping } from 'src/components/icons'
|
||||
import { CheckOutForm } from 'src/utils/types.utils'
|
||||
import { Form, Formik } from 'formik'
|
||||
import { LANGUAGE } from 'src/utils/language.utils'
|
||||
import * as Yup from 'yup'
|
||||
import ChekoutNotePolicy from '../ChekoutNotePolicy/ChekoutNotePolicy'
|
||||
import { useSetOrderShippingAddress } from 'src/components/hooks/order'
|
||||
import { useMessage } from 'src/components/contexts'
|
||||
import { COUNTRY_CODE } from 'src/domains/data/countryCode'
|
||||
|
||||
interface ShippingInfoFormProps {
|
||||
onConfirm?: (id:number,formInfo:CheckOutForm)=>void
|
||||
id:number
|
||||
id: number
|
||||
activeStep: number
|
||||
onConfirm: (id: number) => void
|
||||
|
||||
}
|
||||
|
||||
const option = [
|
||||
|
||||
const displayingErrorMessagesSchema = Yup.object().shape({
|
||||
streetLine1: Yup.string().required(LANGUAGE.MESSAGE.REQUIRED),
|
||||
city: Yup.string().required(LANGUAGE.MESSAGE.REQUIRED),
|
||||
province: Yup.string().required(LANGUAGE.MESSAGE.REQUIRED),
|
||||
postalCode: Yup.number().required(LANGUAGE.MESSAGE.REQUIRED),
|
||||
countryCode: Yup.string().required(LANGUAGE.MESSAGE.REQUIRED),
|
||||
phoneNumber: Yup.string().required(LANGUAGE.MESSAGE.REQUIRED),
|
||||
|
||||
})
|
||||
|
||||
const provinceOptions = [
|
||||
{
|
||||
name: 'Hồ Chí Minh',
|
||||
value: 'Hồ Chí Minh',
|
||||
@@ -22,77 +42,147 @@ const option = [
|
||||
},
|
||||
]
|
||||
|
||||
const ShippingInfoForm = ({onConfirm,id}: ShippingInfoFormProps) => {
|
||||
const ShippingInfoForm = ({ onConfirm, id, activeStep }: ShippingInfoFormProps) => {
|
||||
const addressRef = useRef<CustomInputCommon>(null)
|
||||
const cityRef = useRef<CustomInputCommon>(null)
|
||||
const stateRef = useRef<CustomInputCommon>(null)
|
||||
const codeRef = useRef<CustomInputCommon>(null)
|
||||
const phoneRef = useRef<CustomInputCommon>(null)
|
||||
const handleConfirmClick = () => {
|
||||
onConfirm && onConfirm(id,{
|
||||
address: addressRef?.current?.getValue().toString(),
|
||||
city: cityRef.current?.getValue().toString(),
|
||||
state: stateRef?.current?.getValue().toString(),
|
||||
code: Number(codeRef.current?.getValue()),
|
||||
phone: Number(phoneRef?.current?.getValue()),
|
||||
})
|
||||
const { setOrderShippingAddress } = useSetOrderShippingAddress()
|
||||
const { showMessageError } = useMessage()
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
addressRef.current?.focus()
|
||||
}, 500);
|
||||
}, [activeStep])
|
||||
|
||||
const handleSubmit = (values: any) => {
|
||||
console.log("values: ", values)
|
||||
setOrderShippingAddress(values, onSubmitCalBack)
|
||||
|
||||
// onConfirm && onConfirm(id)
|
||||
}
|
||||
|
||||
const onSubmitCalBack = (isSuccess: boolean, msg?: string) => {
|
||||
if (isSuccess) {
|
||||
onConfirm(id)
|
||||
} else {
|
||||
showMessageError(msg)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className={s.warpper}>
|
||||
<div className={s.body}>
|
||||
<Inputcommon
|
||||
type="text"
|
||||
placeholder="Street Address"
|
||||
ref={addressRef}
|
||||
/>
|
||||
<Inputcommon type="text" placeholder="City" ref={cityRef} />
|
||||
<div className={s.line}>
|
||||
<SelectCommon options={option} type="custom" size="large">State</SelectCommon>
|
||||
<Inputcommon type="text" placeholder="Zip Code" ref={codeRef} />
|
||||
</div>
|
||||
<Inputcommon
|
||||
type="text"
|
||||
placeholder="Phone (delivery contact)"
|
||||
ref={phoneRef}
|
||||
/>
|
||||
<div className={s.method}>
|
||||
<div className={s.left}>
|
||||
<div className={s.icon}>
|
||||
<Shipping/>
|
||||
</div>
|
||||
<div className={s.name}>
|
||||
Standard Delivery Method
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.right}>
|
||||
<div className={s.price}>
|
||||
Free
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.bottom}>
|
||||
<div className={s.note}>
|
||||
By clicking continue you agree to Casper's{' '}
|
||||
{
|
||||
<Link href="#">
|
||||
<strong>terms and conditions</strong>
|
||||
</Link>
|
||||
}{' '}
|
||||
and{' '}
|
||||
{
|
||||
<Link href="#">
|
||||
<strong>privacy policy </strong>
|
||||
</Link>
|
||||
}
|
||||
.
|
||||
</div>
|
||||
<div className={s.button}>
|
||||
<ButtonCommon onClick={handleConfirmClick}>
|
||||
Continue to Payment
|
||||
</ButtonCommon>
|
||||
</div>
|
||||
<Formik
|
||||
initialValues={
|
||||
{
|
||||
streetLine1: '',
|
||||
city: '',
|
||||
province: '',
|
||||
postalCode: '',
|
||||
countryCode: '',
|
||||
phoneNumber: '',
|
||||
}}
|
||||
validationSchema={displayingErrorMessagesSchema}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{({ errors, touched, isValid, submitForm }) => (
|
||||
<Form className="u-form">
|
||||
<div className="body">
|
||||
<div className={s.input}>
|
||||
<InputFiledInForm
|
||||
name="streetLine1"
|
||||
placeholder="Address"
|
||||
ref={addressRef}
|
||||
error={
|
||||
touched.streetLine1 && errors.streetLine1
|
||||
? errors.streetLine1.toString()
|
||||
: ''
|
||||
}
|
||||
isShowIconSuccess={touched.streetLine1 && !errors.streetLine1}
|
||||
/>
|
||||
</div>
|
||||
<div className="line">
|
||||
<div className={s.input}>
|
||||
<InputFiledInForm
|
||||
name="city"
|
||||
placeholder="City"
|
||||
error={
|
||||
touched.city && errors.city
|
||||
? errors.city.toString()
|
||||
: ''
|
||||
}
|
||||
isShowIconSuccess={touched.city && !errors.city}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={s.input}>
|
||||
<SelectFieldInForm
|
||||
options={provinceOptions}
|
||||
name="province"
|
||||
placeholder="Province"
|
||||
error={
|
||||
touched.province && errors.province
|
||||
? errors.province.toString()
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="line">
|
||||
<div className={s.input}>
|
||||
<InputFiledInForm
|
||||
name="postalCode"
|
||||
placeholder="Postal Code"
|
||||
error={
|
||||
touched.postalCode && errors.postalCode
|
||||
? errors.postalCode.toString()
|
||||
: ''
|
||||
}
|
||||
isShowIconSuccess={touched.postalCode && !errors.postalCode}
|
||||
/>
|
||||
</div>
|
||||
<div className={s.input}>
|
||||
<SelectFieldInForm
|
||||
options={COUNTRY_CODE}
|
||||
keyNameOption={['name', 'alpha-2']}
|
||||
keyValueOption="alpha-2"
|
||||
name="countryCode"
|
||||
placeholder="Country"
|
||||
nameSeperator=" - "
|
||||
error={
|
||||
touched.countryCode && errors.countryCode
|
||||
? errors.countryCode.toString()
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={s.inputPhoneNumber}>
|
||||
<InputFiledInForm
|
||||
name="phoneNumber"
|
||||
placeholder="Phone number"
|
||||
error={
|
||||
touched.phoneNumber && errors.phoneNumber
|
||||
? errors.phoneNumber.toString()
|
||||
: ''
|
||||
}
|
||||
isShowIconSuccess={touched.phoneNumber && !errors.phoneNumber}
|
||||
onEnter={isValid ? submitForm : undefined}
|
||||
/>
|
||||
</div>
|
||||
<div className={s.bottom}>
|
||||
<ChekoutNotePolicy />
|
||||
{/* <ButtonCommon HTMLType='submit' loading={loading} size="large"> */}
|
||||
<ButtonCommon HTMLType='submit' size="large">
|
||||
Continue to Payment
|
||||
</ButtonCommon>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
@import "../../../../styles/utilities";
|
||||
.warrper{
|
||||
@apply flex w-full h-full absolute;
|
||||
@apply flex w-full;
|
||||
.right {
|
||||
display: none;
|
||||
@screen lg {
|
||||
|
3227
src/domains/data/CountryCode.ts
Normal file
3227
src/domains/data/CountryCode.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -34,8 +34,9 @@ export interface BlogProps {
|
||||
}
|
||||
|
||||
export interface CheckOutForm {
|
||||
name?: string
|
||||
email?: string
|
||||
firstName?: string
|
||||
lastName?: string
|
||||
emailAddress?: string
|
||||
address?: string
|
||||
city?: string
|
||||
state?: string
|
||||
|
Reference in New Issue
Block a user