@hanzo/commerce 4.4.2 → 4.6.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 +46 -1
- package/components/buy-item/select-category-item-card.tsx +4 -5
- package/components/cart-panel/index.tsx +27 -1
- package/components/category-item-scroll-selector.tsx +40 -0
- package/components/checkout-panel/index.tsx +5 -2
- package/components/checkout-panel/payment/index.tsx +21 -0
- package/components/checkout-panel/payment/pay-with-card.tsx +23 -0
- package/components/checkout-panel/payment/pay-with-crypto.tsx +30 -7
- package/components/checkout-panel/shipping-info.tsx +13 -3
- package/components/facet-values-widget/facet-image.tsx +5 -39
- package/components/index.ts +1 -1
- package/components/select-category-item-panel.tsx +4 -5
- package/package.json +3 -4
- package/util/analytics.ts +21 -0
- package/components/category-item-ios-wheel-selector.tsx +0 -62
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
import React from 'react'
|
|
3
3
|
import { observer } from 'mobx-react-lite'
|
|
4
4
|
|
|
5
|
-
import { Button, type ButtonSizes } from '@hanzo/ui/primitives'
|
|
5
|
+
import { Button, toast, type ButtonSizes } from '@hanzo/ui/primitives'
|
|
6
6
|
import { cn } from '@hanzo/ui/util'
|
|
7
7
|
|
|
8
8
|
import { Icons } from './Icons'
|
|
9
9
|
import type { LineItem } from '../types'
|
|
10
|
+
import { sendFBEvent, sendGAEvent } from '../util/analytics'
|
|
10
11
|
|
|
11
12
|
const AddToCartWidget: React.FC<{
|
|
12
13
|
item: LineItem
|
|
@@ -44,6 +45,33 @@ const AddToCartWidget: React.FC<{
|
|
|
44
45
|
if (onQuantityChanged) {
|
|
45
46
|
onQuantityChanged(item.sku, old, old + 1)
|
|
46
47
|
}
|
|
48
|
+
if (old === 0) {
|
|
49
|
+
toast(`Added ${item.title} to your bag.`)
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
toast(`Changed quantity to ${old + 1} for ${item.title}.`)
|
|
53
|
+
}
|
|
54
|
+
sendGAEvent('add_to_cart', {
|
|
55
|
+
items: [{
|
|
56
|
+
item_id: item.sku,
|
|
57
|
+
item_name: item.title,
|
|
58
|
+
item_category: item.categoryId,
|
|
59
|
+
price: item.price,
|
|
60
|
+
quantity: item.quantity
|
|
61
|
+
}],
|
|
62
|
+
value: item.price,
|
|
63
|
+
currency: 'USD',
|
|
64
|
+
})
|
|
65
|
+
sendFBEvent('AddToCart', {
|
|
66
|
+
content_ids: [item.sku],
|
|
67
|
+
contents: [{
|
|
68
|
+
id: item.sku,
|
|
69
|
+
quantity: item.quantity
|
|
70
|
+
}],
|
|
71
|
+
content_name: item.title,
|
|
72
|
+
value: item.price,
|
|
73
|
+
currency: 'USD',
|
|
74
|
+
})
|
|
47
75
|
}
|
|
48
76
|
|
|
49
77
|
const dec = () => {
|
|
@@ -52,6 +80,23 @@ const AddToCartWidget: React.FC<{
|
|
|
52
80
|
if (onQuantityChanged) {
|
|
53
81
|
onQuantityChanged(item.sku, old, old - 1)
|
|
54
82
|
}
|
|
83
|
+
if (old === 1) {
|
|
84
|
+
toast(`Removed ${item.title} from your bag.`)
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
toast(`Changed quantity to ${old - 1} for ${item.title}.`)
|
|
88
|
+
}
|
|
89
|
+
sendGAEvent('remove_from_cart', {
|
|
90
|
+
items: [{
|
|
91
|
+
item_id: item.sku,
|
|
92
|
+
item_name: item.title,
|
|
93
|
+
item_category: item.categoryId,
|
|
94
|
+
price: item.price,
|
|
95
|
+
quantity: item.quantity
|
|
96
|
+
}],
|
|
97
|
+
value: item.price,
|
|
98
|
+
currency: 'USD',
|
|
99
|
+
})
|
|
55
100
|
}
|
|
56
101
|
|
|
57
102
|
return ( item.isInCart ? (
|
|
@@ -9,7 +9,7 @@ import type { ItemSelector, LineItem } from '../../types'
|
|
|
9
9
|
|
|
10
10
|
import AddToCartWidget from '../add-to-cart-widget'
|
|
11
11
|
import CategoryItemRadioSelector from '../category-item-radio-selector'
|
|
12
|
-
import
|
|
12
|
+
import CategoryItemScrollSelector from '../category-item-scroll-selector'
|
|
13
13
|
import { formatPrice } from '../../util'
|
|
14
14
|
|
|
15
15
|
const SelectCategoryItemCard: React.FC<React.HTMLAttributes<HTMLDivElement> & ItemSelector & {
|
|
@@ -52,13 +52,12 @@ const SelectCategoryItemCard: React.FC<React.HTMLAttributes<HTMLDivElement> & It
|
|
|
52
52
|
)}>
|
|
53
53
|
{!noTitle && (<div className={'h-[1px] bg-muted-3 ' + (mobilePicker ? 'w-pr-55' : 'w-pr-60') } /> )}
|
|
54
54
|
{mobilePicker ? (
|
|
55
|
-
<
|
|
55
|
+
<CategoryItemScrollSelector
|
|
56
56
|
category={category}
|
|
57
57
|
selectedItemRef={selItemRef}
|
|
58
58
|
selectSku={selectSku}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
outerClx='w-full'
|
|
59
|
+
itemClx='h-10 border-b px-4'
|
|
60
|
+
outerClx='min-w-pr-80 h-[180px]' // 80% of 65% parent
|
|
62
61
|
/>
|
|
63
62
|
) : (
|
|
64
63
|
<CategoryItemRadioSelector
|
|
@@ -9,6 +9,7 @@ import { useCommerce } from '../../service/context'
|
|
|
9
9
|
import { formatPrice } from '../../util'
|
|
10
10
|
|
|
11
11
|
import CartLineItem from './cart-line-item'
|
|
12
|
+
import { sendFBEvent, sendGAEvent } from '../../util/analytics'
|
|
12
13
|
|
|
13
14
|
const CartPanel: React.FC<PropsWithChildren & {
|
|
14
15
|
className?: string
|
|
@@ -31,6 +32,31 @@ const CartPanel: React.FC<PropsWithChildren & {
|
|
|
31
32
|
return <div />
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
const showCheckout = () => {
|
|
36
|
+
sendGAEvent('begin_checkout', {
|
|
37
|
+
currency: 'USD',
|
|
38
|
+
value: cmmc.cartTotal,
|
|
39
|
+
items: cmmc.cartItems.map((item) => ({
|
|
40
|
+
item_id: item.sku,
|
|
41
|
+
item_name: item.title,
|
|
42
|
+
item_category: item.categoryId,
|
|
43
|
+
price: item.price,
|
|
44
|
+
quantity: item.quantity
|
|
45
|
+
})),
|
|
46
|
+
})
|
|
47
|
+
sendFBEvent('InitiateCheckout', {
|
|
48
|
+
content_ids: cmmc.cartItems.map((item) => item.sku),
|
|
49
|
+
contents: cmmc.cartItems.map(item => ({
|
|
50
|
+
id: item.sku,
|
|
51
|
+
quantity: item.quantity
|
|
52
|
+
})),
|
|
53
|
+
num_items: cmmc.cartItems.length,
|
|
54
|
+
value: cmmc.cartTotal,
|
|
55
|
+
currency: 'USD',
|
|
56
|
+
})
|
|
57
|
+
onCheckoutOpen && onCheckoutOpen()
|
|
58
|
+
}
|
|
59
|
+
|
|
34
60
|
return (
|
|
35
61
|
<div className={cn('border p-4 rounded-lg', className)}>
|
|
36
62
|
{children}
|
|
@@ -50,7 +76,7 @@ const CartPanel: React.FC<PropsWithChildren & {
|
|
|
50
76
|
variant='primary'
|
|
51
77
|
rounded='lg'
|
|
52
78
|
className='mt-12 mx-auto w-full'
|
|
53
|
-
onClick={
|
|
79
|
+
onClick={showCheckout}
|
|
54
80
|
>
|
|
55
81
|
Checkout
|
|
56
82
|
</Button>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { observer } from 'mobx-react-lite'
|
|
4
|
+
|
|
5
|
+
import { cn } from '@hanzo/ui/util'
|
|
6
|
+
import { ScrollArea } from '@hanzo/ui/primitives'
|
|
7
|
+
|
|
8
|
+
import type { ItemSelector } from '../types'
|
|
9
|
+
import { formatPrice } from '../util'
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
const CategoryItemScrollSelector: React.FC<ItemSelector & {
|
|
13
|
+
outerClx?: string
|
|
14
|
+
itemClx?: string
|
|
15
|
+
}> = observer(({
|
|
16
|
+
category,
|
|
17
|
+
selectedItemRef: iRef,
|
|
18
|
+
selectSku,
|
|
19
|
+
outerClx='',
|
|
20
|
+
itemClx=''
|
|
21
|
+
}) => {
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<ScrollArea className={cn('border border-muted-4 rounded-lg', outerClx)}>
|
|
25
|
+
{category.products.map((prod) => (
|
|
26
|
+
<div key={prod.sku} onClick={() => {selectSku(prod.sku)}}>
|
|
27
|
+
<div className={cn(
|
|
28
|
+
'h-10 border-b flex flex-col justify-center px-4',
|
|
29
|
+
(iRef.item?.sku === prod.sku) ? 'font-semibold text-accent' : 'text-muted',
|
|
30
|
+
itemClx
|
|
31
|
+
)}>
|
|
32
|
+
{prod.titleAsOption + ', ' + formatPrice(prod.price)}
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
))}
|
|
36
|
+
</ScrollArea>
|
|
37
|
+
)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
export default CategoryItemScrollSelector
|
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
AccordionTrigger,
|
|
11
11
|
Dialog,
|
|
12
12
|
DialogPortal,
|
|
13
|
-
Toaster
|
|
14
13
|
} from '@hanzo/ui/primitives'
|
|
15
14
|
import { cn } from '@hanzo/ui/util'
|
|
16
15
|
import { AuthWidget } from '@hanzo/auth/components'
|
|
@@ -33,6 +32,11 @@ const CheckoutPanel: React.FC<{
|
|
|
33
32
|
}) => {
|
|
34
33
|
|
|
35
34
|
const cmmc = useCommerce()
|
|
35
|
+
|
|
36
|
+
// For sites that don't initialize cmmc
|
|
37
|
+
if (!cmmc) {
|
|
38
|
+
return <></>
|
|
39
|
+
}
|
|
36
40
|
|
|
37
41
|
const [step, setStep] = useState<number>(1)
|
|
38
42
|
const [orderId, setOrderId] = useState<string>()
|
|
@@ -100,7 +104,6 @@ const CheckoutPanel: React.FC<{
|
|
|
100
104
|
</div>
|
|
101
105
|
<div className='bg-level-1 flex flex-row items-start justify-start md:overflow-y-auto'>
|
|
102
106
|
<div className='h-full w-full max-w-[750px] relative flex flex-col gap-8 sm:gap-14 px-8 pb-6 pt-6 md:pt-14'>
|
|
103
|
-
<Toaster/>
|
|
104
107
|
<AuthWidget hideLogin className='hidden md:flex absolute top-4 right-4 '/>
|
|
105
108
|
<StepIndicator steps={steps} currentStep={step} className='flex gap-2 mx-auto items-center text-xxs sm:text-base' />
|
|
106
109
|
{steps[step].element}
|
|
@@ -14,6 +14,7 @@ import PayWithBankTransfer from './pay-with-bank-transfer'
|
|
|
14
14
|
import PayWithCard from './pay-with-card'
|
|
15
15
|
import { useCommerce } from '../../../service/context'
|
|
16
16
|
import type { TransactionStatus } from '../../../types'
|
|
17
|
+
import { sendFBEvent, sendGAEvent } from '../../../util/analytics'
|
|
17
18
|
|
|
18
19
|
const contactFormSchema = z.object({
|
|
19
20
|
name: z.string().min(1, 'Enter your full name.'),
|
|
@@ -56,6 +57,26 @@ const Payment: React.FC<{
|
|
|
56
57
|
setOrderId(id)
|
|
57
58
|
}
|
|
58
59
|
if (id) {
|
|
60
|
+
sendGAEvent('add_payment_info', {
|
|
61
|
+
items: cmmc.cartItems.map((item) => ({
|
|
62
|
+
item_id: item.sku,
|
|
63
|
+
item_name: item.title,
|
|
64
|
+
item_category: item.categoryId,
|
|
65
|
+
price: item.price,
|
|
66
|
+
quantity: item.quantity
|
|
67
|
+
})),
|
|
68
|
+
value: cmmc.cartTotal,
|
|
69
|
+
currency: 'USD',
|
|
70
|
+
payment_type: paymentInfo.paymentMethod ?? ''
|
|
71
|
+
})
|
|
72
|
+
sendFBEvent('AddPaymentInfo', {
|
|
73
|
+
contents: cmmc.cartItems.map(item => ({
|
|
74
|
+
id: item.sku,
|
|
75
|
+
quantity: item.quantity
|
|
76
|
+
})),
|
|
77
|
+
value: cmmc.cartTotal,
|
|
78
|
+
currency: 'USD'
|
|
79
|
+
})
|
|
59
80
|
await cmmc.updateOrderPaymentInfo(id, paymentInfo)
|
|
60
81
|
}
|
|
61
82
|
}
|
|
@@ -13,6 +13,7 @@ import type { TransactionStatus } from '../../../types'
|
|
|
13
13
|
|
|
14
14
|
import PaymentMethods from './payment-methods'
|
|
15
15
|
import ContactInfo from './contact-info'
|
|
16
|
+
import { sendFBEvent, sendGAEvent } from '../../../util/analytics'
|
|
16
17
|
|
|
17
18
|
const PayWithCard: React.FC<{
|
|
18
19
|
setStep: (currentStep: number) => void
|
|
@@ -46,6 +47,28 @@ const PayWithCard: React.FC<{
|
|
|
46
47
|
console.log(token)
|
|
47
48
|
await storePaymentInfo({paymentMethod: token.details.method ?? null, processed: res})
|
|
48
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.cartTotal,
|
|
70
|
+
currency: 'USD',
|
|
71
|
+
})
|
|
49
72
|
} else {
|
|
50
73
|
setTransactionStatus('error')
|
|
51
74
|
}
|
|
@@ -19,14 +19,13 @@ import {
|
|
|
19
19
|
} from '@hanzo/ui/primitives'
|
|
20
20
|
|
|
21
21
|
import { useAuth } from '@hanzo/auth/service'
|
|
22
|
-
import { Ethereum as EthIconFromAuth } from '@hanzo/auth/icons'
|
|
23
22
|
|
|
24
23
|
import Eth from '../icons/eth'
|
|
25
24
|
import { useCommerce } from '../../../service/context'
|
|
26
25
|
import type { TransactionStatus } from '../../../types'
|
|
27
26
|
import ContactInfo from './contact-info'
|
|
28
27
|
import type { UseFormReturn } from 'react-hook-form'
|
|
29
|
-
import {
|
|
28
|
+
import { sendFBEvent, sendGAEvent } from '../../../util/analytics'
|
|
30
29
|
|
|
31
30
|
declare global {
|
|
32
31
|
interface Window{
|
|
@@ -53,7 +52,7 @@ const PayWithCrypto: React.FC<{
|
|
|
53
52
|
storePaymentInfo,
|
|
54
53
|
contactForm
|
|
55
54
|
}) => {
|
|
56
|
-
const
|
|
55
|
+
const cmmc = useCommerce()
|
|
57
56
|
const auth = useAuth()
|
|
58
57
|
const [loadingPrice, setLoadingPrice] = useState(false)
|
|
59
58
|
//const [selectedToken, setSelectedToken] = useState('eth')
|
|
@@ -85,7 +84,7 @@ const PayWithCrypto: React.FC<{
|
|
|
85
84
|
.then(res => res.json())
|
|
86
85
|
.then((exchangeRate) => {
|
|
87
86
|
const oneUsdInWei = (10**18) / exchangeRate.data.amount
|
|
88
|
-
const usdAmountInWei = oneUsdInWei *
|
|
87
|
+
const usdAmountInWei = oneUsdInWei * cmmc.cartTotal
|
|
89
88
|
setAmount(usdAmountInWei)
|
|
90
89
|
setLoadingPrice(false)
|
|
91
90
|
})
|
|
@@ -98,7 +97,7 @@ const PayWithCrypto: React.FC<{
|
|
|
98
97
|
const interval = setInterval(fetchPrice, 30000)
|
|
99
98
|
|
|
100
99
|
return () => clearInterval(interval)
|
|
101
|
-
}, [
|
|
100
|
+
}, [cmmc.cartTotal])
|
|
102
101
|
|
|
103
102
|
const sendPayment = async (ether: number) => {
|
|
104
103
|
contactForm.handleSubmit(async () => {
|
|
@@ -130,9 +129,10 @@ const PayWithCrypto: React.FC<{
|
|
|
130
129
|
|
|
131
130
|
const signer = await provider.getSigner()
|
|
132
131
|
ethers.getAddress(process.env.NEXT_PUBLIC_ETH_PAYMENT_ADDRESS ?? '')
|
|
132
|
+
const price = ethers.parseEther(ether.toString())
|
|
133
133
|
const tx = await signer.sendTransaction({
|
|
134
134
|
to: process.env.NEXT_PUBLIC_ETH_PAYMENT_ADDRESS,
|
|
135
|
-
value:
|
|
135
|
+
value: price
|
|
136
136
|
})
|
|
137
137
|
console.log({ ether, addr: process.env.NEXT_PUBLIC_ETH_PAYMENT_ADDRESS })
|
|
138
138
|
console.log('tx', tx)
|
|
@@ -150,7 +150,30 @@ const PayWithCrypto: React.FC<{
|
|
|
150
150
|
await storePaymentInfo({
|
|
151
151
|
ether,
|
|
152
152
|
addr: process.env.NEXT_PUBLIC_ETH_PAYMENT_ADDRESS,
|
|
153
|
-
receipt
|
|
153
|
+
receipt,
|
|
154
|
+
paymentMethod: 'crypto'
|
|
155
|
+
})
|
|
156
|
+
sendGAEvent('purchase', {
|
|
157
|
+
transaction_id: tx.hash,
|
|
158
|
+
value: price,
|
|
159
|
+
currency: 'ETH',
|
|
160
|
+
items: cmmc.cartItems.map((item) => ({
|
|
161
|
+
item_id: item.sku,
|
|
162
|
+
item_name: item.title,
|
|
163
|
+
item_category: item.categoryId,
|
|
164
|
+
price: item.price,
|
|
165
|
+
quantity: item.quantity
|
|
166
|
+
})),
|
|
167
|
+
})
|
|
168
|
+
sendFBEvent('Purchase', {
|
|
169
|
+
content_ids: cmmc.cartItems.map((item) => item.sku),
|
|
170
|
+
contents: cmmc.cartItems.map(item => ({
|
|
171
|
+
id: item.sku,
|
|
172
|
+
quantity: item.quantity
|
|
173
|
+
})),
|
|
174
|
+
num_items: cmmc.cartItems.length,
|
|
175
|
+
value: price,
|
|
176
|
+
currency: 'ETH',
|
|
154
177
|
})
|
|
155
178
|
setTransactionStatus('confirmed')
|
|
156
179
|
})
|
|
@@ -21,11 +21,10 @@ import {
|
|
|
21
21
|
SelectValue
|
|
22
22
|
} from '@hanzo/ui/primitives'
|
|
23
23
|
|
|
24
|
-
import { useAuth } from '@hanzo/auth/service'
|
|
25
|
-
|
|
26
24
|
import { useCommerce } from '../..'
|
|
27
25
|
|
|
28
26
|
import { countries } from './countries'
|
|
27
|
+
import { sendGAEvent } from '../../util/analytics'
|
|
29
28
|
|
|
30
29
|
const shippingFormSchema = z.object({
|
|
31
30
|
addressLine1: z.string().min(2, 'Address must be at least 2 characters.'),
|
|
@@ -43,7 +42,6 @@ const ShippingInfo: React.FC<{
|
|
|
43
42
|
orderId,
|
|
44
43
|
setStep
|
|
45
44
|
}) => {
|
|
46
|
-
const auth = useAuth()
|
|
47
45
|
const cmmc = useCommerce()
|
|
48
46
|
|
|
49
47
|
const shippingForm = useForm<z.infer<typeof shippingFormSchema>>({
|
|
@@ -62,6 +60,18 @@ const ShippingInfo: React.FC<{
|
|
|
62
60
|
if (orderId) {
|
|
63
61
|
await cmmc.updateOrderShippingInfo(orderId, values)
|
|
64
62
|
}
|
|
63
|
+
sendGAEvent('add_shipping_info', {
|
|
64
|
+
items: cmmc.cartItems.map((item) => ({
|
|
65
|
+
item_id: item.sku,
|
|
66
|
+
item_name: item.title,
|
|
67
|
+
item_category: item.categoryId,
|
|
68
|
+
price: item.price,
|
|
69
|
+
quantity: item.quantity
|
|
70
|
+
})),
|
|
71
|
+
num_items: cmmc.cartItems.length,
|
|
72
|
+
value: cmmc.cartTotal,
|
|
73
|
+
currency: 'USD',
|
|
74
|
+
})
|
|
65
75
|
setStep(3)
|
|
66
76
|
}
|
|
67
77
|
|
|
@@ -4,25 +4,25 @@ import Image from 'next/image'
|
|
|
4
4
|
import type { FacetValueDesc } from '../../types'
|
|
5
5
|
|
|
6
6
|
const ICON_SIZE = 20
|
|
7
|
-
const SVG_MULT = 1.5
|
|
8
7
|
|
|
9
8
|
const FacetImage: React.FC<{
|
|
10
9
|
facetValueDesc: FacetValueDesc
|
|
11
10
|
}> = ({
|
|
12
11
|
facetValueDesc
|
|
13
12
|
}) => {
|
|
13
|
+
|
|
14
14
|
const {
|
|
15
15
|
img,
|
|
16
16
|
imgAR: ar,
|
|
17
17
|
label
|
|
18
18
|
} = facetValueDesc
|
|
19
19
|
|
|
20
|
-
|
|
21
20
|
if (!img) {
|
|
22
21
|
return null
|
|
23
22
|
}
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
|
|
24
|
+
// url
|
|
25
|
+
if (typeof img === 'string') {
|
|
26
26
|
return (
|
|
27
27
|
<Image
|
|
28
28
|
src={img as string}
|
|
@@ -33,42 +33,8 @@ const FacetImage: React.FC<{
|
|
|
33
33
|
/>
|
|
34
34
|
)
|
|
35
35
|
}
|
|
36
|
-
|
|
36
|
+
// ReactNode
|
|
37
37
|
return img as React.ReactNode
|
|
38
|
-
|
|
39
|
-
// Otherwise, assume it's a ReactNode of an imported SVG
|
|
40
|
-
|
|
41
|
-
// If it's not square, center it in the appropriate dimension
|
|
42
|
-
/*
|
|
43
|
-
const svgStyle: any = { position: 'relative' }
|
|
44
|
-
if (ar) {
|
|
45
|
-
if (ar < 1) {
|
|
46
|
-
const w = ICON_SIZE * SVG_MULT
|
|
47
|
-
svgStyle.top = (( 1 / ar - 1) * w) / 2
|
|
48
|
-
}
|
|
49
|
-
else if (ar > 1) {
|
|
50
|
-
const h = ICON_SIZE * SVG_MULT
|
|
51
|
-
svgStyle.left = (((1 - ar) * h) / 2)
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return (
|
|
56
|
-
<span
|
|
57
|
-
className='flex justify-center items-center overflow-hidden '
|
|
58
|
-
style={{
|
|
59
|
-
color: 'inherit',
|
|
60
|
-
width: ICON_SIZE * SVG_MULT,
|
|
61
|
-
height: ICON_SIZE * SVG_MULT,
|
|
62
|
-
}}
|
|
63
|
-
>
|
|
64
|
-
{React.cloneElement(facetValueDesc.img as React.ReactElement<any>, {
|
|
65
|
-
width: (facetValueDesc.imgAR ?? 1) * ICON_SIZE * SVG_MULT,
|
|
66
|
-
height: SVG_MULT * ICON_SIZE,
|
|
67
|
-
style: svgStyle
|
|
68
|
-
})}
|
|
69
|
-
</span>
|
|
70
|
-
)
|
|
71
|
-
*/
|
|
72
38
|
}
|
|
73
39
|
|
|
74
40
|
export default FacetImage
|
package/components/index.ts
CHANGED
|
@@ -5,7 +5,7 @@ export { default as BuyItemCard } from './buy-item/buy-item-card'
|
|
|
5
5
|
export { default as BuyItemPopup } from './buy-item/buy-item-popup'
|
|
6
6
|
export { default as CartPanel } from './cart-panel'
|
|
7
7
|
export { default as CheckoutPanel } from './checkout-panel'
|
|
8
|
-
export { default as CategoryItemIOSWheelSelector } from './category-item-
|
|
8
|
+
export { default as CategoryItemIOSWheelSelector } from './category-item-scroll-selector'
|
|
9
9
|
export { default as CategoryItemRadioSelector } from './category-item-radio-selector'
|
|
10
10
|
export { default as FacetValuesWidget } from './facet-values-widget'
|
|
11
11
|
export { default as ProductCard } from './product-card'
|
|
@@ -12,7 +12,7 @@ import { Icons } from './Icons'
|
|
|
12
12
|
|
|
13
13
|
import AddToCartWidget from './add-to-cart-widget'
|
|
14
14
|
import CategoryItemRadioSelector from './category-item-radio-selector'
|
|
15
|
-
import
|
|
15
|
+
import CategoryItemScrollSelector from './category-item-scroll-selector'
|
|
16
16
|
|
|
17
17
|
const SelectCategoryItemPanel: React.FC<
|
|
18
18
|
React.HTMLAttributes<HTMLDivElement> & ItemSelector &
|
|
@@ -89,14 +89,13 @@ const SelectCategoryItemPanel: React.FC<
|
|
|
89
89
|
<div className={'h-[1px] bg-muted-3 ' + (mobilePicker ? 'w-pr-55' : 'w-pr-60') } />
|
|
90
90
|
</div>
|
|
91
91
|
{mobilePicker ? (
|
|
92
|
-
<
|
|
92
|
+
<CategoryItemScrollSelector
|
|
93
93
|
category={category}
|
|
94
94
|
selectedItemRef={selectedItemRef}
|
|
95
95
|
selectSku={selectSku}
|
|
96
96
|
showQuantity={showQuantity}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
outerClx='mb-4'
|
|
97
|
+
itemClx='h-10 border-b px-4'
|
|
98
|
+
outerClx='mb-4 h-[180px]'
|
|
100
99
|
/>
|
|
101
100
|
) : (
|
|
102
101
|
<CategoryItemRadioSelector
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/commerce",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.6.0",
|
|
4
4
|
"description": "Library with shopping cart components.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org/",
|
|
@@ -33,13 +33,12 @@
|
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"ethers": "^6.11.1",
|
|
35
35
|
"next-usequerystate": "^1.17.0",
|
|
36
|
-
"react-mobile-picker": "^1.0.0",
|
|
37
36
|
"react-square-web-payments-sdk": "^3.2.1",
|
|
38
37
|
"square": "^35.0.0"
|
|
39
38
|
},
|
|
40
39
|
"peerDependencies": {
|
|
41
|
-
"@hanzo/auth": "^2.
|
|
42
|
-
"@hanzo/ui": "^3.0.
|
|
40
|
+
"@hanzo/auth": "^2.3.0",
|
|
41
|
+
"@hanzo/ui": "^3.0.7",
|
|
43
42
|
"@hookform/resolvers": "^3.3.4",
|
|
44
43
|
"firebase": "^10.8.0",
|
|
45
44
|
"lucide-react": "^0.307.0",
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
interface Window {
|
|
3
|
+
fbq: Function;
|
|
4
|
+
gtag: Function;
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// https://developers.facebook.com/docs/meta-pixel/reference
|
|
9
|
+
const sendFBEvent = (name: string, options = {}) => {
|
|
10
|
+
window.fbq('track', name, options)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// https://developers.google.com/analytics/devguides/collection/ga4/ecommerce?client_type=gtag
|
|
14
|
+
const sendGAEvent = (name: string, options = {}) => {
|
|
15
|
+
window.gtag('event', name, options)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
sendFBEvent,
|
|
20
|
+
sendGAEvent,
|
|
21
|
+
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
import React from 'react'
|
|
3
|
-
import Picker from 'react-mobile-picker'
|
|
4
|
-
import { observer } from 'mobx-react-lite'
|
|
5
|
-
|
|
6
|
-
import type { ItemSelector } from '../types'
|
|
7
|
-
import { formatPrice } from '../util'
|
|
8
|
-
import { cn } from '@hanzo/ui/util'
|
|
9
|
-
|
|
10
|
-
// from source code
|
|
11
|
-
interface PickerItemRenderProps {
|
|
12
|
-
selected: boolean;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const CategoryItemIOSWheelSelector: React.FC<ItemSelector & {
|
|
16
|
-
height?: number
|
|
17
|
-
itemHeight?: number
|
|
18
|
-
outerClx?: string
|
|
19
|
-
itemClx?: string
|
|
20
|
-
}> = observer(({
|
|
21
|
-
category,
|
|
22
|
-
selectedItemRef: iRef,
|
|
23
|
-
selectSku,
|
|
24
|
-
height,
|
|
25
|
-
itemHeight,
|
|
26
|
-
outerClx='',
|
|
27
|
-
itemClx=''
|
|
28
|
-
}) => {
|
|
29
|
-
|
|
30
|
-
// @ts-ignore
|
|
31
|
-
const onChange = (val, ignore) => {
|
|
32
|
-
console.log("WHEEL: ", val.item)
|
|
33
|
-
selectSku(val.item)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
<div className={cn('border border-muted-4 rounded-lg px-2', outerClx)}>
|
|
38
|
-
<Picker
|
|
39
|
-
className=''
|
|
40
|
-
value={iRef.item ? {item: iRef.item.sku} : {item: undefined}}
|
|
41
|
-
onChange={onChange}
|
|
42
|
-
wheelMode="natural"
|
|
43
|
-
height={height}
|
|
44
|
-
itemHeight={itemHeight}
|
|
45
|
-
>
|
|
46
|
-
<Picker.Column name="item">
|
|
47
|
-
{category.products.map((prod) => (
|
|
48
|
-
<Picker.Item key={prod.sku} value={prod.sku}>
|
|
49
|
-
{({ selected }: PickerItemRenderProps) => (
|
|
50
|
-
<div className={cn(selected ? 'font-semibold text-accent' : 'text-muted', itemClx)}>
|
|
51
|
-
{prod.titleAsOption + ', ' + formatPrice(prod.price)}
|
|
52
|
-
</div>
|
|
53
|
-
)}
|
|
54
|
-
</Picker.Item>
|
|
55
|
-
))}
|
|
56
|
-
</Picker.Column>
|
|
57
|
-
</Picker>
|
|
58
|
-
</div>
|
|
59
|
-
)
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
export default CategoryItemIOSWheelSelector
|