add payment method state management

Signed-off-by: Loan Laux <loan@outgrow.io>
This commit is contained in:
Loan Laux 2021-08-02 17:25:21 +02:00
parent b79a3cd13d
commit 0ab3a973a1
No known key found for this signature in database
GPG Key ID: AF9E9BD6548AD52E
6 changed files with 162 additions and 27 deletions

View File

@ -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}

View File

@ -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>

View File

@ -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
</span>
{/* <span>VISA #### #### #### 2345</span> */}
<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> Add Payment Method</span>
}
</div>
</div>
<div>
<ChevronRight />
{paymentMethodDetails?.paymentMethod ?
<Trash />
:
<ChevronRight />
}
</div>
</div>
)

View File

@ -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

View File

@ -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,
}),

View File

@ -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,
},