mirror of
https://github.com/vercel/commerce.git
synced 2025-06-08 01:06:59 +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 { FC } from 'react'
|
||||
import CartItem from '@components/cart/CartItem'
|
||||
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 usePrice from '@framework/product/use-price'
|
||||
import ShippingWidget from '../ShippingWidget'
|
||||
@ -12,7 +11,7 @@ import SidebarLayout from '@components/common/SidebarLayout'
|
||||
import s from './CheckoutSidebarView.module.css'
|
||||
|
||||
const CheckoutSidebarView: FC = () => {
|
||||
const { setSidebarView } = useUI()
|
||||
const { paymentMethodDetails, setPaymentMethodDetails, setSidebarView } = useUI()
|
||||
const { data } = useCart()
|
||||
|
||||
const { price: subTotal } = usePrice(
|
||||
@ -38,11 +37,19 @@ const CheckoutSidebarView: FC = () => {
|
||||
<Text variant="sectionHeading">Checkout</Text>
|
||||
</Link>
|
||||
|
||||
<PaymentWidget onClick={() => setSidebarView('PAYMENT_VIEW')} />
|
||||
<PaymentWidget
|
||||
onClick={() => {
|
||||
if (paymentMethodDetails.paymentMethod) {
|
||||
setPaymentMethodDetails(initialUIState.paymentMethodDetails)
|
||||
} else {
|
||||
setSidebarView('PAYMENT_VIEW')
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ShippingWidget onClick={() => setSidebarView('SHIPPING_VIEW')} />
|
||||
|
||||
<ul className={s.lineItemsList}>
|
||||
{data!.lineItems.map((item: any) => (
|
||||
{data!.lineItems?.map((item: any) => (
|
||||
<CartItem
|
||||
key={item.id}
|
||||
item={item}
|
||||
|
@ -13,16 +13,52 @@ import s from './PaymentMethodView.module.css'
|
||||
import SidebarLayout from '@components/common/SidebarLayout'
|
||||
|
||||
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 (
|
||||
<SidebarLayout handleBack={() => setSidebarView('CHECKOUT_VIEW')}>
|
||||
<SidebarLayout handleBack={handleSubmit}>
|
||||
<div className="px-4 sm:px-6 flex-1">
|
||||
<Text variant="sectionHeading"> Payment Method</Text>
|
||||
<div>
|
||||
<div className={s.fieldset}>
|
||||
<label className={s.label}>Cardholder Name</label>
|
||||
<input className={s.input} />
|
||||
<input
|
||||
className={s.input}
|
||||
name='cardholderName'
|
||||
onChange={updateAddressData}
|
||||
value={address.cardholderName}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-3 grid-flow-row grid-cols-12">
|
||||
<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={cn(s.fieldset, 'col-span-6')}>
|
||||
<label className={s.label}>First Name</label>
|
||||
<input className={s.input} />
|
||||
<input
|
||||
className={s.input}
|
||||
name='firstName'
|
||||
onChange={updateAddressData}
|
||||
value={address.firstName}
|
||||
/>
|
||||
</div>
|
||||
<div className={cn(s.fieldset, 'col-span-6')}>
|
||||
<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 className={s.fieldset}>
|
||||
<label className={s.label}>Company (Optional)</label>
|
||||
<input className={s.input} />
|
||||
<input
|
||||
className={s.input}
|
||||
name='company'
|
||||
onChange={updateAddressData}
|
||||
value={address.company}
|
||||
/>
|
||||
</div>
|
||||
<div className={s.fieldset}>
|
||||
<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 className={s.fieldset}>
|
||||
<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 className="grid gap-3 grid-flow-row grid-cols-12">
|
||||
<div className={cn(s.fieldset, 'col-span-6')}>
|
||||
<label className={s.label}>Postal Code</label>
|
||||
<input className={s.input} />
|
||||
<input
|
||||
className={s.input}
|
||||
name='postalCode'
|
||||
onChange={updateAddressData}
|
||||
value={address.postalCode}
|
||||
/>
|
||||
</div>
|
||||
<div className={cn(s.fieldset, 'col-span-6')}>
|
||||
<label className={s.label}>City</label>
|
||||
<input className={s.input} />
|
||||
<input
|
||||
className={s.input}
|
||||
name='city'
|
||||
onChange={updateAddressData}
|
||||
value={address.city}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.fieldset}>
|
||||
<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>
|
||||
</select>
|
||||
</div>
|
||||
|
@ -1,12 +1,17 @@
|
||||
import { FC } from 'react'
|
||||
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 {
|
||||
onClick?: () => any
|
||||
}
|
||||
|
||||
const PaymentWidget: FC<ComponentProps> = ({ onClick }) => {
|
||||
const { paymentMethodDetails } = useUI()
|
||||
|
||||
console.log(paymentMethodDetails)
|
||||
|
||||
/* Shipping Address
|
||||
Only available with checkout set to true -
|
||||
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 className="flex flex-1 items-center">
|
||||
<CreditCard className="w-5 flex" />
|
||||
<span className="ml-5 text-sm text-center font-medium">
|
||||
Add Payment Method
|
||||
<div className="ml-5 text-sm text-left font-medium">
|
||||
{paymentMethodDetails?.paymentMethod ?
|
||||
<span className="text-left">
|
||||
{paymentMethodDetails.address.cardholderName}<br/>
|
||||
{paymentMethodDetails.paymentMethod.card.brand.toUpperCase()} **** **** **** {paymentMethodDetails.paymentMethod.card.last4}
|
||||
</span>
|
||||
{/* <span>VISA #### #### #### 2345</span> */}
|
||||
:
|
||||
<span> Add Payment Method</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{paymentMethodDetails?.paymentMethod ?
|
||||
<Trash />
|
||||
:
|
||||
<ChevronRight />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -84,7 +84,8 @@ const SidebarView: FC<{ sidebarView: string; closeSidebar(): any }> = ({
|
||||
}
|
||||
|
||||
const SidebarUI: FC = () => {
|
||||
const { displaySidebar, closeSidebar, sidebarView } = useUI()
|
||||
const { displaySidebar, closeSidebar, sidebarView, paymentMethodDetails } = useUI()
|
||||
console.log("paymentMethodDetails", paymentMethodDetails)
|
||||
return displaySidebar ? (
|
||||
<SidebarView sidebarView={sidebarView} closeSidebar={closeSidebar} />
|
||||
) : null
|
||||
|
@ -8,15 +8,30 @@ export interface State {
|
||||
sidebarView: string
|
||||
modalView: string
|
||||
userAvatar: string
|
||||
paymentMethodDetails: PAYMENT_METHOD_DETAILS
|
||||
}
|
||||
|
||||
const initialState = {
|
||||
export const initialState = {
|
||||
displaySidebar: false,
|
||||
displayDropdown: false,
|
||||
displayModal: false,
|
||||
modalView: 'LOGIN_VIEW',
|
||||
sidebarView: 'CART_VIEW',
|
||||
userAvatar: '',
|
||||
paymentMethodDetails: {
|
||||
address: {
|
||||
cardholderName: '',
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
company: '',
|
||||
addressLine1: '',
|
||||
addressLine2: '',
|
||||
postalCode: '',
|
||||
city: '',
|
||||
countryOrRegion: '',
|
||||
},
|
||||
paymentMethod: null,
|
||||
},
|
||||
}
|
||||
|
||||
type Action =
|
||||
@ -50,6 +65,10 @@ type Action =
|
||||
type: 'SET_USER_AVATAR'
|
||||
value: string
|
||||
}
|
||||
| {
|
||||
type: 'SET_PAYMENT_METHOD_DETAILS',
|
||||
paymentMethodDetails: PAYMENT_METHOD_DETAILS
|
||||
}
|
||||
|
||||
type MODAL_VIEWS =
|
||||
| 'SIGNUP_VIEW'
|
||||
@ -60,6 +79,11 @@ type MODAL_VIEWS =
|
||||
|
||||
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)
|
||||
|
||||
UIContext.displayName = 'UIContext'
|
||||
@ -121,6 +145,12 @@ function uiReducer(state: State, action: Action) {
|
||||
userAvatar: action.value,
|
||||
}
|
||||
}
|
||||
case 'SET_PAYMENT_METHOD_DETAILS': {
|
||||
return {
|
||||
...state,
|
||||
paymentMethodDetails: action.paymentMethodDetails
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -180,6 +210,11 @@ export const UIProvider: FC = (props) => {
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const setPaymentMethodDetails = useCallback(
|
||||
(paymentMethodDetails: PAYMENT_METHOD_DETAILS) => dispatch({ type: 'SET_PAYMENT_METHOD_DETAILS', paymentMethodDetails }),
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
...state,
|
||||
@ -192,6 +227,7 @@ export const UIProvider: FC = (props) => {
|
||||
openModal,
|
||||
closeModal,
|
||||
setModalView,
|
||||
setPaymentMethodDetails,
|
||||
setSidebarView,
|
||||
setUserAvatar,
|
||||
}),
|
||||
|
@ -32,7 +32,7 @@ export const handler: SWRHook<
|
||||
Object.create(response, {
|
||||
isEmpty: {
|
||||
get() {
|
||||
return (response.data?.lineItems.length ?? 0) <= 0
|
||||
return (response.data?.lineItems?.length ?? 0) <= 0
|
||||
},
|
||||
enumerable: true,
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user