@hanzo/commerce 4.9.0 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/add-to-cart-widget.tsx +0 -1
- package/components/buy-item/select-category-and-item-widget.tsx +2 -2
- package/components/buy-item/select-category-item-card.tsx +2 -2
- package/components/{checkout-panel/cart-accordian.tsx → cart-accordian.tsx} +21 -10
- package/components/cart-panel/cart-line-item.tsx +22 -16
- package/components/cart-panel/index.tsx +90 -34
- package/components/cart-panel/promo-code.tsx +98 -0
- package/components/category-item-radio-selector.tsx +2 -2
- package/components/category-item-scroll-selector.tsx +2 -2
- package/components/index.ts +4 -1
- package/components/item-carousel.tsx +80 -0
- package/components/{checkout-panel/steps/payment/cards/index.tsx → payment-step-form/card-icon-row.tsx} +8 -7
- package/components/{checkout-panel/steps/payment → payment-step-form}/cc-button.tsx +1 -1
- package/components/payment-step-form/contact-form.tsx +49 -0
- package/components/{checkout-panel/steps/payment → payment-step-form}/index.tsx +29 -56
- package/components/payment-step-form/methods/bank-transfer.tsx +79 -0
- package/components/payment-step-form/methods/card.tsx +237 -0
- package/components/{checkout-panel/steps/payment/pay-with-crypto.tsx → payment-step-form/methods/crypto.tsx} +10 -24
- package/components/payment-step-form/methods/index.ts +23 -0
- package/components/product-card.tsx +2 -2
- package/components/select-category-item-panel.tsx +2 -2
- package/components/{checkout-panel/steps/shipping-info.tsx → shipping-step-form.tsx} +6 -8
- package/index.ts +1 -1
- package/package.json +2 -2
- package/service/context.tsx +1 -0
- package/service/impls/standalone/standalone-service.ts +56 -4
- package/types/checkout.ts +42 -1
- package/types/commerce-service.ts +9 -1
- package/types/index.ts +2 -1
- package/types/promo.ts +10 -0
- package/util/index.ts +3 -2
- package/util/promo-codes.ts +46 -0
- package/components/cart-panel/products-carousel.tsx +0 -60
- package/components/checkout-panel/_confirm-order.tsx_unused +0 -143
- package/components/checkout-panel/close-button.tsx +0 -24
- package/components/checkout-panel/desktop.tsx +0 -46
- package/components/checkout-panel/icons/bag-icon.tsx +0 -10
- package/components/checkout-panel/index.tsx +0 -108
- package/components/checkout-panel/mobile.tsx +0 -41
- package/components/checkout-panel/steps/payment/contact-info.tsx +0 -56
- package/components/checkout-panel/steps/payment/pay-with-bank-transfer.tsx +0 -110
- package/components/checkout-panel/steps/payment/pay-with-card.tsx +0 -246
- package/components/checkout-panel/steps/thank-you.tsx +0 -18
- package/components/checkout-panel/steps/types.ts +0 -14
- /package/components/{checkout-panel/steps/payment/cards → payment-step-form/card-icons}/amex.tsx +0 -0
- /package/components/{checkout-panel/steps/payment/cards → payment-step-form/card-icons}/diners-club.tsx +0 -0
- /package/components/{checkout-panel/steps/payment/cards → payment-step-form/card-icons}/discover.tsx +0 -0
- /package/components/{checkout-panel/steps/payment/cards → payment-step-form/card-icons}/jcb.tsx +0 -0
- /package/components/{checkout-panel/steps/payment/cards → payment-step-form/card-icons}/mastercard.tsx +0 -0
- /package/components/{checkout-panel/steps/payment/cards → payment-step-form/card-icons}/visa.tsx +0 -0
- /package/components/{checkout-panel/icons → payment-step-form/crypto-icons}/btc.tsx +0 -0
- /package/components/{checkout-panel/icons → payment-step-form/crypto-icons}/eth.tsx +0 -0
- /package/components/{checkout-panel/icons → payment-step-form/crypto-icons}/usdt.tsx +0 -0
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use client'
|
|
2
|
-
|
|
3
|
-
import { useEffect, useState } from 'react'
|
|
2
|
+
import React, { useEffect, useState } from 'react'
|
|
4
3
|
import { observer } from 'mobx-react-lite'
|
|
5
4
|
|
|
6
5
|
import { zodResolver } from '@hookform/resolvers/zod'
|
|
@@ -9,22 +8,19 @@ import { useForm } from 'react-hook-form'
|
|
|
9
8
|
|
|
10
9
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@hanzo/ui/primitives'
|
|
11
10
|
import { useAuth } from '@hanzo/auth/service'
|
|
12
|
-
import PayWithCrypto from './pay-with-crypto'
|
|
13
|
-
import PayWithBankTransfer from './pay-with-bank-transfer'
|
|
14
|
-
import PayWithCard from './pay-with-card'
|
|
15
11
|
|
|
16
|
-
import { useCommerce } from '
|
|
17
|
-
import
|
|
18
|
-
import {
|
|
12
|
+
import { useCommerce } from '../../service/context'
|
|
13
|
+
import { sendFBEvent, sendGAEvent } from '../../util/analytics'
|
|
14
|
+
import type { CheckoutStepComponentProps, TransactionStatus } from '../../types'
|
|
19
15
|
|
|
20
|
-
import
|
|
16
|
+
import METHODS from './methods'
|
|
21
17
|
|
|
22
18
|
const contactFormSchema = z.object({
|
|
23
19
|
name: z.string().min(1, 'Enter your full name.'),
|
|
24
20
|
email: z.string().email(),
|
|
25
21
|
})
|
|
26
22
|
|
|
27
|
-
const
|
|
23
|
+
const PaymentStepForm: React.FC<CheckoutStepComponentProps> = observer(({
|
|
28
24
|
onDone,
|
|
29
25
|
orderId,
|
|
30
26
|
setOrderId
|
|
@@ -64,7 +60,7 @@ const Payment: React.FC<StepComponentProps> = observer(({
|
|
|
64
60
|
price: item.price,
|
|
65
61
|
quantity: item.quantity
|
|
66
62
|
})),
|
|
67
|
-
value: cmmc.
|
|
63
|
+
value: cmmc.promoAppliedCartTotal,
|
|
68
64
|
currency: 'USD',
|
|
69
65
|
payment_type: paymentInfo.paymentMethod ?? ''
|
|
70
66
|
})
|
|
@@ -73,7 +69,7 @@ const Payment: React.FC<StepComponentProps> = observer(({
|
|
|
73
69
|
id: item.sku,
|
|
74
70
|
quantity: item.quantity
|
|
75
71
|
})),
|
|
76
|
-
value: cmmc.
|
|
72
|
+
value: cmmc.promoAppliedCartTotal,
|
|
77
73
|
currency: 'USD'
|
|
78
74
|
})
|
|
79
75
|
await cmmc.updateOrderPaymentInfo(id, paymentInfo)
|
|
@@ -87,58 +83,35 @@ const Payment: React.FC<StepComponentProps> = observer(({
|
|
|
87
83
|
const tabClx = 'whitespace-normal h-full text-xs sm:text-base px-1 text-muted ' +
|
|
88
84
|
'data-[state=active]:text-accent data-[state=active]:bg-level-2 md:data-[state=active]:bg-level-3'
|
|
89
85
|
|
|
86
|
+
const disabled = transactionStatus === 'paid' || transactionStatus === 'confirmed'
|
|
87
|
+
|
|
90
88
|
return (
|
|
91
|
-
<Tabs defaultValue='card' className='w-full
|
|
89
|
+
<Tabs defaultValue='card' className='w-full'>
|
|
92
90
|
<TabsList className={groupClx}>
|
|
91
|
+
{METHODS.map(({ label, value }) => (
|
|
93
92
|
<TabsTrigger
|
|
94
|
-
value=
|
|
95
|
-
className={tabClx}
|
|
96
|
-
disabled={transactionStatus === 'paid' || transactionStatus === 'confirmed'}
|
|
97
|
-
>
|
|
98
|
-
Card
|
|
99
|
-
</TabsTrigger>
|
|
100
|
-
<TabsTrigger
|
|
101
|
-
value='crypto'
|
|
102
|
-
className={tabClx}
|
|
103
|
-
disabled={transactionStatus === 'paid' || transactionStatus === 'confirmed'}
|
|
104
|
-
>
|
|
105
|
-
Wallet
|
|
106
|
-
</TabsTrigger>
|
|
107
|
-
<TabsTrigger
|
|
108
|
-
value='bank'
|
|
93
|
+
value={value}
|
|
109
94
|
className={tabClx}
|
|
110
|
-
disabled={
|
|
95
|
+
disabled={disabled}
|
|
96
|
+
key={`tabs-${value}`}
|
|
111
97
|
>
|
|
112
|
-
|
|
98
|
+
{label}
|
|
113
99
|
</TabsTrigger>
|
|
100
|
+
))}
|
|
114
101
|
</TabsList>
|
|
115
|
-
|
|
116
|
-
<
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
onDone={onDone}
|
|
127
|
-
transactionStatus={transactionStatus}
|
|
128
|
-
setTransactionStatus={setTransactionStatus}
|
|
129
|
-
storePaymentInfo={storePaymentInfo}
|
|
130
|
-
contactForm={contactForm}
|
|
131
|
-
/>
|
|
132
|
-
</TabsContent>
|
|
133
|
-
<TabsContent value='bank'>
|
|
134
|
-
<PayWithBankTransfer
|
|
135
|
-
onDone={onDone}
|
|
136
|
-
storePaymentInfo={storePaymentInfo}
|
|
137
|
-
contactForm={contactForm}
|
|
138
|
-
/>
|
|
139
|
-
</TabsContent>
|
|
102
|
+
{METHODS.map(({Comp: PaymentMethodComp, value}) => (
|
|
103
|
+
<TabsContent value={value} key={`content-${value}`}>
|
|
104
|
+
<PaymentMethodComp
|
|
105
|
+
onDone={onDone}
|
|
106
|
+
transactionStatus={transactionStatus}
|
|
107
|
+
setTransactionStatus={setTransactionStatus}
|
|
108
|
+
storePaymentInfo={storePaymentInfo}
|
|
109
|
+
contactForm={contactForm}
|
|
110
|
+
/>
|
|
111
|
+
</TabsContent>
|
|
112
|
+
))}
|
|
140
113
|
</Tabs>
|
|
141
114
|
)
|
|
142
115
|
})
|
|
143
116
|
|
|
144
|
-
export default
|
|
117
|
+
export default PaymentStepForm
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import { Copy } from 'lucide-react'
|
|
5
|
+
|
|
6
|
+
import { Button, toast } from '@hanzo/ui/primitives'
|
|
7
|
+
|
|
8
|
+
import type { PaymentMethodComponentProps } from '../../../types'
|
|
9
|
+
|
|
10
|
+
import ContactForm from '../contact-form'
|
|
11
|
+
|
|
12
|
+
const InfoField: React.FC<{
|
|
13
|
+
label: string,
|
|
14
|
+
value: React.ReactNode,
|
|
15
|
+
copyValue: string
|
|
16
|
+
}> = ({
|
|
17
|
+
label,
|
|
18
|
+
value,
|
|
19
|
+
copyValue
|
|
20
|
+
}) => {
|
|
21
|
+
|
|
22
|
+
const copyToClipboard = (label: string, text: string) => {
|
|
23
|
+
navigator.clipboard.writeText(text)
|
|
24
|
+
toast(`${label} copied to clipboard.`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div className='flex flex-col gap-1'>
|
|
29
|
+
<p className='text-xs'>{label}</p>
|
|
30
|
+
<div className='flex items-center justify-between sm:text-lg border rounded-lg py-2 px-4'>
|
|
31
|
+
{value}
|
|
32
|
+
<Button variant='ghost' size='icon' onClick={() => copyToClipboard(label, copyValue)}>
|
|
33
|
+
<Copy className='h-4 w-4'/>
|
|
34
|
+
</Button>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const PayWithBankTransfer: React.FC<PaymentMethodComponentProps> = ({
|
|
41
|
+
onDone,
|
|
42
|
+
storePaymentInfo,
|
|
43
|
+
contactForm
|
|
44
|
+
}) => {
|
|
45
|
+
|
|
46
|
+
const payByBankTransfer = async () => {
|
|
47
|
+
contactForm.handleSubmit( async () => {
|
|
48
|
+
await storePaymentInfo({paymentMethod: 'bank-transfer'})
|
|
49
|
+
onDone()
|
|
50
|
+
})()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div className='flex flex-col gap-2 mt-6'>
|
|
55
|
+
<ContactForm form={contactForm}/>
|
|
56
|
+
<div className='w-full mx-auto max-w-[50rem]'>
|
|
57
|
+
<div className='flex flex-col gap-4 w-full'>
|
|
58
|
+
<InfoField
|
|
59
|
+
label='Beneficiary Bank'
|
|
60
|
+
value={<div>Bank of America<br/>NA 222 Broadway<br/>New York, New York. 10038</div>}
|
|
61
|
+
copyValue='Bank of America, NA 222 Broadway, New York, New York. 10038'
|
|
62
|
+
/>
|
|
63
|
+
<InfoField
|
|
64
|
+
label='Beneficiary'
|
|
65
|
+
value={<div>Hanzo, Inc<br/>4811 Mastin Street<br/>Merriam, KS 66203</div>}
|
|
66
|
+
copyValue='Hanzo, Inc, 4811 Mastin Street, Merriam, KS 66203'
|
|
67
|
+
/>
|
|
68
|
+
<InfoField label='Routing Number - ACH' value='113000023 / 111000025' copyValue='113000023 / 111000025'/>
|
|
69
|
+
<InfoField label='Routing Number - Wire' value='026009593' copyValue='026009593'/>
|
|
70
|
+
<InfoField label='Routing Number - SWIFT' value='BOFAUS3N' copyValue='BOFAUS3N'/>
|
|
71
|
+
<InfoField label='Reference' value='Lux' copyValue='Lux'/>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
<Button onClick={payByBankTransfer} className='mx-auto w-full mt-4'>Continue</Button>
|
|
75
|
+
</div>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export default PayWithBankTransfer
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import React, { useEffect, useState } from 'react'
|
|
3
|
+
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
import { ApplePay, GooglePay, CreditCard, PaymentForm } from 'react-square-web-payments-sdk'
|
|
6
|
+
|
|
7
|
+
import { ChevronRight } from 'lucide-react'
|
|
8
|
+
import { observer } from 'mobx-react-lite'
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
Accordion,
|
|
12
|
+
AccordionContent,
|
|
13
|
+
AccordionItem,
|
|
14
|
+
AccordionTrigger,
|
|
15
|
+
ApplyTypography,
|
|
16
|
+
Button,
|
|
17
|
+
Skeleton,
|
|
18
|
+
buttonVariants
|
|
19
|
+
} from '@hanzo/ui/primitives'
|
|
20
|
+
|
|
21
|
+
import { cn } from '@hanzo/ui/util'
|
|
22
|
+
|
|
23
|
+
import { useCommerce } from '../../../service/context'
|
|
24
|
+
import { processSquareCardPayment } from '../../../util'
|
|
25
|
+
import type { PaymentMethodComponentProps } from '../../../types'
|
|
26
|
+
import { sendFBEvent, sendGAEvent } from '../../../util/analytics'
|
|
27
|
+
|
|
28
|
+
import ContactInfo from '../contact-form'
|
|
29
|
+
import PaymentMethods from '../card-icon-row'
|
|
30
|
+
|
|
31
|
+
const PayWithCard: React.FC<PaymentMethodComponentProps> = observer(({
|
|
32
|
+
onDone,
|
|
33
|
+
transactionStatus,
|
|
34
|
+
setTransactionStatus,
|
|
35
|
+
storePaymentInfo,
|
|
36
|
+
contactForm,
|
|
37
|
+
}) => {
|
|
38
|
+
const cmmc = useCommerce()
|
|
39
|
+
|
|
40
|
+
const cardTokenizeResponseReceived = async (
|
|
41
|
+
token: any,
|
|
42
|
+
verifiedBuyer: any
|
|
43
|
+
) => {
|
|
44
|
+
contactForm.handleSubmit(async () => {
|
|
45
|
+
setTransactionStatus('paid')
|
|
46
|
+
const res = await processSquareCardPayment(token.token, cmmc.promoAppliedCartTotal, verifiedBuyer.token)
|
|
47
|
+
if (res) {
|
|
48
|
+
await storePaymentInfo({paymentMethod: token.details.method ?? null, processed: res})
|
|
49
|
+
setTransactionStatus('confirmed')
|
|
50
|
+
sendGAEvent('purchase', {
|
|
51
|
+
transaction_id: res.payment?.id,
|
|
52
|
+
value: res.payment?.amountMoney?.amount,
|
|
53
|
+
currency: res.payment?.amountMoney?.currency,
|
|
54
|
+
items: cmmc.cartItems.map((item) => ({
|
|
55
|
+
item_id: item.sku,
|
|
56
|
+
item_name: item.title,
|
|
57
|
+
item_category: item.categoryId,
|
|
58
|
+
price: item.price,
|
|
59
|
+
quantity: item.quantity
|
|
60
|
+
})),
|
|
61
|
+
})
|
|
62
|
+
sendFBEvent('Purchase', {
|
|
63
|
+
content_ids: cmmc.cartItems.map((item) => item.sku),
|
|
64
|
+
contents: cmmc.cartItems.map(item => ({
|
|
65
|
+
id: item.sku,
|
|
66
|
+
quantity: item.quantity
|
|
67
|
+
})),
|
|
68
|
+
num_items: cmmc.cartItems.length,
|
|
69
|
+
value: cmmc.promoAppliedCartTotal,
|
|
70
|
+
currency: 'USD',
|
|
71
|
+
})
|
|
72
|
+
} else {
|
|
73
|
+
setTransactionStatus('error')
|
|
74
|
+
}
|
|
75
|
+
})()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const createVerificationDetails = () => {
|
|
79
|
+
const {name, email} = contactForm.getValues()
|
|
80
|
+
return {
|
|
81
|
+
amount: cmmc.promoAppliedCartTotal.toFixed(2),
|
|
82
|
+
billingContact: {
|
|
83
|
+
givenName: name,
|
|
84
|
+
email,
|
|
85
|
+
},
|
|
86
|
+
currencyCode: 'USD',
|
|
87
|
+
intent: 'CHARGE',
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const createPaymentRequest = () => ({
|
|
92
|
+
countryCode: "US",
|
|
93
|
+
currencyCode: "USD",
|
|
94
|
+
lineItems: cmmc.cartItems.map(item => ({
|
|
95
|
+
amount: item.price.toFixed(2),
|
|
96
|
+
label: item.title,
|
|
97
|
+
id: item.sku,
|
|
98
|
+
})),
|
|
99
|
+
requestBillingContact: false,
|
|
100
|
+
requestShippingContact: false,
|
|
101
|
+
total: {
|
|
102
|
+
amount: cmmc.promoAppliedCartTotal.toFixed(2),
|
|
103
|
+
label: "Total",
|
|
104
|
+
},
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Reload payment form after checkout value changes (promo code applied, etc.)
|
|
109
|
+
* Reloading is required so that Apple Pay and Google Pay buttons are updated for new cart total.
|
|
110
|
+
*/
|
|
111
|
+
const [loadingPaymentForm, setLoadingPaymentForm] = useState<boolean>(false)
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
setLoadingPaymentForm(true)
|
|
114
|
+
const timeout = setTimeout(() => setLoadingPaymentForm(false), 1000)
|
|
115
|
+
return () => clearTimeout(timeout)
|
|
116
|
+
}, [cmmc.promoAppliedCartTotal])
|
|
117
|
+
|
|
118
|
+
if (loadingPaymentForm) {
|
|
119
|
+
return (
|
|
120
|
+
<div className='flex flex-col gap-2'>
|
|
121
|
+
<Skeleton className='w-full h-10' />
|
|
122
|
+
<Skeleton className='w-full h-10' />
|
|
123
|
+
</div>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<PaymentForm
|
|
129
|
+
/**
|
|
130
|
+
* Identifies the calling form with a verified application ID generated from
|
|
131
|
+
* the Square Application Dashboard.
|
|
132
|
+
*/
|
|
133
|
+
applicationId={process.env.NEXT_PUBLIC_SQUARE_APPLICATION_ID}
|
|
134
|
+
/**
|
|
135
|
+
* Invoked when payment form receives the result of a tokenize generation
|
|
136
|
+
* request. The result will be a valid credit card or wallet token, or an error.
|
|
137
|
+
*/
|
|
138
|
+
cardTokenizeResponseReceived={cardTokenizeResponseReceived}
|
|
139
|
+
/**
|
|
140
|
+
* This function enable the Strong Customer Authentication (SCA) flow
|
|
141
|
+
*
|
|
142
|
+
* We strongly recommend use this function to verify the buyer and reduce
|
|
143
|
+
* the chance of fraudulent transactions.
|
|
144
|
+
*/
|
|
145
|
+
createVerificationDetails={createVerificationDetails}
|
|
146
|
+
/**
|
|
147
|
+
* This function is required for digital wallets (Apple Pay, Google Pay)
|
|
148
|
+
*/
|
|
149
|
+
createPaymentRequest={createPaymentRequest}
|
|
150
|
+
/**
|
|
151
|
+
* Identifies the location of the merchant that is taking the payment.
|
|
152
|
+
* Obtained from the Square Application Dashboard - Locations tab.
|
|
153
|
+
*/
|
|
154
|
+
locationId={process.env.NEXT_PUBLIC_SQUARE_LOCATION_ID}
|
|
155
|
+
>
|
|
156
|
+
<ApplyTypography className='flex flex-col mt-6 gap-1'>
|
|
157
|
+
{transactionStatus === 'paid' ? (
|
|
158
|
+
<h6 className='mx-auto font-nav'>Processing your payment...</h6>
|
|
159
|
+
) : transactionStatus === 'confirmed' ? (
|
|
160
|
+
<div className='flex flex-col gap-4'>
|
|
161
|
+
<h5 className='mx-auto font-nav'>Payment confirmed!</h5>
|
|
162
|
+
<p className='mx-auto'>Thank you for your purchase.</p>
|
|
163
|
+
<Button onClick={onDone}>Continue</Button>
|
|
164
|
+
</div>
|
|
165
|
+
) : (
|
|
166
|
+
<div className='flex flex-col gap-1'>
|
|
167
|
+
<GooglePay/>
|
|
168
|
+
<ApplePay/>
|
|
169
|
+
|
|
170
|
+
<div className='flex gap-2 whitespace-nowrap items-center my-1 sm:my-3 text-xs text-muted'>
|
|
171
|
+
<hr className='grow border'/><div className='shrink-0 mx-1'>or</div><hr className='grow border'/>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
<PaymentMethods />
|
|
175
|
+
|
|
176
|
+
<ContactInfo form={contactForm}/>
|
|
177
|
+
{/* Imitates hanzo/ui Button and Input styles, I was unable to render the
|
|
178
|
+
hanzo/ui button outright and keeping the submit form functionality*/}
|
|
179
|
+
<CreditCard
|
|
180
|
+
style={{
|
|
181
|
+
'.input-container': {
|
|
182
|
+
borderColor: '#404040',
|
|
183
|
+
borderRadius: '6px',
|
|
184
|
+
},
|
|
185
|
+
'.input-container.is-focus': {
|
|
186
|
+
borderColor: '#ffffff',
|
|
187
|
+
},
|
|
188
|
+
'.input-container.is-error': {
|
|
189
|
+
borderColor: '#ff1600',
|
|
190
|
+
},
|
|
191
|
+
'.message-text': {
|
|
192
|
+
color: '#999999',
|
|
193
|
+
},
|
|
194
|
+
'.message-icon': {
|
|
195
|
+
color: '#999999',
|
|
196
|
+
},
|
|
197
|
+
'.message-text.is-error': {
|
|
198
|
+
color: '#ff1600',
|
|
199
|
+
},
|
|
200
|
+
'.message-icon.is-error': {
|
|
201
|
+
color: '#ff1600',
|
|
202
|
+
},
|
|
203
|
+
input: {
|
|
204
|
+
backgroundColor: 'transparent',
|
|
205
|
+
color: '#FFFFFF',
|
|
206
|
+
fontSize: '15px',
|
|
207
|
+
fontFamily: 'helvetica neue, sans-serif',
|
|
208
|
+
},
|
|
209
|
+
'input::placeholder': {
|
|
210
|
+
color: '#999999',
|
|
211
|
+
},
|
|
212
|
+
'input.is-error': {
|
|
213
|
+
color: '#ff1600',
|
|
214
|
+
},
|
|
215
|
+
}}
|
|
216
|
+
render={(Button: any) => (
|
|
217
|
+
<Button className={cn(
|
|
218
|
+
'items-center justify-center font-medium transition-colors focus-visible:outline-none focus-visible:ring-2',
|
|
219
|
+
'focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background',
|
|
220
|
+
'!bg-primary !text-primary-fg hover:!bg-primary-hover font-nav whitespace-nowrap not-typography h-10 py-2 px-4',
|
|
221
|
+
'!text-sm rounded-md lg:min-w-[220px] sm:min-w-[220px] flex'
|
|
222
|
+
)}>
|
|
223
|
+
Pay
|
|
224
|
+
</Button>
|
|
225
|
+
)}
|
|
226
|
+
/>
|
|
227
|
+
{transactionStatus === 'error' && (
|
|
228
|
+
<p className='mx-auto text-destructive'>There was an error processing your payment.</p>
|
|
229
|
+
)}
|
|
230
|
+
</div>
|
|
231
|
+
)}
|
|
232
|
+
</ApplyTypography>
|
|
233
|
+
</PaymentForm>
|
|
234
|
+
)
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
export default PayWithCard
|
|
@@ -18,14 +18,12 @@ import {
|
|
|
18
18
|
toast
|
|
19
19
|
} from '@hanzo/ui/primitives'
|
|
20
20
|
|
|
21
|
-
import
|
|
21
|
+
import Eth from '../crypto-icons/eth'
|
|
22
|
+
import { useCommerce } from '../../../service/context'
|
|
23
|
+
import type { PaymentMethodComponentProps } from '../../../types'
|
|
24
|
+
import { sendFBEvent, sendGAEvent } from '../../../util/analytics'
|
|
22
25
|
|
|
23
|
-
import
|
|
24
|
-
import { useCommerce } from '../../../../service/context'
|
|
25
|
-
import type { TransactionStatus } from '../../../../types'
|
|
26
|
-
import ContactInfo from './contact-info'
|
|
27
|
-
import type { UseFormReturn } from 'react-hook-form'
|
|
28
|
-
import { sendFBEvent, sendGAEvent } from '../../../../util/analytics'
|
|
26
|
+
import ContactForm from '../contact-form'
|
|
29
27
|
|
|
30
28
|
declare global {
|
|
31
29
|
interface Window{
|
|
@@ -33,19 +31,7 @@ declare global {
|
|
|
33
31
|
}
|
|
34
32
|
}
|
|
35
33
|
|
|
36
|
-
const PayWithCrypto: React.FC<{
|
|
37
|
-
onDone: () => void
|
|
38
|
-
transactionStatus: TransactionStatus
|
|
39
|
-
setTransactionStatus: (status: TransactionStatus) => void
|
|
40
|
-
storePaymentInfo: (paymentInfo: any) => Promise<void>
|
|
41
|
-
contactForm: UseFormReturn<{
|
|
42
|
-
name: string
|
|
43
|
-
email: string
|
|
44
|
-
}, any, {
|
|
45
|
-
name: string
|
|
46
|
-
email: string
|
|
47
|
-
}>
|
|
48
|
-
}> = observer(({
|
|
34
|
+
const PayWithCrypto: React.FC<PaymentMethodComponentProps> = observer(({
|
|
49
35
|
onDone,
|
|
50
36
|
transactionStatus,
|
|
51
37
|
setTransactionStatus,
|
|
@@ -53,7 +39,7 @@ const PayWithCrypto: React.FC<{
|
|
|
53
39
|
contactForm
|
|
54
40
|
}) => {
|
|
55
41
|
const cmmc = useCommerce()
|
|
56
|
-
|
|
42
|
+
|
|
57
43
|
const [loadingPrice, setLoadingPrice] = useState(false)
|
|
58
44
|
//const [selectedToken, setSelectedToken] = useState('eth')
|
|
59
45
|
const [amount, setAmount] = useState<number>()
|
|
@@ -78,7 +64,7 @@ const PayWithCrypto: React.FC<{
|
|
|
78
64
|
.then(res => res.json())
|
|
79
65
|
.then((exchangeRate) => {
|
|
80
66
|
const oneUsdInWei = (10**18) / exchangeRate.data.amount
|
|
81
|
-
const usdAmountInWei = oneUsdInWei * cmmc.
|
|
67
|
+
const usdAmountInWei = oneUsdInWei * cmmc.promoAppliedCartTotal
|
|
82
68
|
setAmount(usdAmountInWei)
|
|
83
69
|
setLoadingPrice(false)
|
|
84
70
|
})
|
|
@@ -91,7 +77,7 @@ const PayWithCrypto: React.FC<{
|
|
|
91
77
|
const interval = setInterval(fetchPrice, 30000)
|
|
92
78
|
|
|
93
79
|
return () => clearInterval(interval)
|
|
94
|
-
}, [cmmc.
|
|
80
|
+
}, [cmmc.promoAppliedCartTotal])
|
|
95
81
|
|
|
96
82
|
const sendPayment = async (ether: number) => {
|
|
97
83
|
contactForm.handleSubmit(async () => {
|
|
@@ -188,7 +174,7 @@ const PayWithCrypto: React.FC<{
|
|
|
188
174
|
return (
|
|
189
175
|
<div className='flex flex-col gap-6 mt-6'>
|
|
190
176
|
<div className='flex flex-col gap-2 w-full'>
|
|
191
|
-
<
|
|
177
|
+
<ContactForm form={contactForm}/>
|
|
192
178
|
<div className='flex gap-2 grid grid-cols-3'>
|
|
193
179
|
<Select onValueChange={(token) => {/*ONLY ETH setSelectedToken(token) */}} defaultValue='eth'>
|
|
194
180
|
<SelectTrigger>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { PaymentMethodDesc } from '../../../types'
|
|
2
|
+
|
|
3
|
+
import Card from './card'
|
|
4
|
+
import Crypto from './crypto'
|
|
5
|
+
import BankTransfer from './bank-transfer'
|
|
6
|
+
|
|
7
|
+
export default [
|
|
8
|
+
{
|
|
9
|
+
value: 'card',
|
|
10
|
+
label: 'Card',
|
|
11
|
+
Comp: Card
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
value: 'crypto',
|
|
15
|
+
label: 'Wallet',
|
|
16
|
+
Comp: Crypto
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
value: 'bank',
|
|
20
|
+
label: 'Bank Wire',
|
|
21
|
+
Comp: BankTransfer
|
|
22
|
+
},
|
|
23
|
+
] satisfies PaymentMethodDesc[]
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import type { ImageDef } from '@hanzo/ui/types'
|
|
14
14
|
import { cn } from '@hanzo/ui/util'
|
|
15
15
|
|
|
16
|
-
import {
|
|
16
|
+
import { formatCurrencyValue } from '../util'
|
|
17
17
|
import type { LineItem } from '../types'
|
|
18
18
|
import { Icons } from './Icons'
|
|
19
19
|
|
|
@@ -66,7 +66,7 @@ const ProductCard: React.FC<ProductCardProps> = ({
|
|
|
66
66
|
<CardContent className='grid gap-2.5 p-4'>
|
|
67
67
|
<CardTitle className='text-sm sm:text-base flex flex-col justify-start items-center line-clap-3'>
|
|
68
68
|
{item.title.split(', ').map((e, i) => (<p key={i}>{e}</p>))}
|
|
69
|
-
<p className='mt-1 font-semibold'>{
|
|
69
|
+
<p className='mt-1 font-semibold'>{formatCurrencyValue(item.price)}</p>
|
|
70
70
|
</CardTitle>
|
|
71
71
|
</CardContent>
|
|
72
72
|
<CardFooter className='p-4 flex flex-row justify-center'>
|
|
@@ -7,7 +7,7 @@ import { cn } from '@hanzo/ui/util'
|
|
|
7
7
|
import { Skeleton } from '@hanzo/ui/primitives'
|
|
8
8
|
|
|
9
9
|
import type { ItemSelector } from '../types'
|
|
10
|
-
import {
|
|
10
|
+
import { formatCurrencyValue } from '../util'
|
|
11
11
|
import { Icons } from './Icons'
|
|
12
12
|
|
|
13
13
|
import AddToCartWidget from './add-to-cart-widget'
|
|
@@ -132,7 +132,7 @@ const SelectCategoryItemPanel: React.FC<
|
|
|
132
132
|
</h3>
|
|
133
133
|
{selectedItemRef.item?.sku ? (
|
|
134
134
|
<h6 className='text-center font-semibold'>
|
|
135
|
-
{(soleOption ? '' : (selectedItemRef.item.titleAsOption + ': ')) +
|
|
135
|
+
{(soleOption ? '' : (selectedItemRef.item.titleAsOption + ': ')) + formatCurrencyValue(selectedItemRef.item.price)}
|
|
136
136
|
</h6>
|
|
137
137
|
) : ''}
|
|
138
138
|
</div>
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
import * as z from 'zod'
|
|
5
4
|
import { useForm } from 'react-hook-form'
|
|
6
5
|
import { zodResolver } from '@hookform/resolvers/zod'
|
|
@@ -12,7 +11,6 @@ import {
|
|
|
12
11
|
FormControl,
|
|
13
12
|
FormField,
|
|
14
13
|
FormItem,
|
|
15
|
-
FormLabel,
|
|
16
14
|
FormMessage,
|
|
17
15
|
Select,
|
|
18
16
|
SelectContent,
|
|
@@ -21,12 +19,12 @@ import {
|
|
|
21
19
|
SelectValue
|
|
22
20
|
} from '@hanzo/ui/primitives'
|
|
23
21
|
|
|
24
|
-
import { useCommerce } from '
|
|
22
|
+
import { useCommerce } from '../service/context'
|
|
25
23
|
|
|
26
|
-
import countries from '
|
|
27
|
-
import { sendGAEvent } from '
|
|
24
|
+
import countries from '../util/countries'
|
|
25
|
+
import { sendGAEvent } from '../util/analytics'
|
|
28
26
|
|
|
29
|
-
import type {
|
|
27
|
+
import type { CheckoutStepComponentProps } from '../types'
|
|
30
28
|
|
|
31
29
|
const shippingFormSchema = z.object({
|
|
32
30
|
addressLine1: z.string().min(2, 'Address must be at least 2 characters.'),
|
|
@@ -37,7 +35,7 @@ const shippingFormSchema = z.object({
|
|
|
37
35
|
country: z.string().min(2, 'Country is invalid.'),
|
|
38
36
|
})
|
|
39
37
|
|
|
40
|
-
const
|
|
38
|
+
const ShippingStepForm: React.FC<CheckoutStepComponentProps> = ({
|
|
41
39
|
orderId,
|
|
42
40
|
onDone
|
|
43
41
|
}) => {
|
|
@@ -171,4 +169,4 @@ const ShippingInfo: React.FC<StepComponentProps> = ({
|
|
|
171
169
|
)
|
|
172
170
|
}
|
|
173
171
|
|
|
174
|
-
export default
|
|
172
|
+
export default ShippingStepForm
|
package/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export * from './service/context'
|
|
2
2
|
export * from './components'
|
|
3
3
|
export type { StandaloneServiceOptions as ServiceOptions } from './service/impls/standalone/standalone-service'
|
|
4
|
-
export { useSyncSkuParamWithCurrentItem, getFacetValuesMutator,
|
|
4
|
+
export { useSyncSkuParamWithCurrentItem, getFacetValuesMutator, formatCurrencyValue } from './util'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/commerce",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"description": "Library with shopping cart components.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org/",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"@hanzo/auth": "^2.3.2",
|
|
43
|
-
"@hanzo/ui": "^3.0.
|
|
43
|
+
"@hanzo/ui": "^3.0.19",
|
|
44
44
|
"@hookform/resolvers": "^3.3.4",
|
|
45
45
|
"firebase": "^10.8.0",
|
|
46
46
|
"lucide-react": "^0.307.0",
|
package/service/context.tsx
CHANGED
|
@@ -29,6 +29,7 @@ const CommerceServiceProvider: React.FC<PropsWithChildren & {
|
|
|
29
29
|
options
|
|
30
30
|
}) => {
|
|
31
31
|
|
|
32
|
+
// TODO: Inject Promo fixture here
|
|
32
33
|
const serviceRef = useRef<CommerceService>(getServiceSingleton(productsByCategory, rootFacet, options))
|
|
33
34
|
return (
|
|
34
35
|
<CommerceServiceContext.Provider value={serviceRef.current}>
|