add useAddShippingAddress hook + add phone and region fields in address

Signed-off-by: Loan Laux <loan@outgrow.io>
This commit is contained in:
Loan Laux 2021-08-05 19:01:24 +02:00
parent fb1fa865ac
commit 2943168803
No known key found for this signature in database
GPG Key ID: AF9E9BD6548AD52E
11 changed files with 229 additions and 22 deletions

View File

@ -144,12 +144,21 @@ const PaymentMethodView: FC = () => {
</div> </div>
</div> </div>
<div className={s.fieldset}> <div className={s.fieldset}>
<label className={s.label}>Country/Region</label> <label className={s.label}>Region</label>
<input
className={s.input}
name='region'
onChange={updateAddressData}
value={address.region}
/>
</div>
<div className={s.fieldset}>
<label className={s.label}>Country</label>
<select <select
className={s.select} className={s.select}
name='countryOrRegion' name='country'
onChange={updateAddressData} onChange={updateAddressData}
value={address.countryOrRegion} value={address.country}
> >
{countries.map((country) => ( {countries.map((country) => (
<option <option

View File

@ -1,13 +1,22 @@
import { FC, useState } from 'react' import { FC } from 'react'
import cn from 'classnames' import cn from 'classnames'
import s from './ShippingView.module.css' import s from './ShippingView.module.css'
import Button from '@components/ui/Button' import Button from '@components/ui/Button'
import { useUI } from '@components/ui/context' import { useUI } from '@components/ui/context'
import SidebarLayout from '@components/common/SidebarLayout' import SidebarLayout from '@components/common/SidebarLayout'
import countries from '@lib/countries' import countries from '@lib/countries'
import useAddShippingAddress from '@framework/cart/use-add-shipping-address'
const PaymentMethodView: FC = () => { const PaymentMethodView: FC = () => {
const { paymentMethodDetails, setShippingAddress, setSidebarView, setUseBillingAddressForShipping, shippingAddress, useBillingAddressForShipping } = useUI() const addShippingAddress = useAddShippingAddress()
const {
paymentMethodDetails,
setShippingAddress,
setSidebarView,
setUseBillingAddressForShipping,
shippingAddress,
useBillingAddressForShipping,
} = useUI()
const handleUseBillingAddressForShipping = (event) => { const handleUseBillingAddressForShipping = (event) => {
setUseBillingAddressForShipping(event.target.value === 'true') setUseBillingAddressForShipping(event.target.value === 'true')
@ -21,7 +30,7 @@ const PaymentMethodView: FC = () => {
addressLine2: paymentMethodDetails.address?.addressLine2, addressLine2: paymentMethodDetails.address?.addressLine2,
postalCode: paymentMethodDetails.address?.postalCode, postalCode: paymentMethodDetails.address?.postalCode,
city: paymentMethodDetails.address?.city, city: paymentMethodDetails.address?.city,
countryOrRegion: paymentMethodDetails.address?.countryOrRegion, country: paymentMethodDetails.address?.country,
}) })
} }
} }
@ -32,7 +41,11 @@ const PaymentMethodView: FC = () => {
}) })
return ( return (
<SidebarLayout handleBack={() => setSidebarView('CHECKOUT_VIEW')}> <SidebarLayout handleBack={async () => {
// add shipping address to cart
await addShippingAddress({ address: shippingAddress })
setSidebarView('CHECKOUT_VIEW')
}}>
<div className="px-4 sm:px-6 flex-1"> <div className="px-4 sm:px-6 flex-1">
<h2 className="pt-1 pb-8 text-2xl font-semibold tracking-wide cursor-pointer inline-block"> <h2 className="pt-1 pb-8 text-2xl font-semibold tracking-wide cursor-pointer inline-block">
Shipping Shipping
@ -87,6 +100,17 @@ const PaymentMethodView: FC = () => {
/> />
</div> </div>
</div> </div>
<div className={s.fieldset}>
<label className={s.label}>Phone Number</label>
<input
className={s.input}
name='phone'
onChange={updateAddressData}
value={shippingAddress.phone}
disabled={useBillingAddressForShipping}
type='tel'
/>
</div>
<div className={s.fieldset}> <div className={s.fieldset}>
<label className={s.label}>Company (Optional)</label> <label className={s.label}>Company (Optional)</label>
<input <input
@ -140,12 +164,22 @@ const PaymentMethodView: FC = () => {
</div> </div>
</div> </div>
<div className={s.fieldset}> <div className={s.fieldset}>
<label className={s.label}>Country/Region</label> <label className={s.label}>Region</label>
<input
className={s.input}
name='region'
onChange={updateAddressData}
value={shippingAddress.region}
disabled={useBillingAddressForShipping}
/>
</div>
<div className={s.fieldset}>
<label className={s.label}>Country</label>
<select <select
className={s.select} className={s.select}
name="countryOrRegion" name="country"
onChange={updateAddressData} onChange={updateAddressData}
value={shippingAddress.countryOrRegion} value={shippingAddress.country}
disabled={useBillingAddressForShipping} disabled={useBillingAddressForShipping}
> >

View File

@ -28,7 +28,8 @@ export const initialState = {
addressLine2: '', addressLine2: '',
postalCode: '', postalCode: '',
city: '', city: '',
countryOrRegion: '', country: '',
region: '',
}, },
paymentMethod: null, paymentMethod: null,
}, },
@ -40,7 +41,9 @@ export const initialState = {
addressLine2: '', addressLine2: '',
postalCode: '', postalCode: '',
city: '', city: '',
countryOrRegion: '', country: '',
region: '',
phone: '',
}, },
useBillingAddressForShipping: true, useBillingAddressForShipping: true,
} }

View File

@ -0,0 +1,20 @@
import { useHook, useMutationHook } from '../utils/use-hook'
import { mutationFetcher } from '../utils/default-fetcher'
import type { HookFetcherFn, MutationHook } from '../utils/types'
import type { AddShippingAddressHook } from '../types/cart'
import type { Provider } from '..'
export type UseAddShippingAddress<
H extends MutationHook<AddShippingAddressHook<any>> = MutationHook<AddShippingAddressHook>
> = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<AddShippingAddressHook> = mutationFetcher
const fn = (provider: Provider) => provider.cart?.useAddShippingAddress!
const useRemoveItem: UseAddShippingAddress = (input) => {
const hook = useHook(fn)
return useMutationHook({ fetcher, ...hook })(input)
}
export default useRemoveItem

View File

@ -26,6 +26,7 @@ export type Provider = CommerceConfig & {
cart?: { cart?: {
useCart?: SWRHook<Cart.GetCartHook> useCart?: SWRHook<Cart.GetCartHook>
useAddItem?: MutationHook<Cart.AddItemHook> useAddItem?: MutationHook<Cart.AddItemHook>
useAddShippingAddress?: MutationHook<Cart.AddShippingAddressHook>
useUpdateItem?: MutationHook<Cart.UpdateItemHook> useUpdateItem?: MutationHook<Cart.UpdateItemHook>
useRemoveItem?: MutationHook<Cart.RemoveItemHook> useRemoveItem?: MutationHook<Cart.RemoveItemHook>
} }

View File

@ -96,6 +96,20 @@ export type CartItemBody = {
} }
} }
export type Address = {
cardholderName: string
firstName: string
lastName: string
company: string
addressLine1: string
addressLine2: string
postalCode: string
city: string
region: string
country: string
phone: string
}
/** /**
* Hooks schema * Hooks schema
*/ */
@ -104,6 +118,7 @@ export type CartTypes = {
cart?: Cart cart?: Cart
item: LineItem item: LineItem
itemBody: CartItemBody itemBody: CartItemBody
address: Address
} }
export type CartHooks<T extends CartTypes = CartTypes> = { export type CartHooks<T extends CartTypes = CartTypes> = {
@ -144,6 +159,14 @@ export type RemoveItemHook<T extends CartTypes = CartTypes> = {
actionInput: { id: string } actionInput: { id: string }
} }
export type AddShippingAddressHook<T extends CartTypes = CartTypes> = {
data: T['cart'] | null
input: { address?: T['address'] }
fetcherInput: { address: Address }
body: { itemId: string }
actionInput: { id: string }
}
/** /**
* API Schema * API Schema
*/ */

View File

@ -95,14 +95,13 @@ const addItem: CartEndpoint['handlers']['addItem'] = async ({
} }
if (anonymousCartToken && reactionCustomerToken) { if (anonymousCartToken && reactionCustomerToken) {
console.log('reconciliating carts')( console.log('reconciliating carts')
({ _id: cartId } = await reconcileCarts({ ({ _id: cartId } = await reconcileCarts({
config, config,
cartId, cartId,
anonymousCartToken, anonymousCartToken,
reactionCustomerToken, reactionCustomerToken,
})) }))
)
// Clear the anonymous cart token cookie and update cart ID cookie // Clear the anonymous cart token cookie and update cart ID cookie
res.setHeader('Set-Cookie', [ res.setHeader('Set-Cookie', [
@ -135,7 +134,7 @@ const addItem: CartEndpoint['handlers']['addItem'] = async ({
headers: { headers: {
...authorizationHeaderParam, ...authorizationHeaderParam,
}, },
} },
) )
console.log('updatedCart', updatedCart) console.log('updatedCart', updatedCart)

View File

@ -0,0 +1,97 @@
import { useCallback } from 'react'
import type {
MutationHookContext,
HookFetcherContext,
} from '@commerce/utils/types'
import { ValidationError } from '@commerce/utils/errors'
import useAddShippingAddress, {
AddShippingAddressInput as AddShippingAddressInputBase,
UseAddShippingAddress,
} from '@commerce/cart/use-add-shipping-address'
import useCart from './use-cart'
import {
setShippingAddressOnCartMutation,
getAnonymousCartToken,
getAnonymousCartId,
normalizeCart,
} from '../utils'
import { Cart, LineItem } from '../types'
import { Mutation, MutationUpdateCartItemsQuantityArgs } from '../schema'
import { AddShippingAddressBody } from '@commerce/types'
export type AddShippingAddressFn<T = any> = T extends LineItem
? (input?: AddShippingAddressInput<T>) => Promise<Cart | null>
: (input: AddShippingAddressInput<T>) => Promise<Cart | null>
export type AddShippingAddressInput<T = any> = T extends LineItem
? Partial<AddShippingAddressInputBase>
: AddShippingAddressInputBase
export default useAddShippingAddress as UseAddShippingAddress<typeof handler>
export const handler = {
fetchOptions: {
query: setShippingAddressOnCartMutation,
},
async fetcher({
input: { address },
options,
fetch,
}: HookFetcherContext<AddShippingAddressBody>) {
const { setShippingAddressOnCart } = await fetch<
Mutation,
MutationUpdateCartItemsQuantityArgs
>({
...options,
variables: {
input: {
cartId: getAnonymousCartId(),
cartToken: getAnonymousCartToken(),
address: {
address1: address.addressLine1,
address2: address.addressLine2,
city: address.city,
company: address.company,
country: address.country,
firstName: address.firstName,
lastName: address.lastName,
fullName: `${address.firstName} ${address.lastName}`,
phone: address.phone,
postal: address.postalCode,
region: address.region,
},
},
},
})
return normalizeCart(setShippingAddressOnCart?.cart)
},
useHook: ({
fetch,
}: MutationHookContext<Cart | null, AddShippingAddressBody>) => <
T extends LineItem | undefined = undefined
>(
ctx: { address?: T } = {}
) => {
const { mutate } = useCart()
const addShippingAddress: AddShippingAddressFn<LineItem> = async (input) => {
const address = input?.address ?? ctx.address
console.log('addShippingAddress input', input)
if (!address) {
throw new ValidationError({
message: 'Invalid input used for this operation',
})
}
const data = await fetch({ input: { address } })
await mutate(data, false)
return data
}
return useCallback(addShippingAddress as AddShippingAddressFn<T>, [fetch, mutate])
},
}

View File

@ -5,6 +5,7 @@ import {
import { handler as useCart } from './cart/use-cart' import { handler as useCart } from './cart/use-cart'
import { handler as useAddItem } from './cart/use-add-item' import { handler as useAddItem } from './cart/use-add-item'
import { handler as useAddShippingAddress } from './cart/use-add-shipping-address'
import { handler as useUpdateItem } from './cart/use-update-item' import { handler as useUpdateItem } from './cart/use-update-item'
import { handler as useRemoveItem } from './cart/use-remove-item' import { handler as useRemoveItem } from './cart/use-remove-item'
@ -22,7 +23,13 @@ export const reactionCommerceProvider = {
anonymousCartTokenCookie: REACTION_ANONYMOUS_CART_TOKEN_COOKIE, anonymousCartTokenCookie: REACTION_ANONYMOUS_CART_TOKEN_COOKIE,
cartIdCookie: REACTION_CART_ID_COOKIE, cartIdCookie: REACTION_CART_ID_COOKIE,
fetcher, fetcher,
cart: { useCart, useAddItem, useUpdateItem, useRemoveItem }, cart: {
useCart,
useAddItem,
useAddShippingAddress,
useUpdateItem,
useRemoveItem,
},
customer: { useCustomer }, customer: { useCustomer },
products: { useSearch }, products: { useSearch },
auth: { useLogin, useLogout, useSignup }, auth: { useLogin, useLogout, useSignup },

View File

@ -4,4 +4,5 @@ export { default as createCartMutation } from './create-cart'
export { default as authenticateMutation } from './authenticate' export { default as authenticateMutation } from './authenticate'
export { default as logoutMutation } from './logout' export { default as logoutMutation } from './logout'
export { default as removeCartItemsMutation } from './remove-cart-items' export { default as removeCartItemsMutation } from './remove-cart-items'
export { default as setShippingAddressOnCartMutation } from './set-shipping-address-on-cart'
export { default as updateCartItemsQuantityMutation } from './update-cart-items-quantity' export { default as updateCartItemsQuantityMutation } from './update-cart-items-quantity'

View File

@ -0,0 +1,13 @@
import { cartPayloadFragment } from '@framework/utils/queries/get-cart-query'
const setShippingAddressOnCartMutation = `
mutation setShippingAddressOnCartMutation($input: SetShippingAddressOnCartInput!) {
setShippingAddressOnCart(input: $input) {
cart {
${cartPayloadFragment}
}
}
}
`
export default setShippingAddressOnCartMutation