mirror of
https://github.com/vercel/commerce.git
synced 2025-07-22 12:24:18 +00:00
🎨 styles: shipping method
:%s
This commit is contained in:
11
framework/vendure/schema.d.ts
vendored
11
framework/vendure/schema.d.ts
vendored
@@ -365,8 +365,17 @@ export type Address = Node & {
|
||||
customFields?: Maybe<Scalars['JSON']>
|
||||
}
|
||||
|
||||
export type SetShippingMethodMutationVariables = Exact<{
|
||||
id: Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type SetShippingMethodMutation = {
|
||||
setOrderShippingMethod:
|
||||
| TestOrderFragmentFragment
|
||||
| Pick<OrderModificationError, 'errorCode' | 'message'>
|
||||
| Pick<IneligibleShippingMethodError, 'errorCode' | 'message'>
|
||||
| Pick<NoActiveOrderError, 'errorCode' | 'message'>;
|
||||
};
|
||||
|
||||
export type Asset = Node & {
|
||||
__typename?: 'Asset'
|
||||
|
@@ -0,0 +1,35 @@
|
||||
export const setShippingMethodMutation = /* GraphQL */ `
|
||||
mutation SetShippingMethod($id: ID!) {
|
||||
setOrderShippingMethod(shippingMethodId: $id) {
|
||||
...Cart
|
||||
...ErrorResult
|
||||
__typename
|
||||
}
|
||||
}
|
||||
|
||||
fragment Cart on Order {
|
||||
id
|
||||
code
|
||||
state
|
||||
active
|
||||
shippingLines {
|
||||
priceWithTax
|
||||
shippingMethod {
|
||||
id
|
||||
code
|
||||
name
|
||||
description
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
|
||||
__typename
|
||||
}
|
||||
|
||||
fragment ErrorResult on ErrorResult {
|
||||
errorCode
|
||||
message
|
||||
__typename
|
||||
}
|
||||
`
|
@@ -2,4 +2,4 @@ export { default as useSetCustomerForOrder } from './useSetCustomerForOrder'
|
||||
export { default as useSetOrderShippingAddress } from './useSetOrderShippingAddress'
|
||||
export { default as useApplyCouponCode } from './useApplyCouponCode'
|
||||
export { default as useAvailableCountries } from './useAvailableCountries'
|
||||
|
||||
export { default as useSetOrderShippingMethod } from './useSetOrderShippingMethod'
|
||||
|
41
src/components/hooks/order/useSetOrderShippingMethod.tsx
Normal file
41
src/components/hooks/order/useSetOrderShippingMethod.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { SetShippingMethodMutation } from '@framework/schema'
|
||||
import { setShippingMethodMutation } from '@framework/utils/mutations/set-order-shipping-method-mutation'
|
||||
import { useState } from 'react'
|
||||
import { CommonError } from 'src/domains/interfaces/CommonError'
|
||||
import rawFetcher from 'src/utils/rawFetcher'
|
||||
import { useGetActiveOrder } from '../cart'
|
||||
|
||||
|
||||
const useSetOrderShippingMethod = () => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState<CommonError | null>(null)
|
||||
const { mutate } = useGetActiveOrder()
|
||||
|
||||
const setOrderShippingMethod = (id: string,
|
||||
fCallBack: (isSuccess: boolean, message?: string) => void
|
||||
) => {
|
||||
setError(null)
|
||||
setLoading(true)
|
||||
rawFetcher<SetShippingMethodMutation>({
|
||||
query: setShippingMethodMutation,
|
||||
variables: { id },
|
||||
})
|
||||
.then(({ data }) => {
|
||||
if (data.setOrderShippingMethod.__typename === 'Order') {
|
||||
fCallBack(true)
|
||||
mutate()
|
||||
} else {
|
||||
fCallBack(false, data.setOrderShippingMethod.message)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
setError(error)
|
||||
fCallBack(false, error.message)
|
||||
})
|
||||
.finally(() => setLoading(false))
|
||||
}
|
||||
|
||||
return { loading, setOrderShippingMethod, error }
|
||||
}
|
||||
|
||||
export default useSetOrderShippingMethod
|
@@ -9,6 +9,7 @@ import PaymentInfoForm from './components/PaymentInfoForm/PaymentInfoForm'
|
||||
import ShippingInfoForm from './components/ShippingInfoForm/ShippingInfoForm'
|
||||
interface CheckoutInfoProps {
|
||||
onViewCart: () => void
|
||||
currency?: string
|
||||
}
|
||||
|
||||
enum CheckoutStep {
|
||||
@@ -17,7 +18,7 @@ enum CheckoutStep {
|
||||
PaymentInfo = 3,
|
||||
}
|
||||
|
||||
const CheckoutInfo = ({ onViewCart }: CheckoutInfoProps) => {
|
||||
const CheckoutInfo = ({ onViewCart, currency = "" }: CheckoutInfoProps) => {
|
||||
const [activeStep, setActiveStep] = useState(1)
|
||||
const [doneSteps, setDoneSteps] = useState<CheckoutStep[]>([])
|
||||
const { order } = useGetActiveOrder()
|
||||
@@ -101,7 +102,7 @@ const CheckoutInfo = ({ onViewCart }: CheckoutInfoProps) => {
|
||||
{
|
||||
id: CheckoutStep.ShippingInfo,
|
||||
title: 'Shipping Information',
|
||||
form: <ShippingInfoForm onConfirm={onConfirm} id={CheckoutStep.ShippingInfo} activeStep={activeStep} />,
|
||||
form: <ShippingInfoForm onConfirm={onConfirm} id={CheckoutStep.ShippingInfo} activeStep={activeStep} currency={currency} />,
|
||||
},
|
||||
{
|
||||
id: CheckoutStep.PaymentInfo,
|
||||
|
@@ -18,7 +18,7 @@ const ChekoutNotePolicy = memo(() => {
|
||||
{
|
||||
<Link href={ROUTE.PRIVACY_POLICY}>
|
||||
<a>
|
||||
<strong>privacy policy </strong>
|
||||
<strong>privacy policy</strong>
|
||||
</a>
|
||||
</Link>
|
||||
}
|
||||
|
@@ -24,22 +24,5 @@
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
.method{
|
||||
width: 100%;
|
||||
height: 5.6rem;
|
||||
padding: 1.6rem;
|
||||
border-radius: 0.8rem;
|
||||
@apply flex justify-between items-center border border-solid border-line bg-gray;
|
||||
.left{
|
||||
@apply flex;
|
||||
.name{
|
||||
margin-left: 1.6rem;
|
||||
color: var(--text-active);
|
||||
}
|
||||
}
|
||||
.price{
|
||||
font-weight: bold;
|
||||
color: var(--text-active);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -3,17 +3,19 @@ import React, { useEffect, useRef } from 'react'
|
||||
import { ButtonCommon, InputFiledInForm, SelectFieldInForm } from 'src/components/common'
|
||||
import { useMessage } from 'src/components/contexts'
|
||||
import { useAvailableCountries, useSetOrderShippingAddress } from 'src/components/hooks/order'
|
||||
import { Shipping } from 'src/components/icons'
|
||||
import { LANGUAGE } from 'src/utils/language.utils'
|
||||
import { CustomInputCommon } from 'src/utils/type.utils'
|
||||
import * as Yup from 'yup'
|
||||
import ChekoutNotePolicy from '../ChekoutNotePolicy/ChekoutNotePolicy'
|
||||
import ShippingMethod from '../ShippingMethod/ShippingMethod'
|
||||
import s from './ShippingInfoForm.module.scss'
|
||||
|
||||
interface ShippingInfoFormProps {
|
||||
id: number
|
||||
activeStep: number
|
||||
onConfirm: (id: number) => void
|
||||
currency: string
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -31,6 +33,7 @@ const displayingErrorMessagesSchema = Yup.object().shape({
|
||||
const DEFAULT_COUNTRY_CODE = 'MY'
|
||||
const DEFAULT_PROVINCE = 'Sabah'
|
||||
|
||||
// TODO: update data
|
||||
const provinceOptions = [
|
||||
{
|
||||
name: 'Hồ Chí Minh',
|
||||
@@ -46,7 +49,7 @@ const provinceOptions = [
|
||||
},
|
||||
]
|
||||
|
||||
const ShippingInfoForm = ({ onConfirm, id, activeStep }: ShippingInfoFormProps) => {
|
||||
const ShippingInfoForm = ({ onConfirm, id, activeStep , currency}: ShippingInfoFormProps) => {
|
||||
const addressRef = useRef<CustomInputCommon>(null)
|
||||
const { setOrderShippingAddress, loading } = useSetOrderShippingAddress()
|
||||
const { showMessageError } = useMessage()
|
||||
@@ -59,10 +62,7 @@ const ShippingInfoForm = ({ onConfirm, id, activeStep }: ShippingInfoFormProps)
|
||||
}, [activeStep])
|
||||
|
||||
const handleSubmit = (values: any) => {
|
||||
console.log("values: ", values)
|
||||
setOrderShippingAddress(values, onSubmitCalBack)
|
||||
|
||||
// onConfirm && onConfirm(id)
|
||||
}
|
||||
|
||||
const onSubmitCalBack = (isSuccess: boolean, msg?: string) => {
|
||||
@@ -176,21 +176,7 @@ const ShippingInfoForm = ({ onConfirm, id, activeStep }: ShippingInfoFormProps)
|
||||
onEnter={isValid ? submitForm : undefined}
|
||||
/>
|
||||
</div>
|
||||
<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>
|
||||
<ShippingMethod currency={currency}/>
|
||||
<div className={s.bottom}>
|
||||
<ChekoutNotePolicy />
|
||||
<ButtonCommon HTMLType='submit' loading={loading} size="large">
|
||||
|
@@ -0,0 +1,39 @@
|
||||
.shippingMethod {
|
||||
@apply relative;
|
||||
.method {
|
||||
@apply w-full flex justify-between items-center border border-solid border-line bg-gray cursor-pointer;
|
||||
height: 5.6rem;
|
||||
padding: 1.6rem;
|
||||
border-radius: 0.8rem;
|
||||
.left {
|
||||
@apply flex;
|
||||
.name {
|
||||
margin-left: 1.6rem;
|
||||
color: var(--text-active);
|
||||
}
|
||||
}
|
||||
.price {
|
||||
font-weight: bold;
|
||||
color: var(--text-active);
|
||||
}
|
||||
}
|
||||
|
||||
.options {
|
||||
@apply absolute transition-all duration-200 overflow-hidden;
|
||||
transform: translateX(-100%);
|
||||
opacity: 0;
|
||||
margin-top: 0.8rem;
|
||||
top: 6rem;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background: var(--white);
|
||||
border: 1px solid var(--border-line);
|
||||
border-radius: 0.8rem;
|
||||
&.show {
|
||||
z-index: 10;
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
position: initial;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,94 @@
|
||||
import { ShippingMethodQuote } from '@framework/schema'
|
||||
import classNames from 'classnames'
|
||||
import React, { memo, useState } from 'react'
|
||||
import { useMessage } from 'src/components/contexts'
|
||||
import { useSetOrderShippingMethod } from 'src/components/hooks/order'
|
||||
import { Shipping } from 'src/components/icons'
|
||||
import s from './ShippingMethod.module.scss'
|
||||
import ShippingMethodItem from './ShippingMethodItem/ShippingMethodItem'
|
||||
|
||||
const MOCKUP_DATA = [
|
||||
{
|
||||
"id": "1",
|
||||
"name": "Standard Shipping",
|
||||
"description": "",
|
||||
"price": 0,
|
||||
"priceWithTax": 0,
|
||||
"code": "standard-shipping"
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"name": "Express Shipping",
|
||||
"description": "",
|
||||
"price": 1000,
|
||||
"priceWithTax": 1000,
|
||||
"code": "express-shipping"
|
||||
}
|
||||
]
|
||||
interface Props {
|
||||
currency: string
|
||||
}
|
||||
|
||||
const ShippingMethod = memo(({ currency }: Props) => {
|
||||
const { setOrderShippingMethod } = useSetOrderShippingMethod()
|
||||
const [selectedValue, setSelectedValue] = useState<ShippingMethodQuote>(MOCKUP_DATA[0])
|
||||
const { showMessageError } = useMessage()
|
||||
const [isShowOptions, setIsShowOptions] = useState<boolean>(false)
|
||||
|
||||
const onChange = (id: string) => {
|
||||
const newValue = MOCKUP_DATA.find(item => item.id === id)
|
||||
if (newValue) {
|
||||
setSelectedValue(newValue)
|
||||
if (newValue?.id) {
|
||||
setOrderShippingMethod(newValue?.id, onSubmitCalBack)
|
||||
}
|
||||
}
|
||||
setIsShowOptions(false)
|
||||
}
|
||||
|
||||
const onSubmitCalBack = (isSuccess: boolean, msg?: string) => {
|
||||
if (!isSuccess) {
|
||||
showMessageError(msg)
|
||||
}
|
||||
}
|
||||
|
||||
const onCollapseOptions = () => {
|
||||
setIsShowOptions(!isShowOptions)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className={s.shippingMethod}>
|
||||
<div className={s.method} onClick={onCollapseOptions}>
|
||||
<div className={s.left}>
|
||||
<div className={s.icon}>
|
||||
<Shipping />
|
||||
</div>
|
||||
<div className={s.name}>
|
||||
{selectedValue.name}
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.right}>
|
||||
<div className={s.price}>
|
||||
{selectedValue.price ? `${selectedValue.price / 100} ${currency}` : "Free"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classNames(s.options, { [s.show]: isShowOptions })}>
|
||||
<ul>
|
||||
{MOCKUP_DATA.map(item => <ShippingMethodItem
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
name={item.name}
|
||||
price={item.price}
|
||||
currency={currency}
|
||||
onSelect={onChange}
|
||||
/>)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
ShippingMethod.displayName = 'ShippingMethod'
|
||||
export default ShippingMethod
|
@@ -0,0 +1,19 @@
|
||||
.shippingMethodItem {
|
||||
@apply flex justify-between items-center cursor-pointer transition-all duration-200;
|
||||
width: 100%;
|
||||
padding: 1.6rem;
|
||||
&:hover {
|
||||
@apply bg-gray;
|
||||
}
|
||||
.left {
|
||||
@apply flex;
|
||||
.name {
|
||||
margin-left: 1.6rem;
|
||||
color: var(--text-active);
|
||||
}
|
||||
}
|
||||
.price {
|
||||
font-weight: bold;
|
||||
color: var(--text-active);
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
import React, { memo } from 'react'
|
||||
import s from './ShippingMethodItem.module.scss'
|
||||
|
||||
interface Props {
|
||||
id: string
|
||||
name: string
|
||||
price: number
|
||||
currency: string
|
||||
onSelect: (id: string) => void
|
||||
}
|
||||
|
||||
const ShippingMethodItem = memo(({ id, name, price, currency, onSelect }: Props) => {
|
||||
const handleSelect = () => {
|
||||
onSelect(id)
|
||||
}
|
||||
return (
|
||||
<li className={s.shippingMethodItem} key={id} onClick={handleSelect}>
|
||||
<div className={s.name}>
|
||||
{name}
|
||||
</div>
|
||||
<div className={s.price}>
|
||||
{price ? `${price / 100} ${currency}` : "Free"}
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
|
||||
ShippingMethodItem.displayName = 'ShippingMethodItem'
|
||||
export default ShippingMethodItem
|
@@ -24,7 +24,7 @@ const CheckoutPage = ({ }: CheckoutPageProps) => {
|
||||
return (
|
||||
<div className={s.warrper}>
|
||||
<MessageCommon messages={messages} onRemove={removeMessage} />
|
||||
<div className={s.left}><CheckoutInfo onViewCart={onViewCart} /></div>
|
||||
<div className={s.left}><CheckoutInfo onViewCart={onViewCart} currency={order?.currency.code}/></div>
|
||||
<div className={s.right}><CheckoutBill data={order} /></div>
|
||||
<div className={classNames({ [s.mobile]: true, [s.isShow]: isShow })}>
|
||||
<div className={s.modal}>
|
||||
|
Reference in New Issue
Block a user