mirror of
https://github.com/vercel/commerce.git
synced 2025-06-08 09:16:58 +00:00
add payment method state management
Signed-off-by: Loan Laux <loan@outgrow.io>
This commit is contained in:
parent
b79a3cd13d
commit
0ab3a973a1
@ -1,9 +1,8 @@
|
|||||||
import cn from 'classnames'
|
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import CartItem from '@components/cart/CartItem'
|
import CartItem from '@components/cart/CartItem'
|
||||||
import { Button, Text } from '@components/ui'
|
import { Button, Text } from '@components/ui'
|
||||||
import { useUI } from '@components/ui/context'
|
import { initialState as initialUIState, useUI } from '@components/ui/context'
|
||||||
import useCart from '@framework/cart/use-cart'
|
import useCart from '@framework/cart/use-cart'
|
||||||
import usePrice from '@framework/product/use-price'
|
import usePrice from '@framework/product/use-price'
|
||||||
import ShippingWidget from '../ShippingWidget'
|
import ShippingWidget from '../ShippingWidget'
|
||||||
@ -12,7 +11,7 @@ import SidebarLayout from '@components/common/SidebarLayout'
|
|||||||
import s from './CheckoutSidebarView.module.css'
|
import s from './CheckoutSidebarView.module.css'
|
||||||
|
|
||||||
const CheckoutSidebarView: FC = () => {
|
const CheckoutSidebarView: FC = () => {
|
||||||
const { setSidebarView } = useUI()
|
const { paymentMethodDetails, setPaymentMethodDetails, setSidebarView } = useUI()
|
||||||
const { data } = useCart()
|
const { data } = useCart()
|
||||||
|
|
||||||
const { price: subTotal } = usePrice(
|
const { price: subTotal } = usePrice(
|
||||||
@ -38,11 +37,19 @@ const CheckoutSidebarView: FC = () => {
|
|||||||
<Text variant="sectionHeading">Checkout</Text>
|
<Text variant="sectionHeading">Checkout</Text>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<PaymentWidget onClick={() => setSidebarView('PAYMENT_VIEW')} />
|
<PaymentWidget
|
||||||
|
onClick={() => {
|
||||||
|
if (paymentMethodDetails.paymentMethod) {
|
||||||
|
setPaymentMethodDetails(initialUIState.paymentMethodDetails)
|
||||||
|
} else {
|
||||||
|
setSidebarView('PAYMENT_VIEW')
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<ShippingWidget onClick={() => setSidebarView('SHIPPING_VIEW')} />
|
<ShippingWidget onClick={() => setSidebarView('SHIPPING_VIEW')} />
|
||||||
|
|
||||||
<ul className={s.lineItemsList}>
|
<ul className={s.lineItemsList}>
|
||||||
{data!.lineItems.map((item: any) => (
|
{data!.lineItems?.map((item: any) => (
|
||||||
<CartItem
|
<CartItem
|
||||||
key={item.id}
|
key={item.id}
|
||||||
item={item}
|
item={item}
|
||||||
|
@ -13,16 +13,52 @@ import s from './PaymentMethodView.module.css'
|
|||||||
import SidebarLayout from '@components/common/SidebarLayout'
|
import SidebarLayout from '@components/common/SidebarLayout'
|
||||||
|
|
||||||
const PaymentMethodView: FC = () => {
|
const PaymentMethodView: FC = () => {
|
||||||
const { setSidebarView } = useUI()
|
const { paymentMethodDetails, setPaymentMethodDetails, setSidebarView } = useUI()
|
||||||
|
const { address } = paymentMethodDetails
|
||||||
|
const stripe = useStripe()
|
||||||
|
const elements = useElements()
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!stripe || !elements) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const card = elements.getElement(CardNumberElement)
|
||||||
|
|
||||||
|
const { error, paymentMethod } = await stripe.createPaymentMethod({
|
||||||
|
type: 'card',
|
||||||
|
card
|
||||||
|
})
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
setPaymentMethodDetails({ address, paymentMethod })
|
||||||
|
setSidebarView('CHECKOUT_VIEW')
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateAddressData = ({ target }: any) => setPaymentMethodDetails({
|
||||||
|
...paymentMethodDetails,
|
||||||
|
address: {
|
||||||
|
...paymentMethodDetails.address,
|
||||||
|
[target.name]: target.value,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarLayout handleBack={() => setSidebarView('CHECKOUT_VIEW')}>
|
<SidebarLayout handleBack={handleSubmit}>
|
||||||
<div className="px-4 sm:px-6 flex-1">
|
<div className="px-4 sm:px-6 flex-1">
|
||||||
<Text variant="sectionHeading"> Payment Method</Text>
|
<Text variant="sectionHeading"> Payment Method</Text>
|
||||||
<div>
|
<div>
|
||||||
<div className={s.fieldset}>
|
<div className={s.fieldset}>
|
||||||
<label className={s.label}>Cardholder Name</label>
|
<label className={s.label}>Cardholder Name</label>
|
||||||
<input className={s.input} />
|
<input
|
||||||
|
className={s.input}
|
||||||
|
name='cardholderName'
|
||||||
|
onChange={updateAddressData}
|
||||||
|
value={address.cardholderName}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-3 grid-flow-row grid-cols-12">
|
<div className="grid gap-3 grid-flow-row grid-cols-12">
|
||||||
<div className={cn(s.fieldset, 'col-span-7')}>
|
<div className={cn(s.fieldset, 'col-span-7')}>
|
||||||
@ -42,38 +78,78 @@ const PaymentMethodView: FC = () => {
|
|||||||
<div className="grid gap-3 grid-flow-row grid-cols-12">
|
<div className="grid gap-3 grid-flow-row grid-cols-12">
|
||||||
<div className={cn(s.fieldset, 'col-span-6')}>
|
<div className={cn(s.fieldset, 'col-span-6')}>
|
||||||
<label className={s.label}>First Name</label>
|
<label className={s.label}>First Name</label>
|
||||||
<input className={s.input} />
|
<input
|
||||||
|
className={s.input}
|
||||||
|
name='firstName'
|
||||||
|
onChange={updateAddressData}
|
||||||
|
value={address.firstName}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={cn(s.fieldset, 'col-span-6')}>
|
<div className={cn(s.fieldset, 'col-span-6')}>
|
||||||
<label className={s.label}>Last Name</label>
|
<label className={s.label}>Last Name</label>
|
||||||
<input className={s.input} />
|
<input
|
||||||
|
className={s.input}
|
||||||
|
name='lastName'
|
||||||
|
onChange={updateAddressData}
|
||||||
|
value={address.lastName}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={s.fieldset}>
|
<div className={s.fieldset}>
|
||||||
<label className={s.label}>Company (Optional)</label>
|
<label className={s.label}>Company (Optional)</label>
|
||||||
<input className={s.input} />
|
<input
|
||||||
|
className={s.input}
|
||||||
|
name='company'
|
||||||
|
onChange={updateAddressData}
|
||||||
|
value={address.company}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={s.fieldset}>
|
<div className={s.fieldset}>
|
||||||
<label className={s.label}>Street and House Number</label>
|
<label className={s.label}>Street and House Number</label>
|
||||||
<input className={s.input} />
|
<input
|
||||||
|
className={s.input}
|
||||||
|
name='addressLine1'
|
||||||
|
onChange={updateAddressData}
|
||||||
|
value={address.addressLine1}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={s.fieldset}>
|
<div className={s.fieldset}>
|
||||||
<label className={s.label}>Apartment, Suite, Etc. (Optional)</label>
|
<label className={s.label}>Apartment, Suite, Etc. (Optional)</label>
|
||||||
<input className={s.input} />
|
<input
|
||||||
|
className={s.input}
|
||||||
|
name='addressLine2'
|
||||||
|
onChange={updateAddressData}
|
||||||
|
value={address.addressLine2}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-3 grid-flow-row grid-cols-12">
|
<div className="grid gap-3 grid-flow-row grid-cols-12">
|
||||||
<div className={cn(s.fieldset, 'col-span-6')}>
|
<div className={cn(s.fieldset, 'col-span-6')}>
|
||||||
<label className={s.label}>Postal Code</label>
|
<label className={s.label}>Postal Code</label>
|
||||||
<input className={s.input} />
|
<input
|
||||||
|
className={s.input}
|
||||||
|
name='postalCode'
|
||||||
|
onChange={updateAddressData}
|
||||||
|
value={address.postalCode}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={cn(s.fieldset, 'col-span-6')}>
|
<div className={cn(s.fieldset, 'col-span-6')}>
|
||||||
<label className={s.label}>City</label>
|
<label className={s.label}>City</label>
|
||||||
<input className={s.input} />
|
<input
|
||||||
|
className={s.input}
|
||||||
|
name='city'
|
||||||
|
onChange={updateAddressData}
|
||||||
|
value={address.city}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={s.fieldset}>
|
<div className={s.fieldset}>
|
||||||
<label className={s.label}>Country/Region</label>
|
<label className={s.label}>Country/Region</label>
|
||||||
<select className={s.select}>
|
<select
|
||||||
|
className={s.select}
|
||||||
|
name='countryOrRegion'
|
||||||
|
onChange={updateAddressData}
|
||||||
|
value={address.countryOrRegion}
|
||||||
|
>
|
||||||
<option>Hong Kong</option>
|
<option>Hong Kong</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import s from './PaymentWidget.module.css'
|
import s from './PaymentWidget.module.css'
|
||||||
import { ChevronRight, CreditCard } from '@components/icons'
|
import { ChevronRight, CreditCard, Trash } from '@components/icons'
|
||||||
|
import { useUI } from '@components/ui/context'
|
||||||
|
|
||||||
interface ComponentProps {
|
interface ComponentProps {
|
||||||
onClick?: () => any
|
onClick?: () => any
|
||||||
}
|
}
|
||||||
|
|
||||||
const PaymentWidget: FC<ComponentProps> = ({ onClick }) => {
|
const PaymentWidget: FC<ComponentProps> = ({ onClick }) => {
|
||||||
|
const { paymentMethodDetails } = useUI()
|
||||||
|
|
||||||
|
console.log(paymentMethodDetails)
|
||||||
|
|
||||||
/* Shipping Address
|
/* Shipping Address
|
||||||
Only available with checkout set to true -
|
Only available with checkout set to true -
|
||||||
This means that the provider does offer checkout functionality. */
|
This means that the provider does offer checkout functionality. */
|
||||||
@ -14,13 +19,23 @@ const PaymentWidget: FC<ComponentProps> = ({ onClick }) => {
|
|||||||
<div onClick={onClick} className={s.root}>
|
<div onClick={onClick} className={s.root}>
|
||||||
<div className="flex flex-1 items-center">
|
<div className="flex flex-1 items-center">
|
||||||
<CreditCard className="w-5 flex" />
|
<CreditCard className="w-5 flex" />
|
||||||
<span className="ml-5 text-sm text-center font-medium">
|
<div className="ml-5 text-sm text-left font-medium">
|
||||||
Add Payment Method
|
{paymentMethodDetails?.paymentMethod ?
|
||||||
</span>
|
<span className="text-left">
|
||||||
{/* <span>VISA #### #### #### 2345</span> */}
|
{paymentMethodDetails.address.cardholderName}<br/>
|
||||||
|
{paymentMethodDetails.paymentMethod.card.brand.toUpperCase()} **** **** **** {paymentMethodDetails.paymentMethod.card.last4}
|
||||||
|
</span>
|
||||||
|
:
|
||||||
|
<span> Add Payment Method</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<ChevronRight />
|
{paymentMethodDetails?.paymentMethod ?
|
||||||
|
<Trash />
|
||||||
|
:
|
||||||
|
<ChevronRight />
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -84,7 +84,8 @@ const SidebarView: FC<{ sidebarView: string; closeSidebar(): any }> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SidebarUI: FC = () => {
|
const SidebarUI: FC = () => {
|
||||||
const { displaySidebar, closeSidebar, sidebarView } = useUI()
|
const { displaySidebar, closeSidebar, sidebarView, paymentMethodDetails } = useUI()
|
||||||
|
console.log("paymentMethodDetails", paymentMethodDetails)
|
||||||
return displaySidebar ? (
|
return displaySidebar ? (
|
||||||
<SidebarView sidebarView={sidebarView} closeSidebar={closeSidebar} />
|
<SidebarView sidebarView={sidebarView} closeSidebar={closeSidebar} />
|
||||||
) : null
|
) : null
|
||||||
|
@ -8,15 +8,30 @@ export interface State {
|
|||||||
sidebarView: string
|
sidebarView: string
|
||||||
modalView: string
|
modalView: string
|
||||||
userAvatar: string
|
userAvatar: string
|
||||||
|
paymentMethodDetails: PAYMENT_METHOD_DETAILS
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState = {
|
export const initialState = {
|
||||||
displaySidebar: false,
|
displaySidebar: false,
|
||||||
displayDropdown: false,
|
displayDropdown: false,
|
||||||
displayModal: false,
|
displayModal: false,
|
||||||
modalView: 'LOGIN_VIEW',
|
modalView: 'LOGIN_VIEW',
|
||||||
sidebarView: 'CART_VIEW',
|
sidebarView: 'CART_VIEW',
|
||||||
userAvatar: '',
|
userAvatar: '',
|
||||||
|
paymentMethodDetails: {
|
||||||
|
address: {
|
||||||
|
cardholderName: '',
|
||||||
|
firstName: '',
|
||||||
|
lastName: '',
|
||||||
|
company: '',
|
||||||
|
addressLine1: '',
|
||||||
|
addressLine2: '',
|
||||||
|
postalCode: '',
|
||||||
|
city: '',
|
||||||
|
countryOrRegion: '',
|
||||||
|
},
|
||||||
|
paymentMethod: null,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
type Action =
|
type Action =
|
||||||
@ -50,6 +65,10 @@ type Action =
|
|||||||
type: 'SET_USER_AVATAR'
|
type: 'SET_USER_AVATAR'
|
||||||
value: string
|
value: string
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: 'SET_PAYMENT_METHOD_DETAILS',
|
||||||
|
paymentMethodDetails: PAYMENT_METHOD_DETAILS
|
||||||
|
}
|
||||||
|
|
||||||
type MODAL_VIEWS =
|
type MODAL_VIEWS =
|
||||||
| 'SIGNUP_VIEW'
|
| 'SIGNUP_VIEW'
|
||||||
@ -60,6 +79,11 @@ type MODAL_VIEWS =
|
|||||||
|
|
||||||
type SIDEBAR_VIEWS = 'CART_VIEW' | 'CHECKOUT_VIEW' | 'PAYMENT_METHOD_VIEW'
|
type SIDEBAR_VIEWS = 'CART_VIEW' | 'CHECKOUT_VIEW' | 'PAYMENT_METHOD_VIEW'
|
||||||
|
|
||||||
|
type PAYMENT_METHOD_DETAILS = {
|
||||||
|
address: object,
|
||||||
|
paymentMethod: object,
|
||||||
|
}
|
||||||
|
|
||||||
export const UIContext = React.createContext<State | any>(initialState)
|
export const UIContext = React.createContext<State | any>(initialState)
|
||||||
|
|
||||||
UIContext.displayName = 'UIContext'
|
UIContext.displayName = 'UIContext'
|
||||||
@ -121,6 +145,12 @@ function uiReducer(state: State, action: Action) {
|
|||||||
userAvatar: action.value,
|
userAvatar: action.value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case 'SET_PAYMENT_METHOD_DETAILS': {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
paymentMethodDetails: action.paymentMethodDetails
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,6 +210,11 @@ export const UIProvider: FC = (props) => {
|
|||||||
[dispatch]
|
[dispatch]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const setPaymentMethodDetails = useCallback(
|
||||||
|
(paymentMethodDetails: PAYMENT_METHOD_DETAILS) => dispatch({ type: 'SET_PAYMENT_METHOD_DETAILS', paymentMethodDetails }),
|
||||||
|
[dispatch]
|
||||||
|
)
|
||||||
|
|
||||||
const value = useMemo(
|
const value = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
...state,
|
...state,
|
||||||
@ -192,6 +227,7 @@ export const UIProvider: FC = (props) => {
|
|||||||
openModal,
|
openModal,
|
||||||
closeModal,
|
closeModal,
|
||||||
setModalView,
|
setModalView,
|
||||||
|
setPaymentMethodDetails,
|
||||||
setSidebarView,
|
setSidebarView,
|
||||||
setUserAvatar,
|
setUserAvatar,
|
||||||
}),
|
}),
|
||||||
|
@ -32,7 +32,7 @@ export const handler: SWRHook<
|
|||||||
Object.create(response, {
|
Object.create(response, {
|
||||||
isEmpty: {
|
isEmpty: {
|
||||||
get() {
|
get() {
|
||||||
return (response.data?.lineItems.length ?? 0) <= 0
|
return (response.data?.lineItems?.length ?? 0) <= 0
|
||||||
},
|
},
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user