@hanzo/commerce 4.4.0 → 4.4.1
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/checkout-panel/index.tsx +6 -14
- package/components/checkout-panel/payment/index.tsx +12 -13
- package/components/checkout-panel/payment/pay-with-card.tsx +2 -2
- package/components/checkout-panel/payment/pay-with-crypto.tsx +118 -111
- package/components/checkout-panel/shipping-info.tsx +27 -61
- package/package.json +1 -1
|
@@ -13,8 +13,7 @@ import {
|
|
|
13
13
|
Toaster
|
|
14
14
|
} from '@hanzo/ui/primitives'
|
|
15
15
|
import { cn } from '@hanzo/ui/util'
|
|
16
|
-
import {
|
|
17
|
-
import { LoginComponent, AuthWidget } from '@hanzo/auth/components'
|
|
16
|
+
import { AuthWidget } from '@hanzo/auth/components'
|
|
18
17
|
|
|
19
18
|
import ShippingInfo from './shipping-info'
|
|
20
19
|
import ThankYou from './thank-you'
|
|
@@ -33,7 +32,6 @@ const CheckoutPanel: React.FC<{
|
|
|
33
32
|
setOpen
|
|
34
33
|
}) => {
|
|
35
34
|
|
|
36
|
-
const auth = useAuth()
|
|
37
35
|
const cmmc = useCommerce()
|
|
38
36
|
|
|
39
37
|
const [step, setStep] = useState<number>(1)
|
|
@@ -43,17 +41,11 @@ const CheckoutPanel: React.FC<{
|
|
|
43
41
|
1: {
|
|
44
42
|
label: 'Payment',
|
|
45
43
|
element: (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
/>
|
|
52
|
-
) : (
|
|
53
|
-
<LoginComponent hideHeader className='max-w-[20rem] mx-auto' inputClassName='border-muted-4'>
|
|
54
|
-
<h6 className='font-nav'>Please login to proceed</h6>
|
|
55
|
-
</LoginComponent >
|
|
56
|
-
)
|
|
44
|
+
<Payment
|
|
45
|
+
orderId={orderId}
|
|
46
|
+
setOrderId={setOrderId}
|
|
47
|
+
setStep={setStep}
|
|
48
|
+
/>
|
|
57
49
|
)}
|
|
58
50
|
,
|
|
59
51
|
2: {
|
|
@@ -49,16 +49,14 @@ const Payment: React.FC<{
|
|
|
49
49
|
}, [auth.loggedIn])
|
|
50
50
|
|
|
51
51
|
const storePaymentInfo = async (paymentInfo: any) => {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
await cmmc.updateOrderPaymentInfo(id, paymentInfo)
|
|
61
|
-
}
|
|
52
|
+
const {name, email} = contactForm.getValues()
|
|
53
|
+
let id
|
|
54
|
+
if (!orderId) {
|
|
55
|
+
id = await cmmc.createOrder(email, name)
|
|
56
|
+
setOrderId(id)
|
|
57
|
+
}
|
|
58
|
+
if (id) {
|
|
59
|
+
await cmmc.updateOrderPaymentInfo(id, paymentInfo)
|
|
62
60
|
}
|
|
63
61
|
}
|
|
64
62
|
|
|
@@ -67,21 +65,21 @@ const Payment: React.FC<{
|
|
|
67
65
|
<TabsList className='grid w-full grid-cols-3 mx-auto bg-level-2 h-auto'>
|
|
68
66
|
<TabsTrigger
|
|
69
67
|
value='card'
|
|
70
|
-
className='
|
|
68
|
+
className='whitespace-normal h-full text-sm sm:text-base'
|
|
71
69
|
disabled={transactionStatus === 'paid' || transactionStatus === 'confirmed'}
|
|
72
70
|
>
|
|
73
71
|
PAY WITH CARD
|
|
74
72
|
</TabsTrigger>
|
|
75
73
|
<TabsTrigger
|
|
76
74
|
value='crypto'
|
|
77
|
-
className='
|
|
75
|
+
className='whitespace-normal h-full text-sm sm:text-base'
|
|
78
76
|
disabled={transactionStatus === 'paid' || transactionStatus === 'confirmed'}
|
|
79
77
|
>
|
|
80
78
|
PAY WITH CRYPTO
|
|
81
79
|
</TabsTrigger>
|
|
82
80
|
<TabsTrigger
|
|
83
81
|
value='bank'
|
|
84
|
-
className='
|
|
82
|
+
className='whitespace-normal h-full text-sm sm:text-base'
|
|
85
83
|
disabled={transactionStatus === 'paid' || transactionStatus === 'confirmed'}
|
|
86
84
|
>
|
|
87
85
|
BANK TRANSFER
|
|
@@ -102,6 +100,7 @@ const Payment: React.FC<{
|
|
|
102
100
|
transactionStatus={transactionStatus}
|
|
103
101
|
setTransactionStatus={setTransactionStatus}
|
|
104
102
|
storePaymentInfo={storePaymentInfo}
|
|
103
|
+
contactForm={contactForm}
|
|
105
104
|
/>
|
|
106
105
|
</TabsContent>
|
|
107
106
|
<TabsContent value='bank'>
|
|
@@ -112,13 +112,13 @@ const PayWithCard: React.FC<{
|
|
|
112
112
|
>
|
|
113
113
|
<ApplyTypography className='flex flex-col gap-2 mt-6'>
|
|
114
114
|
{transactionStatus === 'paid' ? (
|
|
115
|
+
<h6 className='mx-auto font-nav'>Processing your payment...</h6>
|
|
116
|
+
) : transactionStatus === 'confirmed' ? (
|
|
115
117
|
<div className='flex flex-col gap-4'>
|
|
116
118
|
<h5 className='mx-auto font-nav'>Payment confirmed!</h5>
|
|
117
119
|
<p className='mx-auto'>Thank you for your purchase.</p>
|
|
118
120
|
<Button onClick={() => setStep(2)}>Continue</Button>
|
|
119
121
|
</div>
|
|
120
|
-
) : transactionStatus === 'confirmed' ? (
|
|
121
|
-
<h6 className='mx-auto font-nav'>Processing your payment...</h6>
|
|
122
122
|
) : (
|
|
123
123
|
<>
|
|
124
124
|
<GooglePay/>
|
|
@@ -24,6 +24,9 @@ import { Ethereum as EthIconFromAuth } from '@hanzo/auth/icons'
|
|
|
24
24
|
import Eth from '../icons/eth'
|
|
25
25
|
import { useCommerce } from '../../../service/context'
|
|
26
26
|
import type { TransactionStatus } from '../../../types'
|
|
27
|
+
import ContactInfo from './contact-info'
|
|
28
|
+
import type { UseFormReturn } from 'react-hook-form'
|
|
29
|
+
import { LoginComponent } from '@hanzo/auth/components'
|
|
27
30
|
|
|
28
31
|
declare global {
|
|
29
32
|
interface Window{
|
|
@@ -36,11 +39,19 @@ const PayWithCrypto: React.FC<{
|
|
|
36
39
|
transactionStatus: TransactionStatus
|
|
37
40
|
setTransactionStatus: (status: TransactionStatus) => void
|
|
38
41
|
storePaymentInfo: (paymentInfo: any) => Promise<void>
|
|
42
|
+
contactForm: UseFormReturn<{
|
|
43
|
+
name: string
|
|
44
|
+
email: string
|
|
45
|
+
}, any, {
|
|
46
|
+
name: string
|
|
47
|
+
email: string
|
|
48
|
+
}>
|
|
39
49
|
}> = observer(({
|
|
40
50
|
setStep,
|
|
41
51
|
transactionStatus,
|
|
42
52
|
setTransactionStatus,
|
|
43
|
-
storePaymentInfo
|
|
53
|
+
storePaymentInfo,
|
|
54
|
+
contactForm
|
|
44
55
|
}) => {
|
|
45
56
|
const c = useCommerce()
|
|
46
57
|
const auth = useAuth()
|
|
@@ -90,61 +101,69 @@ const PayWithCrypto: React.FC<{
|
|
|
90
101
|
}, [c.cartTotal])
|
|
91
102
|
|
|
92
103
|
const sendPayment = async (ether: number) => {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const newProvider = new ethers.BrowserProvider(window.ethereum)
|
|
100
|
-
setProvider(newProvider)
|
|
101
|
-
if (auth.user?.walletAddress) {
|
|
102
|
-
newProvider.getBalance(auth.user?.walletAddress).then((balance: any) => {
|
|
103
|
-
setAvailableAmount(Number(balance)/(10**18))
|
|
104
|
+
contactForm.handleSubmit(async () => {
|
|
105
|
+
// Check that we are on ethereum network
|
|
106
|
+
try {
|
|
107
|
+
await window.ethereum.request({
|
|
108
|
+
method: 'wallet_switchEthereumChain',
|
|
109
|
+
params: [{ chainId: "0x1"}],
|
|
104
110
|
})
|
|
111
|
+
const newProvider = new ethers.BrowserProvider(window.ethereum)
|
|
112
|
+
setProvider(newProvider)
|
|
113
|
+
if (auth.user?.walletAddress) {
|
|
114
|
+
newProvider.getBalance(auth.user?.walletAddress).then((balance: any) => {
|
|
115
|
+
setAvailableAmount(Number(balance)/(10**18))
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
} catch (err) {
|
|
119
|
+
toast({title: 'Please switch your wallet to the Ethereum network.'})
|
|
120
|
+
return
|
|
105
121
|
}
|
|
106
|
-
} catch (err) {
|
|
107
|
-
toast({title: 'Please switch your wallet to the Ethereum network.'})
|
|
108
|
-
return
|
|
109
|
-
}
|
|
110
122
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
})
|
|
125
|
-
console.log({ ether, addr: process.env.NEXT_PUBLIC_ETH_PAYMENT_ADDRESS })
|
|
126
|
-
console.log('tx', tx)
|
|
127
|
-
setTransactionStatus('paid')
|
|
128
|
-
|
|
129
|
-
provider.waitForTransaction(tx.hash)
|
|
130
|
-
.then(async (receipt) => {
|
|
131
|
-
console.log(receipt)
|
|
132
|
-
await storePaymentInfo({
|
|
133
|
-
ether,
|
|
134
|
-
addr: process.env.NEXT_PUBLIC_ETH_PAYMENT_ADDRESS,
|
|
135
|
-
receipt
|
|
136
|
-
})
|
|
137
|
-
setTransactionStatus('confirmed')
|
|
123
|
+
try {
|
|
124
|
+
if (!provider) {
|
|
125
|
+
// :aa TODO string table
|
|
126
|
+
throw new Error('No crypto wallet found. Please install it.')
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
await window.ethereum.send('eth_requestAccounts')
|
|
130
|
+
|
|
131
|
+
const signer = await provider.getSigner()
|
|
132
|
+
ethers.getAddress(process.env.NEXT_PUBLIC_ETH_PAYMENT_ADDRESS ?? '')
|
|
133
|
+
const tx = await signer.sendTransaction({
|
|
134
|
+
to: process.env.NEXT_PUBLIC_ETH_PAYMENT_ADDRESS,
|
|
135
|
+
value: ethers.parseEther(ether.toString())
|
|
138
136
|
})
|
|
139
|
-
.
|
|
140
|
-
|
|
141
|
-
|
|
137
|
+
console.log({ ether, addr: process.env.NEXT_PUBLIC_ETH_PAYMENT_ADDRESS })
|
|
138
|
+
console.log('tx', tx)
|
|
139
|
+
setTransactionStatus('paid')
|
|
140
|
+
await storePaymentInfo({
|
|
141
|
+
ether,
|
|
142
|
+
transactionHash: tx.hash,
|
|
143
|
+
to: process.env.NEXT_PUBLIC_ETH_PAYMENT_ADDRESS,
|
|
144
|
+
paymentMethod: 'crypto'
|
|
142
145
|
})
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
|
|
147
|
+
provider.waitForTransaction(tx.hash)
|
|
148
|
+
.then(async (receipt) => {
|
|
149
|
+
console.log(receipt)
|
|
150
|
+
await storePaymentInfo({
|
|
151
|
+
ether,
|
|
152
|
+
addr: process.env.NEXT_PUBLIC_ETH_PAYMENT_ADDRESS,
|
|
153
|
+
receipt
|
|
154
|
+
})
|
|
155
|
+
setTransactionStatus('confirmed')
|
|
156
|
+
})
|
|
157
|
+
.catch((error) => {
|
|
158
|
+
console.error(error)
|
|
159
|
+
setTransactionStatus('error')
|
|
160
|
+
})
|
|
161
|
+
} catch (err) {
|
|
162
|
+
console.log(err)
|
|
163
|
+
// :aa TODO string table
|
|
164
|
+
toast({title: 'Not enough funds in your wallet'})
|
|
165
|
+
}
|
|
166
|
+
})()
|
|
148
167
|
}
|
|
149
168
|
|
|
150
169
|
const nextStep = async () => {
|
|
@@ -154,69 +173,57 @@ const PayWithCrypto: React.FC<{
|
|
|
154
173
|
setStep(2)
|
|
155
174
|
}
|
|
156
175
|
|
|
157
|
-
|
|
158
|
-
<div className='
|
|
159
|
-
<
|
|
160
|
-
|
|
161
|
-
className='
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
</
|
|
174
|
-
<
|
|
175
|
-
<
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
value={amount ? amount/(10**18) : amount}
|
|
185
|
-
contentEditable={false}
|
|
186
|
-
className='border-muted-4'
|
|
187
|
-
/>
|
|
188
|
-
<div className='relative flex items-center gap-2 -top-[32px] justify-end px-2 py-1 rounded-lg bg-muted-4 w-fit text-xs float-right mr-3'>
|
|
189
|
-
<Eth height={10}/>
|
|
190
|
-
ETH
|
|
176
|
+
return (
|
|
177
|
+
<div className='flex flex-col gap-6 mt-6'>
|
|
178
|
+
<div className='flex flex-col gap-2 w-full'>
|
|
179
|
+
<ContactInfo form={contactForm}/>
|
|
180
|
+
<div className='flex gap-2 grid grid-cols-3'>
|
|
181
|
+
<Select onValueChange={(token) => {/*ONLY ETH setSelectedToken(token) */}} defaultValue='eth'>
|
|
182
|
+
<SelectTrigger>
|
|
183
|
+
<SelectValue defaultValue='eth' className='border-muted-4'/>
|
|
184
|
+
</SelectTrigger>
|
|
185
|
+
<SelectContent>
|
|
186
|
+
<SelectGroup>
|
|
187
|
+
<SelectItem value='eth'><div className='flex items-center gap-2'><Eth height={14}/>ETH</div></SelectItem>
|
|
188
|
+
{/* <SelectItem value='btc' ><div className='flex items-center gap-2'><Btc height={14}/>BTC</div></SelectItem>
|
|
189
|
+
<SelectItem value='usdt' ><div className='flex items-center gap-2'><Usdt height={14}/>USDT</div></SelectItem> */}
|
|
190
|
+
</SelectGroup>
|
|
191
|
+
</SelectContent>
|
|
192
|
+
</Select>
|
|
193
|
+
<div className='col-span-2'>
|
|
194
|
+
<Input
|
|
195
|
+
value={amount ? amount/(10**18) : amount}
|
|
196
|
+
contentEditable={false}
|
|
197
|
+
className='border-muted-4'
|
|
198
|
+
/>
|
|
199
|
+
<div className='relative flex items-center gap-2 -top-[32px] justify-end px-2 py-1 rounded-lg bg-muted-4 w-fit text-xs float-right mr-3'>
|
|
200
|
+
<Eth height={10}/>
|
|
201
|
+
ETH
|
|
202
|
+
</div>
|
|
191
203
|
</div>
|
|
192
204
|
</div>
|
|
205
|
+
<div>Available funds in your wallet: {availableAmount} ETH</div>
|
|
206
|
+
|
|
207
|
+
{transactionStatus === 'error' ? (
|
|
208
|
+
<h4 className='text-destructive'>There was an error while confirming the transaction.</h4>
|
|
209
|
+
) : transactionStatus === 'paid' ? (
|
|
210
|
+
<h4>Waiting for transaction to be confirmed on chain.</h4>
|
|
211
|
+
) : transactionStatus === 'confirmed' ? (
|
|
212
|
+
<h4>Transaction confirmed!</h4>
|
|
213
|
+
) : null}
|
|
214
|
+
|
|
215
|
+
{transactionStatus === 'unpaid' ? (
|
|
216
|
+
<Button
|
|
217
|
+
onClick={() => sendPayment(amount ? amount/(10**18) : 0)}
|
|
218
|
+
className='mx-auto w-full'
|
|
219
|
+
disabled={loadingPrice}
|
|
220
|
+
>
|
|
221
|
+
Pay
|
|
222
|
+
</Button>
|
|
223
|
+
) : (
|
|
224
|
+
<Button onClick={nextStep} className='mx-auto w-full'>Continue</Button>
|
|
225
|
+
)}
|
|
193
226
|
</div>
|
|
194
|
-
<div>Available funds in your wallet: {availableAmount} ETH</div>
|
|
195
|
-
|
|
196
|
-
{transactionStatus === 'error' ? (
|
|
197
|
-
<h4 className='text-destructive'>There was an error while confirming the transaction.</h4>
|
|
198
|
-
) : transactionStatus === 'paid' ? (
|
|
199
|
-
<h4>Waiting for transaction to be confirmed on chain.</h4>
|
|
200
|
-
) : transactionStatus === 'confirmed' ? (
|
|
201
|
-
<h4>Transaction confirmed!</h4>
|
|
202
|
-
) : null}
|
|
203
|
-
</div>
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
return (
|
|
207
|
-
<div className='flex flex-col gap-6 mt-6'>
|
|
208
|
-
{cryptoPriceWidget}
|
|
209
|
-
{transactionStatus === 'unpaid' ? (
|
|
210
|
-
<Button
|
|
211
|
-
onClick={() => sendPayment(amount ? amount/(10**18) : 0)}
|
|
212
|
-
className='mx-auto w-full'
|
|
213
|
-
disabled={loadingPrice}
|
|
214
|
-
>
|
|
215
|
-
Pay
|
|
216
|
-
</Button>
|
|
217
|
-
) : (
|
|
218
|
-
<Button onClick={nextStep} className='mx-auto w-full'>Continue</Button>
|
|
219
|
-
)}
|
|
220
227
|
</div>
|
|
221
228
|
)
|
|
222
229
|
})
|
|
@@ -28,8 +28,6 @@ import { useCommerce } from '../..'
|
|
|
28
28
|
import { countries } from './countries'
|
|
29
29
|
|
|
30
30
|
const shippingFormSchema = z.object({
|
|
31
|
-
firstName: z.string().min(2, 'First name must be at least 2 characters.'),
|
|
32
|
-
lastName: z.string().min(2, 'Last name must be at least 2 characters.'),
|
|
33
31
|
addressLine1: z.string().min(2, 'Address must be at least 2 characters.'),
|
|
34
32
|
addressLine2: z.string().optional(),
|
|
35
33
|
zipCode: z.string().min(2, 'Zip code is invalid.'),
|
|
@@ -51,8 +49,6 @@ const ShippingInfo: React.FC<{
|
|
|
51
49
|
const shippingForm = useForm<z.infer<typeof shippingFormSchema>>({
|
|
52
50
|
resolver: zodResolver(shippingFormSchema),
|
|
53
51
|
defaultValues: {
|
|
54
|
-
firstName: '',
|
|
55
|
-
lastName: '',
|
|
56
52
|
addressLine1: '',
|
|
57
53
|
addressLine2: '',
|
|
58
54
|
zipCode: '',
|
|
@@ -63,7 +59,7 @@ const ShippingInfo: React.FC<{
|
|
|
63
59
|
})
|
|
64
60
|
|
|
65
61
|
const onSubmit = async (values: z.infer<typeof shippingFormSchema>) => {
|
|
66
|
-
if (
|
|
62
|
+
if (orderId) {
|
|
67
63
|
await cmmc.updateOrderShippingInfo(orderId, values)
|
|
68
64
|
}
|
|
69
65
|
setStep(3)
|
|
@@ -73,62 +69,32 @@ const ShippingInfo: React.FC<{
|
|
|
73
69
|
<Form {...shippingForm}>
|
|
74
70
|
<form onSubmit={shippingForm.handleSubmit(onSubmit)} className='text-left'>
|
|
75
71
|
<div className='flex flex-col gap-4'>
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
<
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
/>
|
|
103
|
-
</div>
|
|
104
|
-
<div className='flex gap-4 items-end'>
|
|
105
|
-
<FormField
|
|
106
|
-
control={shippingForm.control}
|
|
107
|
-
name='addressLine1'
|
|
108
|
-
render={({ field }) => (
|
|
109
|
-
<FormItem className='space-y-1 w-full'>
|
|
110
|
-
<FormLabel>Address line 1</FormLabel>
|
|
111
|
-
<FormControl>
|
|
112
|
-
<Input {...field} className='border-muted-4'/>
|
|
113
|
-
</FormControl>
|
|
114
|
-
<FormMessage />
|
|
115
|
-
</FormItem>
|
|
116
|
-
)}
|
|
117
|
-
/>
|
|
118
|
-
<FormField
|
|
119
|
-
control={shippingForm.control}
|
|
120
|
-
name='addressLine2'
|
|
121
|
-
render={({ field }) => (
|
|
122
|
-
<FormItem className='space-y-1 w-full'>
|
|
123
|
-
<FormLabel>Address line 2 (optional)</FormLabel>
|
|
124
|
-
<FormControl>
|
|
125
|
-
<Input {...field} className='border-muted-4'/>
|
|
126
|
-
</FormControl>
|
|
127
|
-
<FormMessage />
|
|
128
|
-
</FormItem>
|
|
129
|
-
)}
|
|
130
|
-
/>
|
|
131
|
-
</div>
|
|
72
|
+
<FormField
|
|
73
|
+
control={shippingForm.control}
|
|
74
|
+
name='addressLine1'
|
|
75
|
+
render={({ field }) => (
|
|
76
|
+
<FormItem className='space-y-1 w-full'>
|
|
77
|
+
<FormLabel>Address line 1</FormLabel>
|
|
78
|
+
<FormControl>
|
|
79
|
+
<Input {...field} className='border-muted-4'/>
|
|
80
|
+
</FormControl>
|
|
81
|
+
<FormMessage />
|
|
82
|
+
</FormItem>
|
|
83
|
+
)}
|
|
84
|
+
/>
|
|
85
|
+
<FormField
|
|
86
|
+
control={shippingForm.control}
|
|
87
|
+
name='addressLine2'
|
|
88
|
+
render={({ field }) => (
|
|
89
|
+
<FormItem className='space-y-1 w-full'>
|
|
90
|
+
<FormLabel>Address line 2 (optional)</FormLabel>
|
|
91
|
+
<FormControl>
|
|
92
|
+
<Input {...field} className='border-muted-4'/>
|
|
93
|
+
</FormControl>
|
|
94
|
+
<FormMessage />
|
|
95
|
+
</FormItem>
|
|
96
|
+
)}
|
|
97
|
+
/>
|
|
132
98
|
<div className='flex gap-4 items-end'>
|
|
133
99
|
<FormField
|
|
134
100
|
control={shippingForm.control}
|