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

View File

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

View File

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

View File

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

View File

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

View File

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