@hanzo/commerce 4.5.0 → 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.
@@ -7,6 +7,7 @@ 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
@@ -50,7 +51,27 @@ const AddToCartWidget: React.FC<{
50
51
  else {
51
52
  toast(`Changed quantity to ${old + 1} for ${item.title}.`)
52
53
  }
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
+ })
54
75
  }
55
76
 
56
77
  const dec = () => {
@@ -65,6 +86,17 @@ const AddToCartWidget: React.FC<{
65
86
  else {
66
87
  toast(`Changed quantity to ${old - 1} for ${item.title}.`)
67
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
+ })
68
100
  }
69
101
 
70
102
  return ( item.isInCart ? (
@@ -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={onCheckoutOpen}
79
+ onClick={showCheckout}
54
80
  >
55
81
  Checkout
56
82
  </Button>
@@ -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 { LoginComponent } from '@hanzo/auth/components'
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 c = useCommerce()
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 * c.cartTotal
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
- }, [c.cartTotal])
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: ethers.parseEther(ether.toString())
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
- const isURL = typeof img === 'string'
25
- if (isURL) {
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/commerce",
3
- "version": "4.5.0",
3
+ "version": "4.6.0",
4
4
  "description": "Library with shopping cart components.",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/",
@@ -37,7 +37,7 @@
37
37
  "square": "^35.0.0"
38
38
  },
39
39
  "peerDependencies": {
40
- "@hanzo/auth": "^2.2.1",
40
+ "@hanzo/auth": "^2.3.0",
41
41
  "@hanzo/ui": "^3.0.7",
42
42
  "@hookform/resolvers": "^3.3.4",
43
43
  "firebase": "^10.8.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
+ }