@hanzo/commerce 4.6.0 → 4.8.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 +2 -2
- package/components/cart-panel/index.tsx +13 -12
- package/components/cart-panel/products-carousel.tsx +60 -0
- package/components/checkout-panel/cart-accordian.tsx +47 -0
- package/components/checkout-panel/close-button.tsx +3 -3
- package/components/checkout-panel/desktop.tsx +46 -0
- package/components/checkout-panel/index.tsx +86 -97
- package/components/checkout-panel/mobile.tsx +41 -0
- package/components/checkout-panel/{payment/payment-methods → steps/payment/cards}/index.tsx +3 -2
- package/components/checkout-panel/steps/payment/cc-button.tsx +17 -0
- package/components/checkout-panel/{payment → steps/payment}/contact-info.tsx +7 -10
- package/components/checkout-panel/{payment → steps/payment}/index.tsx +27 -21
- package/components/checkout-panel/{payment → steps/payment}/pay-with-bank-transfer.tsx +14 -10
- package/components/checkout-panel/steps/payment/pay-with-card.tsx +246 -0
- package/components/checkout-panel/{payment → steps/payment}/pay-with-crypto.tsx +7 -19
- package/components/checkout-panel/{shipping-info.tsx → steps/shipping-info.tsx} +15 -22
- package/components/checkout-panel/{thank-you.tsx → steps/thank-you.tsx} +4 -1
- package/components/checkout-panel/steps/types.ts +14 -0
- package/package.json +5 -3
- package/service/impls/standalone/actual-line-item.ts +7 -1
- package/types/product.ts +4 -0
- package/{components/checkout-panel → util}/countries.ts +1 -1
- package/components/checkout-panel/payment/pay-with-card.tsx +0 -217
- package/components/checkout-panel/step-indicator.tsx +0 -30
- package/components/checkout-panel/step.ts +0 -11
- /package/components/checkout-panel/{confirm-order.tsx → _confirm-order.tsx_unused} +0 -0
- /package/components/checkout-panel/{payment/payment-methods → steps/payment/cards}/amex.tsx +0 -0
- /package/components/checkout-panel/{payment/payment-methods → steps/payment/cards}/diners-club.tsx +0 -0
- /package/components/checkout-panel/{payment/payment-methods → steps/payment/cards}/discover.tsx +0 -0
- /package/components/checkout-panel/{payment/payment-methods → steps/payment/cards}/jcb.tsx +0 -0
- /package/components/checkout-panel/{payment/payment-methods → steps/payment/cards}/mastercard.tsx +0 -0
- /package/components/checkout-panel/{payment/payment-methods → steps/payment/cards}/visa.tsx +0 -0
|
@@ -12,21 +12,20 @@ import { useAuth } from '@hanzo/auth/service'
|
|
|
12
12
|
import PayWithCrypto from './pay-with-crypto'
|
|
13
13
|
import PayWithBankTransfer from './pay-with-bank-transfer'
|
|
14
14
|
import PayWithCard from './pay-with-card'
|
|
15
|
-
|
|
16
|
-
import
|
|
17
|
-
import {
|
|
15
|
+
|
|
16
|
+
import { useCommerce } from '../../../../service/context'
|
|
17
|
+
import type { TransactionStatus } from '../../../../types'
|
|
18
|
+
import { sendFBEvent, sendGAEvent } from '../../../../util/analytics'
|
|
19
|
+
|
|
20
|
+
import type { StepComponentProps } from '../types'
|
|
18
21
|
|
|
19
22
|
const contactFormSchema = z.object({
|
|
20
23
|
name: z.string().min(1, 'Enter your full name.'),
|
|
21
24
|
email: z.string().email(),
|
|
22
25
|
})
|
|
23
26
|
|
|
24
|
-
const Payment: React.FC<{
|
|
25
|
-
|
|
26
|
-
orderId?: string
|
|
27
|
-
setOrderId: (orderId?: string) => void
|
|
28
|
-
}> = observer(({
|
|
29
|
-
setStep,
|
|
27
|
+
const Payment: React.FC<StepComponentProps> = observer(({
|
|
28
|
+
onDone,
|
|
30
29
|
orderId,
|
|
31
30
|
setOrderId
|
|
32
31
|
}) => {
|
|
@@ -51,7 +50,7 @@ const Payment: React.FC<{
|
|
|
51
50
|
|
|
52
51
|
const storePaymentInfo = async (paymentInfo: any) => {
|
|
53
52
|
const {name, email} = contactForm.getValues()
|
|
54
|
-
let id
|
|
53
|
+
let id: string | undefined = undefined
|
|
55
54
|
if (!orderId) {
|
|
56
55
|
id = await cmmc.createOrder(email, name)
|
|
57
56
|
setOrderId(id)
|
|
@@ -81,34 +80,41 @@ const Payment: React.FC<{
|
|
|
81
80
|
}
|
|
82
81
|
}
|
|
83
82
|
|
|
83
|
+
const groupClx = 'grid w-full grid-cols-3 mx-auto ' +
|
|
84
|
+
'p-0 h-auto overflow-hidden ' +
|
|
85
|
+
'border-2 bg-background border-level-2 md:bg-level-1 md:border-level-3 '
|
|
86
|
+
|
|
87
|
+
const tabClx = 'whitespace-normal h-full text-xs sm:text-base px-1 text-muted ' +
|
|
88
|
+
'data-[state=active]:text-accent data-[state=active]:bg-level-2 md:data-[state=active]:bg-level-3'
|
|
89
|
+
|
|
84
90
|
return (
|
|
85
|
-
<Tabs defaultValue='card' className='w-full
|
|
86
|
-
<TabsList className=
|
|
91
|
+
<Tabs defaultValue='card' className='w-full sm:max-w-[500px] sm:mx-auto'>
|
|
92
|
+
<TabsList className={groupClx}>
|
|
87
93
|
<TabsTrigger
|
|
88
94
|
value='card'
|
|
89
|
-
className=
|
|
95
|
+
className={tabClx}
|
|
90
96
|
disabled={transactionStatus === 'paid' || transactionStatus === 'confirmed'}
|
|
91
97
|
>
|
|
92
|
-
|
|
98
|
+
Card
|
|
93
99
|
</TabsTrigger>
|
|
94
100
|
<TabsTrigger
|
|
95
101
|
value='crypto'
|
|
96
|
-
className=
|
|
102
|
+
className={tabClx}
|
|
97
103
|
disabled={transactionStatus === 'paid' || transactionStatus === 'confirmed'}
|
|
98
104
|
>
|
|
99
|
-
|
|
105
|
+
Wallet
|
|
100
106
|
</TabsTrigger>
|
|
101
107
|
<TabsTrigger
|
|
102
108
|
value='bank'
|
|
103
|
-
className=
|
|
109
|
+
className={tabClx}
|
|
104
110
|
disabled={transactionStatus === 'paid' || transactionStatus === 'confirmed'}
|
|
105
111
|
>
|
|
106
|
-
|
|
112
|
+
Bank Wire
|
|
107
113
|
</TabsTrigger>
|
|
108
114
|
</TabsList>
|
|
109
115
|
<TabsContent value='card'>
|
|
110
116
|
<PayWithCard
|
|
111
|
-
|
|
117
|
+
onDone={onDone}
|
|
112
118
|
transactionStatus={transactionStatus}
|
|
113
119
|
setTransactionStatus={setTransactionStatus}
|
|
114
120
|
storePaymentInfo={storePaymentInfo}
|
|
@@ -117,7 +123,7 @@ const Payment: React.FC<{
|
|
|
117
123
|
</TabsContent>
|
|
118
124
|
<TabsContent value='crypto'>
|
|
119
125
|
<PayWithCrypto
|
|
120
|
-
|
|
126
|
+
onDone={onDone}
|
|
121
127
|
transactionStatus={transactionStatus}
|
|
122
128
|
setTransactionStatus={setTransactionStatus}
|
|
123
129
|
storePaymentInfo={storePaymentInfo}
|
|
@@ -126,7 +132,7 @@ const Payment: React.FC<{
|
|
|
126
132
|
</TabsContent>
|
|
127
133
|
<TabsContent value='bank'>
|
|
128
134
|
<PayWithBankTransfer
|
|
129
|
-
|
|
135
|
+
onDone={onDone}
|
|
130
136
|
storePaymentInfo={storePaymentInfo}
|
|
131
137
|
contactForm={contactForm}
|
|
132
138
|
/>
|
|
@@ -18,13 +18,13 @@ const InfoField: React.FC<{
|
|
|
18
18
|
}) => {
|
|
19
19
|
const copyToClipboard = (label: string, text: string) => {
|
|
20
20
|
navigator.clipboard.writeText(text)
|
|
21
|
-
toast(`${label} copied to clipboard
|
|
21
|
+
toast(`${label} copied to clipboard.`)
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
return (
|
|
25
25
|
<div className='flex flex-col gap-1'>
|
|
26
26
|
<p className='text-xs'>{label}</p>
|
|
27
|
-
<div className='flex items-center justify-between text-lg border rounded-lg py-2 px-4'>
|
|
27
|
+
<div className='flex items-center justify-between sm:text-lg border rounded-lg py-2 px-4'>
|
|
28
28
|
{value}
|
|
29
29
|
<Button variant='ghost' size='icon' onClick={() => copyToClipboard(label, copyValue)}>
|
|
30
30
|
<Copy className='h-4 w-4'/>
|
|
@@ -35,7 +35,7 @@ const InfoField: React.FC<{
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
const PayWithBankTransfer: React.FC<{
|
|
38
|
-
|
|
38
|
+
onDone: () => void
|
|
39
39
|
storePaymentInfo: (paymentInfo: any) => Promise<void>
|
|
40
40
|
contactForm: UseFormReturn<{
|
|
41
41
|
name: string
|
|
@@ -45,24 +45,28 @@ const PayWithBankTransfer: React.FC<{
|
|
|
45
45
|
email: string
|
|
46
46
|
}>
|
|
47
47
|
}> = ({
|
|
48
|
-
|
|
48
|
+
onDone,
|
|
49
49
|
storePaymentInfo,
|
|
50
50
|
contactForm
|
|
51
51
|
}) => {
|
|
52
|
+
|
|
52
53
|
const payByBankTransfer = async () => {
|
|
53
|
-
contactForm.handleSubmit(async () => {
|
|
54
|
+
contactForm.handleSubmit( async () => {
|
|
54
55
|
await storePaymentInfo({paymentMethod: 'bank-transfer'})
|
|
55
|
-
|
|
56
|
+
onDone()
|
|
56
57
|
})()
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
const groupClx = 'border-2 bg-background border-level-2 md:bg-level-1 md:border-level-3 p-0 h-auto overflow-hidden rounded-md'
|
|
61
|
+
const tabClx = 'text-muted data-[state=active]:text-accent data-[state=active]:bg-level-2 md:data-[state=active]:bg-level-3'
|
|
62
|
+
|
|
59
63
|
return (
|
|
60
|
-
<div className='flex flex-col gap-
|
|
64
|
+
<div className='flex flex-col gap-2 mt-6'>
|
|
61
65
|
<ContactInfo form={contactForm}/>
|
|
62
66
|
<Tabs defaultValue="usd" className='w-full mx-auto max-w-[50rem]'>
|
|
63
|
-
<TabsList className="grid w-full grid-cols-2 max-w-[15rem] mx-auto
|
|
64
|
-
<TabsTrigger value="usd">USD</TabsTrigger>
|
|
65
|
-
<TabsTrigger value="eur">EUR/GBP</TabsTrigger>
|
|
67
|
+
<TabsList className={"grid w-full grid-cols-2 max-w-[15rem] mx-auto " + groupClx}>
|
|
68
|
+
<TabsTrigger className={tabClx} value="usd">USD</TabsTrigger>
|
|
69
|
+
<TabsTrigger className={tabClx} value="eur">EUR/GBP</TabsTrigger>
|
|
66
70
|
</TabsList>
|
|
67
71
|
<TabsContent value="usd">
|
|
68
72
|
<div className='flex flex-col gap-4 w-full'>
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
import type { UseFormReturn } from 'react-hook-form'
|
|
5
|
+
// @ts-ignore
|
|
6
|
+
import { ApplePay, GooglePay, CreditCard, PaymentForm } from 'react-square-web-payments-sdk'
|
|
7
|
+
|
|
8
|
+
import { ChevronRight } from 'lucide-react'
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
Accordion,
|
|
12
|
+
AccordionContent,
|
|
13
|
+
AccordionItem,
|
|
14
|
+
AccordionTrigger,
|
|
15
|
+
ApplyTypography,
|
|
16
|
+
Button,
|
|
17
|
+
buttonVariants
|
|
18
|
+
} from '@hanzo/ui/primitives'
|
|
19
|
+
|
|
20
|
+
import { cn } from '@hanzo/ui/util'
|
|
21
|
+
|
|
22
|
+
import { processSquareCardPayment } from '../../../../util'
|
|
23
|
+
import { useCommerce } from '../../../../service/context'
|
|
24
|
+
import type { TransactionStatus } from '../../../../types'
|
|
25
|
+
|
|
26
|
+
import ContactInfo from './contact-info'
|
|
27
|
+
import { sendFBEvent, sendGAEvent } from '../../../../util/analytics'
|
|
28
|
+
import PaymentMethods from './cards'
|
|
29
|
+
|
|
30
|
+
const PayWithCard: React.FC<{
|
|
31
|
+
onDone: () => void
|
|
32
|
+
transactionStatus: TransactionStatus
|
|
33
|
+
setTransactionStatus: (status: TransactionStatus) => void
|
|
34
|
+
storePaymentInfo: (paymentInfo: any) => Promise<void>
|
|
35
|
+
contactForm: UseFormReturn<{
|
|
36
|
+
name: string
|
|
37
|
+
email: string
|
|
38
|
+
}, any, {
|
|
39
|
+
name: string
|
|
40
|
+
email: string
|
|
41
|
+
}>
|
|
42
|
+
}> = ({
|
|
43
|
+
onDone,
|
|
44
|
+
transactionStatus,
|
|
45
|
+
setTransactionStatus,
|
|
46
|
+
storePaymentInfo,
|
|
47
|
+
contactForm,
|
|
48
|
+
}) => {
|
|
49
|
+
const cmmc = useCommerce()
|
|
50
|
+
|
|
51
|
+
const cardTokenizeResponseReceived = async (
|
|
52
|
+
token: any,
|
|
53
|
+
verifiedBuyer: any
|
|
54
|
+
) => {
|
|
55
|
+
contactForm.handleSubmit(async () => {
|
|
56
|
+
setTransactionStatus('paid')
|
|
57
|
+
const res = await processSquareCardPayment(token.token, cmmc.cartTotal, verifiedBuyer.token)
|
|
58
|
+
if (res) {
|
|
59
|
+
console.log(token)
|
|
60
|
+
await storePaymentInfo({paymentMethod: token.details.method ?? null, processed: res})
|
|
61
|
+
setTransactionStatus('confirmed')
|
|
62
|
+
sendGAEvent('purchase', {
|
|
63
|
+
transaction_id: res.payment?.id,
|
|
64
|
+
value: res.payment?.amountMoney?.amount,
|
|
65
|
+
currency: res.payment?.amountMoney?.currency,
|
|
66
|
+
items: cmmc.cartItems.map((item) => ({
|
|
67
|
+
item_id: item.sku,
|
|
68
|
+
item_name: item.title,
|
|
69
|
+
item_category: item.categoryId,
|
|
70
|
+
price: item.price,
|
|
71
|
+
quantity: item.quantity
|
|
72
|
+
})),
|
|
73
|
+
})
|
|
74
|
+
sendFBEvent('Purchase', {
|
|
75
|
+
content_ids: cmmc.cartItems.map((item) => item.sku),
|
|
76
|
+
contents: cmmc.cartItems.map(item => ({
|
|
77
|
+
id: item.sku,
|
|
78
|
+
quantity: item.quantity
|
|
79
|
+
})),
|
|
80
|
+
num_items: cmmc.cartItems.length,
|
|
81
|
+
value: cmmc.cartTotal,
|
|
82
|
+
currency: 'USD',
|
|
83
|
+
})
|
|
84
|
+
} else {
|
|
85
|
+
setTransactionStatus('error')
|
|
86
|
+
}
|
|
87
|
+
})()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const createVerificationDetails = () => {
|
|
91
|
+
const {name, email} = contactForm.getValues()
|
|
92
|
+
return {
|
|
93
|
+
amount: cmmc.cartTotal.toFixed(2),
|
|
94
|
+
billingContact: {
|
|
95
|
+
givenName: name,
|
|
96
|
+
email,
|
|
97
|
+
},
|
|
98
|
+
currencyCode: 'USD',
|
|
99
|
+
intent: 'CHARGE',
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const createPaymentRequest= () => ({
|
|
104
|
+
countryCode: "US",
|
|
105
|
+
currencyCode: "USD",
|
|
106
|
+
lineItems: cmmc.cartItems.map(item => ({
|
|
107
|
+
amount: item.price.toFixed(2),
|
|
108
|
+
label: item.title,
|
|
109
|
+
id: item.sku,
|
|
110
|
+
})),
|
|
111
|
+
requestBillingContact: false,
|
|
112
|
+
requestShippingContact: false,
|
|
113
|
+
total: {
|
|
114
|
+
amount: cmmc.cartTotal.toFixed(2),
|
|
115
|
+
label: "Total",
|
|
116
|
+
},
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<PaymentForm
|
|
121
|
+
/**
|
|
122
|
+
* Identifies the calling form with a verified application ID generated from
|
|
123
|
+
* the Square Application Dashboard.
|
|
124
|
+
*/
|
|
125
|
+
applicationId={process.env.NEXT_PUBLIC_SQUARE_APPLICATION_ID}
|
|
126
|
+
/**
|
|
127
|
+
* Invoked when payment form receives the result of a tokenize generation
|
|
128
|
+
* request. The result will be a valid credit card or wallet token, or an error.
|
|
129
|
+
*/
|
|
130
|
+
cardTokenizeResponseReceived={cardTokenizeResponseReceived}
|
|
131
|
+
/**
|
|
132
|
+
* This function enable the Strong Customer Authentication (SCA) flow
|
|
133
|
+
*
|
|
134
|
+
* We strongly recommend use this function to verify the buyer and reduce
|
|
135
|
+
* the chance of fraudulent transactions.
|
|
136
|
+
*/
|
|
137
|
+
createVerificationDetails={createVerificationDetails}
|
|
138
|
+
/**
|
|
139
|
+
* This function is required for digital wallets (Apple Pay, Google Pay)
|
|
140
|
+
*/
|
|
141
|
+
createPaymentRequest={createPaymentRequest}
|
|
142
|
+
/**
|
|
143
|
+
* Identifies the location of the merchant that is taking the payment.
|
|
144
|
+
* Obtained from the Square Application Dashboard - Locations tab.
|
|
145
|
+
*/
|
|
146
|
+
locationId={process.env.NEXT_PUBLIC_SQUARE_LOCATION_ID}
|
|
147
|
+
>
|
|
148
|
+
<ApplyTypography className='flex flex-col mt-6 gap-1'>
|
|
149
|
+
{transactionStatus === 'paid' ? (
|
|
150
|
+
<h6 className='mx-auto font-nav'>Processing your payment...</h6>
|
|
151
|
+
) : transactionStatus === 'confirmed' ? (
|
|
152
|
+
<div className='flex flex-col gap-4'>
|
|
153
|
+
<h5 className='mx-auto font-nav'>Payment confirmed!</h5>
|
|
154
|
+
<p className='mx-auto'>Thank you for your purchase.</p>
|
|
155
|
+
<Button onClick={onDone}>Continue</Button>
|
|
156
|
+
</div>
|
|
157
|
+
) : (
|
|
158
|
+
<>
|
|
159
|
+
<GooglePay/>
|
|
160
|
+
<ApplePay/>
|
|
161
|
+
|
|
162
|
+
<div className='flex gap-2 whitespace-nowrap items-center my-0.5 sm:my-1 text-xs text-muted'>
|
|
163
|
+
<hr className='grow border'/><div className='shrink-0 mx-1'>or</div><hr className='grow border'/>
|
|
164
|
+
</div>
|
|
165
|
+
|
|
166
|
+
<Accordion type="single" collapsible className={''}>
|
|
167
|
+
<AccordionItem value="cart" className='w-full border-b-0 data-[state=open]:border data-[state=open]:px-3 rounded '>
|
|
168
|
+
<AccordionTrigger className={cn(
|
|
169
|
+
buttonVariants({variant: 'outline', size: 'lg'}),
|
|
170
|
+
'!no-underline group flex justify-between ',
|
|
171
|
+
'bg-transparent hover:bg-transparent border-muted-2 hover:border-muted-2',
|
|
172
|
+
'data-[state=open]:border-none data-[state=open]:py-0 px-4 data-[state=open]:px-1'
|
|
173
|
+
)}>
|
|
174
|
+
<div className='font-sans text-base mr-2 text-muted'>CC</div>
|
|
175
|
+
<PaymentMethods />
|
|
176
|
+
<div>
|
|
177
|
+
<ChevronRight className="h-5 w-5 -mr-2 text-muted shrink-0 transition-transform duration-200 group-data-[state=open]:rotate-90 hidden group-data-[state=open]:block" />
|
|
178
|
+
</div>
|
|
179
|
+
</AccordionTrigger>
|
|
180
|
+
<AccordionContent className='data-[state=open]:mb-4 data-[state=open]:mt-1'>
|
|
181
|
+
<ContactInfo form={contactForm}/>
|
|
182
|
+
{/* Imitates hanzo/ui Button and Input styles, I was unable to render the
|
|
183
|
+
hanzo/ui button outright and keeping the submit form functionality*/}
|
|
184
|
+
<CreditCard
|
|
185
|
+
style={{
|
|
186
|
+
'.input-container': {
|
|
187
|
+
borderColor: '#404040',
|
|
188
|
+
borderRadius: '6px',
|
|
189
|
+
},
|
|
190
|
+
'.input-container.is-focus': {
|
|
191
|
+
borderColor: '#ffffff',
|
|
192
|
+
},
|
|
193
|
+
'.input-container.is-error': {
|
|
194
|
+
borderColor: '#ff1600',
|
|
195
|
+
},
|
|
196
|
+
'.message-text': {
|
|
197
|
+
color: '#999999',
|
|
198
|
+
},
|
|
199
|
+
'.message-icon': {
|
|
200
|
+
color: '#999999',
|
|
201
|
+
},
|
|
202
|
+
'.message-text.is-error': {
|
|
203
|
+
color: '#ff1600',
|
|
204
|
+
},
|
|
205
|
+
'.message-icon.is-error': {
|
|
206
|
+
color: '#ff1600',
|
|
207
|
+
},
|
|
208
|
+
input: {
|
|
209
|
+
backgroundColor: '#1f1f1f',
|
|
210
|
+
color: '#FFFFFF',
|
|
211
|
+
fontSize: '15px',
|
|
212
|
+
fontFamily: 'helvetica neue, sans-serif',
|
|
213
|
+
},
|
|
214
|
+
'input::placeholder': {
|
|
215
|
+
color: '#999999',
|
|
216
|
+
},
|
|
217
|
+
'input.is-error': {
|
|
218
|
+
color: '#ff1600',
|
|
219
|
+
},
|
|
220
|
+
}}
|
|
221
|
+
render={(Button: any) => (
|
|
222
|
+
<Button className={cn(
|
|
223
|
+
'items-center justify-center font-medium transition-colors focus-visible:outline-none focus-visible:ring-2',
|
|
224
|
+
'focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background',
|
|
225
|
+
'!bg-primary !text-primary-fg hover:!bg-primary-hover font-nav whitespace-nowrap not-typography h-10 py-2 px-4',
|
|
226
|
+
'!text-sm rounded-md lg:min-w-[220px] sm:min-w-[220px] flex'
|
|
227
|
+
)}>
|
|
228
|
+
Pay
|
|
229
|
+
</Button>
|
|
230
|
+
)}
|
|
231
|
+
/>
|
|
232
|
+
{transactionStatus === 'error' && (
|
|
233
|
+
<p className='mx-auto text-destructive'>There was an error processing your payment.</p>
|
|
234
|
+
)}
|
|
235
|
+
</AccordionContent>
|
|
236
|
+
</AccordionItem>
|
|
237
|
+
</Accordion>
|
|
238
|
+
|
|
239
|
+
</>
|
|
240
|
+
)}
|
|
241
|
+
</ApplyTypography>
|
|
242
|
+
</PaymentForm>
|
|
243
|
+
)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export default PayWithCard
|
|
@@ -20,12 +20,12 @@ import {
|
|
|
20
20
|
|
|
21
21
|
import { useAuth } from '@hanzo/auth/service'
|
|
22
22
|
|
|
23
|
-
import Eth from '
|
|
24
|
-
import { useCommerce } from '
|
|
25
|
-
import type { TransactionStatus } from '
|
|
23
|
+
import Eth from '../../icons/eth'
|
|
24
|
+
import { useCommerce } from '../../../../service/context'
|
|
25
|
+
import type { TransactionStatus } from '../../../../types'
|
|
26
26
|
import ContactInfo from './contact-info'
|
|
27
27
|
import type { UseFormReturn } from 'react-hook-form'
|
|
28
|
-
import { sendFBEvent, sendGAEvent } from '
|
|
28
|
+
import { sendFBEvent, sendGAEvent } from '../../../../util/analytics'
|
|
29
29
|
|
|
30
30
|
declare global {
|
|
31
31
|
interface Window{
|
|
@@ -34,7 +34,7 @@ declare global {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
const PayWithCrypto: React.FC<{
|
|
37
|
-
|
|
37
|
+
onDone: () => void
|
|
38
38
|
transactionStatus: TransactionStatus
|
|
39
39
|
setTransactionStatus: (status: TransactionStatus) => void
|
|
40
40
|
storePaymentInfo: (paymentInfo: any) => Promise<void>
|
|
@@ -46,7 +46,7 @@ const PayWithCrypto: React.FC<{
|
|
|
46
46
|
email: string
|
|
47
47
|
}>
|
|
48
48
|
}> = observer(({
|
|
49
|
-
|
|
49
|
+
onDone,
|
|
50
50
|
transactionStatus,
|
|
51
51
|
setTransactionStatus,
|
|
52
52
|
storePaymentInfo,
|
|
@@ -57,7 +57,6 @@ const PayWithCrypto: React.FC<{
|
|
|
57
57
|
const [loadingPrice, setLoadingPrice] = useState(false)
|
|
58
58
|
//const [selectedToken, setSelectedToken] = useState('eth')
|
|
59
59
|
const [amount, setAmount] = useState<number>()
|
|
60
|
-
const [availableAmount, setAvailableAmount] = useState<number>()
|
|
61
60
|
const [provider, setProvider] = useState<ethers.BrowserProvider>()
|
|
62
61
|
|
|
63
62
|
//const selectedToken = 'eth'
|
|
@@ -68,11 +67,6 @@ const PayWithCrypto: React.FC<{
|
|
|
68
67
|
return autorun(() => {
|
|
69
68
|
const newProvider = new ethers.BrowserProvider(window.ethereum)
|
|
70
69
|
setProvider(newProvider)
|
|
71
|
-
if (auth.user?.walletAddress) {
|
|
72
|
-
newProvider.getBalance(auth.user?.walletAddress).then((balance: any) => {
|
|
73
|
-
setAvailableAmount(Number(balance)/(10**18))
|
|
74
|
-
})
|
|
75
|
-
}
|
|
76
70
|
})
|
|
77
71
|
}, [])
|
|
78
72
|
|
|
@@ -109,11 +103,6 @@ const PayWithCrypto: React.FC<{
|
|
|
109
103
|
})
|
|
110
104
|
const newProvider = new ethers.BrowserProvider(window.ethereum)
|
|
111
105
|
setProvider(newProvider)
|
|
112
|
-
if (auth.user?.walletAddress) {
|
|
113
|
-
newProvider.getBalance(auth.user?.walletAddress).then((balance: any) => {
|
|
114
|
-
setAvailableAmount(Number(balance)/(10**18))
|
|
115
|
-
})
|
|
116
|
-
}
|
|
117
106
|
} catch (err) {
|
|
118
107
|
toast('Please switch your wallet to the Ethereum network.')
|
|
119
108
|
return
|
|
@@ -193,7 +182,7 @@ const PayWithCrypto: React.FC<{
|
|
|
193
182
|
await storePaymentInfo({
|
|
194
183
|
paymentMethod: 'crypto'
|
|
195
184
|
})
|
|
196
|
-
|
|
185
|
+
onDone()
|
|
197
186
|
}
|
|
198
187
|
|
|
199
188
|
return (
|
|
@@ -225,7 +214,6 @@ const PayWithCrypto: React.FC<{
|
|
|
225
214
|
</div>
|
|
226
215
|
</div>
|
|
227
216
|
</div>
|
|
228
|
-
<div>Available funds in your wallet: {availableAmount} ETH</div>
|
|
229
217
|
|
|
230
218
|
{transactionStatus === 'error' ? (
|
|
231
219
|
<h4 className='text-destructive'>There was an error while confirming the transaction.</h4>
|
|
@@ -21,10 +21,12 @@ import {
|
|
|
21
21
|
SelectValue
|
|
22
22
|
} from '@hanzo/ui/primitives'
|
|
23
23
|
|
|
24
|
-
import { useCommerce } from '
|
|
24
|
+
import { useCommerce } from '../../..'
|
|
25
25
|
|
|
26
|
-
import
|
|
27
|
-
import { sendGAEvent } from '
|
|
26
|
+
import countries from '../../../util/countries'
|
|
27
|
+
import { sendGAEvent } from '../../../util/analytics'
|
|
28
|
+
|
|
29
|
+
import type { StepComponentProps } from './types'
|
|
28
30
|
|
|
29
31
|
const shippingFormSchema = z.object({
|
|
30
32
|
addressLine1: z.string().min(2, 'Address must be at least 2 characters.'),
|
|
@@ -35,12 +37,9 @@ const shippingFormSchema = z.object({
|
|
|
35
37
|
country: z.string().min(2, 'Country is invalid.'),
|
|
36
38
|
})
|
|
37
39
|
|
|
38
|
-
const ShippingInfo: React.FC<{
|
|
39
|
-
orderId?: string,
|
|
40
|
-
setStep: (currentStep: number) => void
|
|
41
|
-
}> = ({
|
|
40
|
+
const ShippingInfo: React.FC<StepComponentProps> = ({
|
|
42
41
|
orderId,
|
|
43
|
-
|
|
42
|
+
onDone
|
|
44
43
|
}) => {
|
|
45
44
|
const cmmc = useCommerce()
|
|
46
45
|
|
|
@@ -72,7 +71,7 @@ const ShippingInfo: React.FC<{
|
|
|
72
71
|
value: cmmc.cartTotal,
|
|
73
72
|
currency: 'USD',
|
|
74
73
|
})
|
|
75
|
-
|
|
74
|
+
onDone()
|
|
76
75
|
}
|
|
77
76
|
|
|
78
77
|
return (
|
|
@@ -84,9 +83,8 @@ const ShippingInfo: React.FC<{
|
|
|
84
83
|
name='addressLine1'
|
|
85
84
|
render={({ field }) => (
|
|
86
85
|
<FormItem className='space-y-1 w-full'>
|
|
87
|
-
<FormLabel>Address line 1</FormLabel>
|
|
88
86
|
<FormControl>
|
|
89
|
-
<Input {...field} className='border-muted-4'/>
|
|
87
|
+
<Input {...field} className='border-muted-4' placeholder='Address line 1'/>
|
|
90
88
|
</FormControl>
|
|
91
89
|
<FormMessage />
|
|
92
90
|
</FormItem>
|
|
@@ -97,9 +95,8 @@ const ShippingInfo: React.FC<{
|
|
|
97
95
|
name='addressLine2'
|
|
98
96
|
render={({ field }) => (
|
|
99
97
|
<FormItem className='space-y-1 w-full'>
|
|
100
|
-
<FormLabel>Address line 2 (optional)</FormLabel>
|
|
101
98
|
<FormControl>
|
|
102
|
-
<Input {...field} className='border-muted-4'/>
|
|
99
|
+
<Input {...field} className='border-muted-4' placeholder='Address line 2 (optional)'/>
|
|
103
100
|
</FormControl>
|
|
104
101
|
<FormMessage />
|
|
105
102
|
</FormItem>
|
|
@@ -111,9 +108,8 @@ const ShippingInfo: React.FC<{
|
|
|
111
108
|
name='zipCode'
|
|
112
109
|
render={({ field }) => (
|
|
113
110
|
<FormItem className='space-y-1 w-full'>
|
|
114
|
-
<FormLabel>Zip code</FormLabel>
|
|
115
111
|
<FormControl>
|
|
116
|
-
<Input {...field} className='border-muted-4'/>
|
|
112
|
+
<Input {...field} className='border-muted-4' placeholder='Zip code'/>
|
|
117
113
|
</FormControl>
|
|
118
114
|
<FormMessage />
|
|
119
115
|
</FormItem>
|
|
@@ -124,9 +120,8 @@ const ShippingInfo: React.FC<{
|
|
|
124
120
|
name='city'
|
|
125
121
|
render={({ field }) => (
|
|
126
122
|
<FormItem className='space-y-1 w-full'>
|
|
127
|
-
<FormLabel>City</FormLabel>
|
|
128
123
|
<FormControl>
|
|
129
|
-
<Input {...field} className='border-muted-4'/>
|
|
124
|
+
<Input {...field} className='border-muted-4' placeholder='City'/>
|
|
130
125
|
</FormControl>
|
|
131
126
|
<FormMessage />
|
|
132
127
|
</FormItem>
|
|
@@ -139,9 +134,8 @@ const ShippingInfo: React.FC<{
|
|
|
139
134
|
name='state'
|
|
140
135
|
render={({ field }) => (
|
|
141
136
|
<FormItem className='space-y-1 w-full'>
|
|
142
|
-
<FormLabel>State (Optional)</FormLabel>
|
|
143
137
|
<FormControl>
|
|
144
|
-
<Input {...field} className='border-muted-4'/>
|
|
138
|
+
<Input {...field} className='border-muted-4' placeholder='State (optional)'/>
|
|
145
139
|
</FormControl>
|
|
146
140
|
<FormMessage />
|
|
147
141
|
</FormItem>
|
|
@@ -152,11 +146,10 @@ const ShippingInfo: React.FC<{
|
|
|
152
146
|
name='country'
|
|
153
147
|
render={({ field }) => (
|
|
154
148
|
<FormItem className='space-y-1 w-full'>
|
|
155
|
-
<FormLabel>Country</FormLabel>
|
|
156
149
|
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
|
157
150
|
<FormControl>
|
|
158
|
-
<SelectTrigger>
|
|
159
|
-
<SelectValue placeholder='
|
|
151
|
+
<SelectTrigger className='bg-level-1 border-muted-4'>
|
|
152
|
+
<SelectValue placeholder='Country' />
|
|
160
153
|
</SelectTrigger>
|
|
161
154
|
</FormControl>
|
|
162
155
|
<SelectContent>
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import Link from 'next/link'
|
|
3
|
+
|
|
3
4
|
import { ApplyTypography } from '@hanzo/ui/primitives'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
import type { StepComponentProps } from './types'
|
|
7
|
+
|
|
8
|
+
const ThankYou: React.FC<StepComponentProps> = ({}) => (
|
|
6
9
|
<ApplyTypography className='flex flex-col gap-4 text-center mt-10'>
|
|
7
10
|
<h3>Thank you for your order!</h3>
|
|
8
11
|
<h6>Once your payment has been confirmed we will send additional information to your email.</h6>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ComponentType } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface StepComponentProps {
|
|
4
|
+
onDone: () => void
|
|
5
|
+
orderId: string | undefined
|
|
6
|
+
setOrderId: (orderId: string | undefined) => void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface CheckoutStep {
|
|
10
|
+
name: string
|
|
11
|
+
label?: string
|
|
12
|
+
Comp: ComponentType<StepComponentProps>
|
|
13
|
+
}
|
|
14
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/commerce",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.8.0",
|
|
4
4
|
"description": "Library with shopping cart components.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org/",
|
|
@@ -31,14 +31,16 @@
|
|
|
31
31
|
"./types": "./types/index.ts"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
+
"@splinetool/react-spline": "^2.2.6",
|
|
35
|
+
"@splinetool/runtime": "^1.0.75",
|
|
34
36
|
"ethers": "^6.11.1",
|
|
35
37
|
"next-usequerystate": "^1.17.0",
|
|
36
38
|
"react-square-web-payments-sdk": "^3.2.1",
|
|
37
39
|
"square": "^35.0.0"
|
|
38
40
|
},
|
|
39
41
|
"peerDependencies": {
|
|
40
|
-
"@hanzo/auth": "^2.3.
|
|
41
|
-
"@hanzo/ui": "^3.0.
|
|
42
|
+
"@hanzo/auth": "^2.3.2",
|
|
43
|
+
"@hanzo/ui": "^3.0.10",
|
|
42
44
|
"@hookform/resolvers": "^3.3.4",
|
|
43
45
|
"firebase": "^10.8.0",
|
|
44
46
|
"lucide-react": "^0.307.0",
|