diff --git a/.changeset/flat-wombats-lie.md b/.changeset/flat-wombats-lie.md
new file mode 100644
index 0000000000..e1568078d7
--- /dev/null
+++ b/.changeset/flat-wombats-lie.md
@@ -0,0 +1,7 @@
+---
+"@graphcommerce/google-datalayer": patch
+"@graphcommerce/googletagmanager": patch
+"@graphcommerce/googleanalytics": patch
+---
+
+Extracted the datalayer from the googleanalytics package and moved to google-datalayer package. Make sure Google Analytics and Google Tagmanager both can send events individually. Be able to configure the datalayer will send as GA4 or legacy GA3 events.
diff --git a/docs/framework/config.md b/docs/framework/config.md
index d3568fffc1..3d114ebdce 100644
--- a/docs/framework/config.md
+++ b/docs/framework/config.md
@@ -1,4 +1,12 @@
+### AnalyticsConfig
+
+AnalyticsConfig will contain all configuration values for the analytics in GraphCommerce.
+
+#### eventFormat: GA3 | GA4[]
+
+eventFormat contains the list of fired and formatted events
+
# GraphCommerce configuration system
Global GraphCommerce configuration can be configured in your `graphcommerce.config.js` file
@@ -105,6 +113,8 @@ Examples:
All storefront configuration for the project
+#### analytics: [AnalyticsConfig](#AnalyticsConfig)
+
#### cartDisplayPricesInclTax: boolean
Due to a limitation of the GraphQL API it is not possible to determine if a cart should be displayed including or excluding tax.
diff --git a/packages/google-datalayer/Config.graphqls b/packages/google-datalayer/Config.graphqls
new file mode 100644
index 0000000000..4308d07de8
--- /dev/null
+++ b/packages/google-datalayer/Config.graphqls
@@ -0,0 +1,21 @@
+extend input GraphCommerceConfig {
+ analytics: AnalyticsConfig
+}
+
+"""
+EventFormat is an enumatation of different event formats. This decides what the format of the event data will be.
+"""
+enum EventFormat {
+ GA3
+ GA4
+}
+
+"""
+AnalyticsConfig will contain all configuration values for the analytics in GraphCommerce.
+"""
+input AnalyticsConfig {
+ """
+ eventFormat contains the list of fired and formatted events
+ """
+ eventFormat: [EventFormat!]
+}
diff --git a/packages/google-datalayer/README.md b/packages/google-datalayer/README.md
new file mode 100644
index 0000000000..93cbb9cbe7
--- /dev/null
+++ b/packages/google-datalayer/README.md
@@ -0,0 +1,6 @@
+# @graphcommerce/google-datalayer
+
+This package contains utilities that can be used in different app analytics
+trackers.
+
+add_payment_info add_shipping_info
diff --git a/packages/google-datalayer/components/AnalyticsItemList.tsx b/packages/google-datalayer/components/AnalyticsItemList.tsx
new file mode 100644
index 0000000000..5033d91963
--- /dev/null
+++ b/packages/google-datalayer/components/AnalyticsItemList.tsx
@@ -0,0 +1,63 @@
+import { nonNullable, useMemoObject } from '@graphcommerce/next-ui'
+import { useEventCallback } from '@mui/material'
+import React, { useContext, useEffect, useMemo } from 'react'
+import { Item, ProductToItemFragment, productToItem } from '../lib'
+import { event } from '../lib/event'
+
+export type UseViewItemListProps
= {
+ title: string
+ items?: (P | null | undefined)[] | null
+ listId?: string
+}
+
+export type ViewItemList = {
+ item_list_id: string
+ item_list_name: string
+ items: Item[]
+}
+
+const GoogleTagManagerItemListContext = React.createContext<{
+ item_list_id: string
+ item_list_name: string
+}>({ item_list_id: '', item_list_name: '' })
+
+export function useListItemHandler(item: ProductToItemFragment) {
+ const { item_list_id, item_list_name } = useContext(GoogleTagManagerItemListContext)
+ return useEventCallback(() =>
+ event('select_item', {
+ item_list_id,
+ item_list_name,
+ items: productToItem(item),
+ }),
+ )
+}
+
+export function ItemList
(
+ props: UseViewItemListProps
& { children: React.ReactNode },
+) {
+ const { title, items, listId, children } = props
+
+ const eventData: ViewItemList = useMemoObject({
+ item_list_id: listId ?? title?.toLowerCase().replace(/\s/g, '_'),
+ item_list_name: title,
+ items: items?.map((item) => (item ? productToItem(item) : null)).filter(nonNullable) ?? [],
+ })
+
+ useEffect(() => {
+ event('view_item_list', eventData)
+ }, [eventData])
+
+ const value = useMemo(
+ () => ({
+ item_list_id: listId ?? title?.toLowerCase().replace(/\s/g, '_'),
+ item_list_name: title ?? listId,
+ }),
+ [listId, title],
+ )
+
+ return (
+
+ {children}
+
+ )
+}
diff --git a/packages/googleanalytics/events/gtagAddPaymentInfo/GtagAddPaymentInfo.graphql b/packages/google-datalayer/events/add_payment_info/AddPaymentInfoFragment.graphql
similarity index 91%
rename from packages/googleanalytics/events/gtagAddPaymentInfo/GtagAddPaymentInfo.graphql
rename to packages/google-datalayer/events/add_payment_info/AddPaymentInfoFragment.graphql
index 7e8ef3e5d9..24266ff9d7 100644
--- a/packages/googleanalytics/events/gtagAddPaymentInfo/GtagAddPaymentInfo.graphql
+++ b/packages/google-datalayer/events/add_payment_info/AddPaymentInfoFragment.graphql
@@ -1,4 +1,4 @@
-fragment GtagAddPaymentInfo on Cart
+fragment AddPaymentInfoFragment on Cart
@inject(into: ["PaymentMethodContext", "PaymentMethodUpdated"]) {
items {
uid
diff --git a/packages/googleanalytics/events/gtagAddPaymentInfo/gtagAddPaymentInfo.ts b/packages/google-datalayer/events/add_payment_info/addPaymentInfo.ts
similarity index 74%
rename from packages/googleanalytics/events/gtagAddPaymentInfo/gtagAddPaymentInfo.ts
rename to packages/google-datalayer/events/add_payment_info/addPaymentInfo.ts
index 454e237bf0..46b29c9a6a 100644
--- a/packages/googleanalytics/events/gtagAddPaymentInfo/gtagAddPaymentInfo.ts
+++ b/packages/google-datalayer/events/add_payment_info/addPaymentInfo.ts
@@ -1,7 +1,9 @@
-import { GtagAddPaymentInfoFragment } from './GtagAddPaymentInfo.gql'
+import { event } from '../../lib/event'
+import { AddPaymentInfoFragment } from './AddPaymentInfoFragment.gql'
-export function gtagAddPaymentInfo(cart?: C | null) {
- globalThis.gtag?.('event', 'add_payment_info', {
+export const addPaymentInfo = (cart?: C | null) =>
+ event('add_payment_info', {
+ ...cart,
currency: cart?.prices?.grand_total?.currency,
value: cart?.prices?.grand_total?.value,
coupon: cart?.applied_coupons?.map((coupon) => coupon?.code),
@@ -19,4 +21,3 @@ export function gtagAddPaymentInfo(cart?:
quantity: item?.quantity,
})),
})
-}
diff --git a/packages/google-datalayer/events/add_payment_info/index.ts b/packages/google-datalayer/events/add_payment_info/index.ts
new file mode 100644
index 0000000000..22b1e9c648
--- /dev/null
+++ b/packages/google-datalayer/events/add_payment_info/index.ts
@@ -0,0 +1,2 @@
+export * from './addPaymentInfo'
+export * from './AddPaymentInfoFragment.gql'
diff --git a/packages/googleanalytics/events/gtagAddShippingInfo/GtagAddShippingInfo.graphql b/packages/google-datalayer/events/add_shipping_info/AddSchippingInfoFragment.graphql
similarity index 83%
rename from packages/googleanalytics/events/gtagAddShippingInfo/GtagAddShippingInfo.graphql
rename to packages/google-datalayer/events/add_shipping_info/AddSchippingInfoFragment.graphql
index f6dfea2b25..497c600e9c 100644
--- a/packages/googleanalytics/events/gtagAddShippingInfo/GtagAddShippingInfo.graphql
+++ b/packages/google-datalayer/events/add_shipping_info/AddSchippingInfoFragment.graphql
@@ -1,4 +1,4 @@
-fragment GtagAddShippingInfo on Cart @inject(into: ["ShippingMethodSelected"]) {
+fragment AddShippingInfoFragment on Cart @inject(into: ["ShippingMethodSelected"]) {
prices {
grand_total {
currency
diff --git a/packages/googleanalytics/events/gtagAddShippingInfo/gtagAddShippingInfo.ts b/packages/google-datalayer/events/add_shipping_info/addShippingInfo.ts
similarity index 61%
rename from packages/googleanalytics/events/gtagAddShippingInfo/gtagAddShippingInfo.ts
rename to packages/google-datalayer/events/add_shipping_info/addShippingInfo.ts
index 76bdf1a950..12548aeeca 100644
--- a/packages/googleanalytics/events/gtagAddShippingInfo/gtagAddShippingInfo.ts
+++ b/packages/google-datalayer/events/add_shipping_info/addShippingInfo.ts
@@ -1,9 +1,9 @@
-import { GtagAddShippingInfoFragment } from './GtagAddShippingInfo.gql'
+import { event } from '../../lib/event'
+import { AddShippingInfoFragment } from './AddSchippingInfoFragment.gql'
-export function gtagAddShippingInfo(cart?: C | null) {
- if (!cart) return
-
- globalThis.gtag?.('event', 'add_shipping_info', {
+export const addShippingInfo = (cart?: C) =>
+ event('add_shipping_info', {
+ ...cart,
currency: cart?.prices?.grand_total?.currency,
value: cart?.prices?.grand_total?.value,
coupon: cart?.applied_coupons?.map((coupon) => coupon?.code),
@@ -15,9 +15,8 @@ export function gtagAddShippingInfo(cart?
(sum, discount) => sum + (discount?.amount?.value ?? 0),
0,
),
- item_variant: item?.__typename === 'ConfigurableCartItem' ? item.configured_variant.sku : '',
+ item_variant: item && 'configured_variant' in item ? item.configured_variant.sku : '',
price: item?.prices?.price.value,
quantity: item?.quantity,
})),
})
-}
diff --git a/packages/google-datalayer/events/add_shipping_info/index.ts b/packages/google-datalayer/events/add_shipping_info/index.ts
new file mode 100644
index 0000000000..e6a2cf1ae3
--- /dev/null
+++ b/packages/google-datalayer/events/add_shipping_info/index.ts
@@ -0,0 +1,2 @@
+export * from './addShippingInfo'
+export * from './AddSchippingInfoFragment.gql'
diff --git a/packages/googleanalytics/events/gtagAddToCart/GtagAddToCart.graphql b/packages/google-datalayer/events/add_to_cart/AddToCartFragment.graphql
similarity index 57%
rename from packages/googleanalytics/events/gtagAddToCart/GtagAddToCart.graphql
rename to packages/google-datalayer/events/add_to_cart/AddToCartFragment.graphql
index 18ae30a040..abe8330c49 100644
--- a/packages/googleanalytics/events/gtagAddToCart/GtagAddToCart.graphql
+++ b/packages/google-datalayer/events/add_to_cart/AddToCartFragment.graphql
@@ -1,5 +1,10 @@
-fragment GtagAddToCart on Cart @inject(into: ["CartItemCountChanged"]) {
+fragment AddToCartFragment on Cart @inject(into: ["CartItemCountChanged"]) {
items {
+ quantity
+ product {
+ sku
+ name
+ }
prices {
discounts {
amount {
@@ -17,4 +22,9 @@ fragment GtagAddToCart on Cart @inject(into: ["CartItemCountChanged"]) {
}
}
}
+ prices {
+ grand_total {
+ currency
+ }
+ }
}
diff --git a/packages/google-datalayer/events/add_to_cart/addToCart.ts b/packages/google-datalayer/events/add_to_cart/addToCart.ts
new file mode 100644
index 0000000000..b643354146
--- /dev/null
+++ b/packages/google-datalayer/events/add_to_cart/addToCart.ts
@@ -0,0 +1,58 @@
+import type { FetchResult } from '@graphcommerce/graphql'
+import {
+ AddProductsToCartFields,
+ AddProductsToCartMutation,
+ findAddedItems,
+ toUserErrors,
+} from '@graphcommerce/magento-product'
+import { nonNullable } from '@graphcommerce/next-ui'
+import { event } from '../../lib/event'
+
+export const addToCart = (
+ result: FetchResult,
+ variables: AddProductsToCartFields,
+) => {
+ const { data, errors } = result
+ const cart = data?.addProductsToCart?.cart
+
+ const addedItems = findAddedItems(data, variables)
+
+ const items = addedItems
+ .map(({ itemVariable, itemInCart }) => {
+ if (!itemInCart) return null
+ const { product, prices } = itemInCart
+ return {
+ item_id: product.sku,
+ item_name: product.name,
+ currency: prices?.price.currency,
+ price: (prices?.row_total_including_tax.value ?? 1) / itemInCart.quantity,
+ quantity: itemVariable.quantity,
+ discount: prices?.discounts?.reduce(
+ (sum, discount) => sum + (discount?.amount?.value ?? 0) / itemVariable.quantity,
+ 0,
+ ),
+ }
+ })
+ .filter(nonNullable)
+
+ const userErrors = toUserErrors(result.data)
+ if ((errors && errors.length > 0) || userErrors.length > 0) {
+ event('add_to_cart_error', {
+ userErrors: userErrors?.map((e) => e.message),
+ errors: errors?.map((e) => e.message),
+ variables,
+ })
+ }
+
+ if (!items.length) return
+
+ event('add_to_cart', {
+ currency: cart?.prices?.grand_total?.currency,
+ value: addedItems.reduce(
+ (sum, { itemVariable, itemInCart }) =>
+ sum + (itemInCart?.prices?.row_total_including_tax.value ?? 1) / itemVariable.quantity,
+ 0,
+ ),
+ items,
+ })
+}
diff --git a/packages/google-datalayer/events/add_to_cart/index.ts b/packages/google-datalayer/events/add_to_cart/index.ts
new file mode 100644
index 0000000000..6fdd3cecf8
--- /dev/null
+++ b/packages/google-datalayer/events/add_to_cart/index.ts
@@ -0,0 +1,2 @@
+export * from './addToCart'
+export * from './AddToCartFragment.gql'
diff --git a/packages/googleanalytics/events/gtagBeginCheckout/GtagBeginCheckout.graphql b/packages/google-datalayer/events/begin_checkout/BeginCheckoutFragment.graphql
similarity index 85%
rename from packages/googleanalytics/events/gtagBeginCheckout/GtagBeginCheckout.graphql
rename to packages/google-datalayer/events/begin_checkout/BeginCheckoutFragment.graphql
index 1bdde9ff8e..dd7e65c2ec 100644
--- a/packages/googleanalytics/events/gtagBeginCheckout/GtagBeginCheckout.graphql
+++ b/packages/google-datalayer/events/begin_checkout/BeginCheckoutFragment.graphql
@@ -1,4 +1,4 @@
-fragment GtagBeginCheckout on Cart @inject(into: ["CartStartCheckout"]) {
+fragment BeginCheckoutFragment on Cart @inject(into: ["CartStartCheckout"]) {
prices {
grand_total {
currency
diff --git a/packages/googleanalytics/events/gtagBeginCheckout/gtagBeginCheckout.ts b/packages/google-datalayer/events/begin_checkout/beginCheckout.ts
similarity index 73%
rename from packages/googleanalytics/events/gtagBeginCheckout/gtagBeginCheckout.ts
rename to packages/google-datalayer/events/begin_checkout/beginCheckout.ts
index 3343462536..208a881872 100644
--- a/packages/googleanalytics/events/gtagBeginCheckout/gtagBeginCheckout.ts
+++ b/packages/google-datalayer/events/begin_checkout/beginCheckout.ts
@@ -1,7 +1,9 @@
-import { GtagBeginCheckoutFragment } from './GtagBeginCheckout.gql'
+import { event } from '../../lib/event'
+import { BeginCheckoutFragment } from './BeginCheckoutFragment.gql'
-export function gtagBeginCheckout(cart?: C | null) {
- globalThis.gtag?.('event', 'begin_checkout', {
+export const beginCheckout = (cart?: C | null) =>
+ event('begin_checkout', {
+ ...cart,
currency: cart?.prices?.grand_total?.currency,
value: cart?.prices?.grand_total?.value,
coupon: cart?.applied_coupons?.map((coupon) => coupon?.code),
@@ -18,4 +20,3 @@ export function gtagBeginCheckout(cart?: C
quantity: item?.quantity,
})),
})
-}
diff --git a/packages/google-datalayer/events/begin_checkout/index.ts b/packages/google-datalayer/events/begin_checkout/index.ts
new file mode 100644
index 0000000000..7e8893210c
--- /dev/null
+++ b/packages/google-datalayer/events/begin_checkout/index.ts
@@ -0,0 +1,2 @@
+export * from './beginCheckout'
+export * from './BeginCheckoutFragment.gql'
diff --git a/packages/google-datalayer/events/purchase/index.ts b/packages/google-datalayer/events/purchase/index.ts
new file mode 100644
index 0000000000..c75468140d
--- /dev/null
+++ b/packages/google-datalayer/events/purchase/index.ts
@@ -0,0 +1 @@
+export * from './purchase'
diff --git a/packages/googleanalytics/events/gtagAddPurchaseInfo/gtagAddPurchaseInfo.ts b/packages/google-datalayer/events/purchase/purchase.ts
similarity index 79%
rename from packages/googleanalytics/events/gtagAddPurchaseInfo/gtagAddPurchaseInfo.ts
rename to packages/google-datalayer/events/purchase/purchase.ts
index 5a5b117cfc..381e3ddfa4 100644
--- a/packages/googleanalytics/events/gtagAddPurchaseInfo/gtagAddPurchaseInfo.ts
+++ b/packages/google-datalayer/events/purchase/purchase.ts
@@ -1,14 +1,16 @@
import { PaymentMethodContextFragment } from '@graphcommerce/magento-cart-payment-method/Api/PaymentMethodContext.gql'
+import { event } from '../../lib/event'
-export function gtagAddPurchaseInfo(
+export const purchase = (
orderNumber: string,
- cart: PaymentMethodContextFragment | null | undefined,
-) {
- globalThis.gtag?.('event', 'purchase', {
+ cart: C | null | undefined,
+) =>
+ event('purchase', {
+ ...cart,
transaction_id: orderNumber,
currency: cart?.prices?.grand_total?.currency,
value: cart?.prices?.grand_total?.value,
- coupon: cart?.applied_coupons?.map((coupon) => coupon?.code),
+ coupon: cart?.applied_coupons?.map((coupon) => coupon?.code).join(' '),
payment_type: cart?.selected_payment_method?.code,
tax: cart?.prices?.applied_taxes?.reduce((sum, tax) => sum + (tax?.amount?.value ?? 0), 0),
items: cart?.items?.map((item) => ({
@@ -16,7 +18,6 @@ export function gtagAddPurchaseInfo(
item_name: item?.product.name,
currency: item?.prices?.price.currency,
discount: item?.prices?.discounts?.reduce(
- // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
(sum, discount) => sum + (discount?.amount?.value ?? 0),
0,
),
@@ -24,4 +25,3 @@ export function gtagAddPurchaseInfo(
quantity: item?.quantity,
})),
})
-}
diff --git a/packages/googleanalytics/events/gtagRemoveFromCart/GtagRemoveFromCart.graphql b/packages/google-datalayer/events/remove_from_cart/RemoveFromCartFragment.graphql
similarity index 83%
rename from packages/googleanalytics/events/gtagRemoveFromCart/GtagRemoveFromCart.graphql
rename to packages/google-datalayer/events/remove_from_cart/RemoveFromCartFragment.graphql
index f7989d9e80..021eb624bd 100644
--- a/packages/googleanalytics/events/gtagRemoveFromCart/GtagRemoveFromCart.graphql
+++ b/packages/google-datalayer/events/remove_from_cart/RemoveFromCartFragment.graphql
@@ -1,4 +1,4 @@
-fragment GtagRemoveFromCart on Cart @inject(into: ["CartItemCountChanged"]) {
+fragment RemoveFromCartFragment on Cart @inject(into: ["CartItemCountChanged"]) {
__typename
prices {
grand_total {
diff --git a/packages/google-datalayer/events/remove_from_cart/index.ts b/packages/google-datalayer/events/remove_from_cart/index.ts
new file mode 100644
index 0000000000..7931331aa9
--- /dev/null
+++ b/packages/google-datalayer/events/remove_from_cart/index.ts
@@ -0,0 +1,2 @@
+export * from './removeFromCart'
+export * from './RemoveFromCartFragment.gql'
diff --git a/packages/googleanalytics/events/gtagRemoveFromCart/gtagRemoveFromCart.ts b/packages/google-datalayer/events/remove_from_cart/removeFromCart.ts
similarity index 70%
rename from packages/googleanalytics/events/gtagRemoveFromCart/gtagRemoveFromCart.ts
rename to packages/google-datalayer/events/remove_from_cart/removeFromCart.ts
index abfef53046..7506f56272 100644
--- a/packages/googleanalytics/events/gtagRemoveFromCart/gtagRemoveFromCart.ts
+++ b/packages/google-datalayer/events/remove_from_cart/removeFromCart.ts
@@ -1,9 +1,9 @@
-import { GtagRemoveFromCartFragment } from './GtagRemoveFromCart.gql'
+import { event } from '../../lib/event'
+import { RemoveFromCartFragment } from './RemoveFromCartFragment.gql'
-export function gtagRemoveFromCart(cart?: C | null) {
- if (!cart) return
-
- globalThis.gtag?.('event', 'remove_from_cart', {
+export const removeFromCart = (cart: C | null | undefined) =>
+ event('remove_from_cart', {
+ ...cart,
currency: cart?.prices?.grand_total?.currency,
value: cart?.prices?.grand_total?.value,
items: cart?.items?.map((item) => ({
@@ -19,4 +19,3 @@ export function gtagRemoveFromCart(cart?:
quantity: item?.quantity,
})),
})
-}
diff --git a/packages/google-datalayer/events/select_item/index.ts b/packages/google-datalayer/events/select_item/index.ts
new file mode 100644
index 0000000000..a4f7241a94
--- /dev/null
+++ b/packages/google-datalayer/events/select_item/index.ts
@@ -0,0 +1 @@
+export * from './select_item'
diff --git a/packages/google-datalayer/events/select_item/select_item.ts b/packages/google-datalayer/events/select_item/select_item.ts
new file mode 100644
index 0000000000..73c3136e28
--- /dev/null
+++ b/packages/google-datalayer/events/select_item/select_item.ts
@@ -0,0 +1,8 @@
+import { event } from '../../lib/event'
+
+export const selectItem = (itemListId: string, itemListName: string, items: unknown[]) =>
+ event('select_item', {
+ item_list_id: itemListId,
+ item_list_name: itemListName,
+ items,
+ })
diff --git a/packages/googleanalytics/events/gtagViewCart/GtagViewCart.graphql b/packages/google-datalayer/events/view_cart/ViewCartFragment.graphql
similarity index 84%
rename from packages/googleanalytics/events/gtagViewCart/GtagViewCart.graphql
rename to packages/google-datalayer/events/view_cart/ViewCartFragment.graphql
index 522f932ce9..442a711b3b 100644
--- a/packages/googleanalytics/events/gtagViewCart/GtagViewCart.graphql
+++ b/packages/google-datalayer/events/view_cart/ViewCartFragment.graphql
@@ -1,4 +1,4 @@
-fragment GtagViewCart on Cart @inject(into: ["CartItemCountChanged"]) {
+fragment ViewCartFragment on Cart @inject(into: ["CartItemCountChanged"]) {
prices {
grand_total {
currency
diff --git a/packages/google-datalayer/events/view_cart/index.ts b/packages/google-datalayer/events/view_cart/index.ts
new file mode 100644
index 0000000000..1731a6bf56
--- /dev/null
+++ b/packages/google-datalayer/events/view_cart/index.ts
@@ -0,0 +1,2 @@
+export * from './viewCart'
+export * from './ViewCartFragment.gql'
diff --git a/packages/googleanalytics/events/gtagViewCart/gtagViewCart.ts b/packages/google-datalayer/events/view_cart/viewCart.ts
similarity index 72%
rename from packages/googleanalytics/events/gtagViewCart/gtagViewCart.ts
rename to packages/google-datalayer/events/view_cart/viewCart.ts
index e42c24a5a0..8989a9d768 100644
--- a/packages/googleanalytics/events/gtagViewCart/gtagViewCart.ts
+++ b/packages/google-datalayer/events/view_cart/viewCart.ts
@@ -1,9 +1,9 @@
-import { GtagViewCartFragment } from './GtagViewCart.gql'
+import { event } from '../../lib/event'
+import { ViewCartFragment } from './ViewCartFragment.gql'
-export function gtagViewCart(cart?: C | null) {
- if (!cart) return
-
- globalThis.gtag?.('event', 'view_cart', {
+export const viewCart = (cart: C | null | undefined) =>
+ event('view_cart', {
+ ...cart,
currency: cart?.prices?.grand_total?.currency,
value: cart?.prices?.grand_total?.value,
items: cart?.items?.map((item) => ({
@@ -19,4 +19,3 @@ export function gtagViewCart(cart?: C | null) {
quantity: item?.quantity,
})),
})
-}
diff --git a/packages/google-datalayer/events/view_item/index.ts b/packages/google-datalayer/events/view_item/index.ts
new file mode 100644
index 0000000000..e834da439b
--- /dev/null
+++ b/packages/google-datalayer/events/view_item/index.ts
@@ -0,0 +1 @@
+export * from './view_item'
diff --git a/packages/google-datalayer/events/view_item/view_item.ts b/packages/google-datalayer/events/view_item/view_item.ts
new file mode 100644
index 0000000000..0dbae337fe
--- /dev/null
+++ b/packages/google-datalayer/events/view_item/view_item.ts
@@ -0,0 +1,8 @@
+import { event } from '../../lib/event'
+
+export const viewItem = (itemListId: string, itemListName: string, items: unknown[]) =>
+ event('view_item', {
+ item_list_id: itemListId,
+ item_list_name: itemListName,
+ items,
+ })
diff --git a/packages/google-datalayer/events/view_item_list/index.ts b/packages/google-datalayer/events/view_item_list/index.ts
new file mode 100644
index 0000000000..3efae2b72e
--- /dev/null
+++ b/packages/google-datalayer/events/view_item_list/index.ts
@@ -0,0 +1 @@
+export * from './view_item_list'
diff --git a/packages/google-datalayer/events/view_item_list/view_item_list.ts b/packages/google-datalayer/events/view_item_list/view_item_list.ts
new file mode 100644
index 0000000000..c1ced45b8b
--- /dev/null
+++ b/packages/google-datalayer/events/view_item_list/view_item_list.ts
@@ -0,0 +1,8 @@
+import { event } from '../../lib/event'
+
+export const viewItemList = (itemListId: string, itemListName: string, items: unknown[]) =>
+ event('view_item_list', {
+ item_list_id: itemListId,
+ item_list_name: itemListName,
+ items,
+ })
diff --git a/packages/google-datalayer/lib/event.ts b/packages/google-datalayer/lib/event.ts
new file mode 100644
index 0000000000..34f9ea002b
--- /dev/null
+++ b/packages/google-datalayer/lib/event.ts
@@ -0,0 +1,39 @@
+import { EventFormatSchema } from '@graphcommerce/next-config'
+
+type EventMapFunctionType = (
+ eventName: Gtag.EventNames | (string & Record),
+ eventData: {
+ [key: string]: unknown
+ },
+) => void
+
+type EventType = keyof (typeof EventFormatSchema)['Enum']
+
+const eventMap: { [key in EventType]: EventMapFunctionType } = {
+ GA4: (eventName, eventData) => {
+ if (import.meta.graphCommerce.googleAnalyticsId) {
+ globalThis.gtag('event', eventName, eventData)
+ }
+
+ if (import.meta.graphCommerce.googleTagmanagerId) {
+ globalThis.dataLayer.push({ event: eventName, ...eventData })
+ }
+ },
+ GA3: (eventName, eventData) => {
+ if (import.meta.graphCommerce.googleAnalyticsId) {
+ console.warn(
+ "Google Analytics 3 format is not supported for Google Analytics 4. Please update your event format to 'GA4'.",
+ )
+ }
+
+ if (import.meta.graphCommerce.googleTagmanagerId) {
+ globalThis.dataLayer.push({ event: eventName, ecommerce: eventData })
+ }
+ },
+}
+
+const eventsToBeFired = import.meta.graphCommerce.analytics?.eventFormat ?? ['GA4']
+
+export const event: EventMapFunctionType = (eventName, eventData) => {
+ eventsToBeFired.map((e) => eventMap[e](eventName, eventData))
+}
diff --git a/packages/google-datalayer/lib/index.ts b/packages/google-datalayer/lib/index.ts
new file mode 100644
index 0000000000..2b49b3e886
--- /dev/null
+++ b/packages/google-datalayer/lib/index.ts
@@ -0,0 +1,2 @@
+export * from './productToItem/productToItem'
+export * from './productToItem/ProductToItem.gql'
diff --git a/packages/googleanalytics/events/productToGtagItem/ProductToGtagItem.graphql b/packages/google-datalayer/lib/productToItem/ProductToItem.graphql
similarity index 81%
rename from packages/googleanalytics/events/productToGtagItem/ProductToGtagItem.graphql
rename to packages/google-datalayer/lib/productToItem/ProductToItem.graphql
index 73c663a477..5c6af3fbcd 100644
--- a/packages/googleanalytics/events/productToGtagItem/ProductToGtagItem.graphql
+++ b/packages/google-datalayer/lib/productToItem/ProductToItem.graphql
@@ -1,4 +1,4 @@
-fragment ProductToGtagItem on ProductInterface {
+fragment ProductToItem on ProductInterface {
name
sku
price_range {
diff --git a/packages/googleanalytics/events/productToGtagItem/productToGtagItem.ts b/packages/google-datalayer/lib/productToItem/productToItem.ts
similarity index 84%
rename from packages/googleanalytics/events/productToGtagItem/productToGtagItem.ts
rename to packages/google-datalayer/lib/productToItem/productToItem.ts
index 866a877138..ae84752139 100644
--- a/packages/googleanalytics/events/productToGtagItem/productToGtagItem.ts
+++ b/packages/google-datalayer/lib/productToItem/productToItem.ts
@@ -1,6 +1,6 @@
-import { ProductToGtagItemFragment } from './ProductToGtagItem.gql'
+import { ProductToItemFragment } from './ProductToItem.gql'
-export type GtagItem = {
+export type Item = {
item_id: string
item_name: string
affiliation?: string
@@ -22,7 +22,7 @@ export type GtagItem = {
quantity?: number
}
-export function productToGtagItem(item: P): GtagItem {
+export function productToItem
(item: P): Item {
return {
item_id: item.sku ?? '',
item_name: item.name ?? '',
diff --git a/packages/google-datalayer/package.json b/packages/google-datalayer/package.json
new file mode 100644
index 0000000000..01563c6ea3
--- /dev/null
+++ b/packages/google-datalayer/package.json
@@ -0,0 +1,44 @@
+{
+ "name": "@graphcommerce/google-datalayer",
+ "homepage": "https://www.graphcommerce.org/",
+ "repository": "github:graphcommerce-org/graphcommerce",
+ "version": "8.0.4-canary.0",
+ "sideEffects": false,
+ "prettier": "@graphcommerce/prettier-config-pwa",
+ "eslintConfig": {
+ "extends": "@graphcommerce/eslint-config-pwa",
+ "parserOptions": {
+ "project": "./tsconfig.json"
+ }
+ },
+ "peerDependencies": {
+ "@graphcommerce/eslint-config-pwa": "^8.0.0-canary.74",
+ "@graphcommerce/graphql-mesh": "^8.0.0-canary.74",
+ "@graphcommerce/magento-cart": "^8.0.0-canary.74",
+ "@graphcommerce/magento-cart-payment-method": "^8.0.0-canary.74",
+ "@graphcommerce/magento-cart-shipping-method": "^8.0.0-canary.74",
+ "@graphcommerce/magento-product": "^8.0.0-canary.74",
+ "@graphcommerce/next-config": "^8.0.0-canary.74",
+ "@graphcommerce/next-ui": "^8.0.0-canary.74",
+ "@graphcommerce/prettier-config-pwa": "^8.0.0-canary.74",
+ "@graphcommerce/typescript-config-pwa": "^8.0.0-canary.74",
+ "@mui/material": "^5.14.20",
+ "next": "^14",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@graphcommerce/magento-cart": {
+ "optional": true
+ },
+ "@graphcommerce/magento-cart-payment-method": {
+ "optional": true
+ },
+ "@graphcommerce/magento-cart-shipping-method": {
+ "optional": true
+ },
+ "@graphcommerce/magento-product": {
+ "optional": true
+ }
+ }
+}
diff --git a/packages/google-datalayer/plugins/GoogleDatalayerAddProductsToCartForm.tsx b/packages/google-datalayer/plugins/GoogleDatalayerAddProductsToCartForm.tsx
new file mode 100644
index 0000000000..3cc3079bb2
--- /dev/null
+++ b/packages/google-datalayer/plugins/GoogleDatalayerAddProductsToCartForm.tsx
@@ -0,0 +1,23 @@
+import { AddProductsToCartFormProps } from '@graphcommerce/magento-product'
+import { PluginProps } from '@graphcommerce/next-config'
+import { addToCart } from '../events/add_to_cart'
+
+export const component = 'AddProductsToCartForm'
+export const exported = '@graphcommerce/magento-product'
+
+/** When a product is added to the Cart, send a Google Analytics event */
+function GoogleDatalayerAddProductsToCartForm(props: PluginProps) {
+ const { Prev, onComplete, ...rest } = props
+
+ return (
+ {
+ addToCart(result, variables)
+ return onComplete?.(result, variables)
+ }}
+ />
+ )
+}
+
+export const Plugin = GoogleDatalayerAddProductsToCartForm
diff --git a/packages/google-datalayer/plugins/GoogleDatalayerAllPagesPageview.tsx b/packages/google-datalayer/plugins/GoogleDatalayerAllPagesPageview.tsx
new file mode 100644
index 0000000000..0b9b5bc278
--- /dev/null
+++ b/packages/google-datalayer/plugins/GoogleDatalayerAllPagesPageview.tsx
@@ -0,0 +1,37 @@
+import type { PagesProps } from '@graphcommerce/framer-next-pages'
+import type { PluginProps } from '@graphcommerce/next-config'
+import { useRouter } from 'next/router'
+import { useEffect } from 'react'
+import { event } from '../lib/event'
+
+export const component = 'FramerNextPages'
+export const exported = '@graphcommerce/framer-next-pages'
+
+function GoogleDatalayerAllPagesPageview(props: PluginProps) {
+ const { Prev, ...rest } = props
+
+ const { events } = useRouter()
+
+ useEffect(() => {
+ const onRouteChangeComplete = (url: string) => {
+ /**
+ * Todo: the actual page_view event is currently disabled, because we run the risk of double counting page views.
+ * https://developers.google.com/analytics/devguides/collection/ga4/views?client_type=gtag#manually_send_page_view_events
+ *
+ * https://developers.google.com/analytics/devguides/collection/ga4/single-page-applications?implementation=event
+ */
+ // event('page_view', {
+ // page_title: '',
+ // page_location: '',
+ // })
+ // event('pageview', { page: url })
+ }
+
+ events.on('routeChangeComplete', onRouteChangeComplete)
+ return () => events.off('routeChangeComplete', onRouteChangeComplete)
+ }, [events])
+
+ return
+}
+
+export const Plugin = GoogleDatalayerAllPagesPageview
diff --git a/packages/google-datalayer/plugins/GoogleDatalayerCartStartCheckout.tsx b/packages/google-datalayer/plugins/GoogleDatalayerCartStartCheckout.tsx
new file mode 100644
index 0000000000..372df8c8cc
--- /dev/null
+++ b/packages/google-datalayer/plugins/GoogleDatalayerCartStartCheckout.tsx
@@ -0,0 +1,21 @@
+import { CartStartCheckoutProps } from '@graphcommerce/magento-cart'
+import { PluginProps } from '@graphcommerce/next-config'
+import { beginCheckout } from '../events/begin_checkout'
+
+export const component = 'CartStartCheckout'
+export const exported = '@graphcommerce/magento-cart'
+
+export function GoogleDatalayerCartStartCheckout(props: PluginProps) {
+ const { Prev, onStart, ...rest } = props
+ return (
+ {
+ beginCheckout(cart)
+ return onStart?.(e, cart)
+ }}
+ />
+ )
+}
+
+export const Plugin = GoogleDatalayerCartStartCheckout
diff --git a/packages/googleanalytics/plugins/GaCartStartCheckoutLinkOrButton.tsx b/packages/google-datalayer/plugins/GoogleDatalayerCartStartCheckoutLinkOrButton.tsx
similarity index 60%
rename from packages/googleanalytics/plugins/GaCartStartCheckoutLinkOrButton.tsx
rename to packages/google-datalayer/plugins/GoogleDatalayerCartStartCheckoutLinkOrButton.tsx
index 7acbe64d8a..5e19648ff2 100644
--- a/packages/googleanalytics/plugins/GaCartStartCheckoutLinkOrButton.tsx
+++ b/packages/google-datalayer/plugins/GoogleDatalayerCartStartCheckoutLinkOrButton.tsx
@@ -1,15 +1,14 @@
import { CartStartCheckoutLinkOrButtonProps } from '@graphcommerce/magento-cart'
-import { IfConfig, PluginProps } from '@graphcommerce/next-config'
+import { PluginProps } from '@graphcommerce/next-config'
import { useMemoObject } from '@graphcommerce/next-ui'
import { useEffect } from 'react'
-import { gtagBeginCheckout } from '../events/gtagBeginCheckout/gtagBeginCheckout'
-import { gtagViewCart } from '../events/gtagViewCart/gtagViewCart'
+import { beginCheckout } from '../events/begin_checkout'
+import { viewCart } from '../events/view_cart'
export const component = 'CartStartCheckoutLinkOrButton'
export const exported = '@graphcommerce/magento-cart'
-export const ifConfig: IfConfig = 'googleAnalyticsId'
-export function GaCartStartCheckoutLinkOrButton(
+export function GoogleDatalayerCartStartCheckoutLinkOrButton(
props: PluginProps,
) {
const { Prev, onStart, ...rest } = props
@@ -18,7 +17,7 @@ export function GaCartStartCheckoutLinkOrButton(
useEffect(() => {
if (cartObject.items) {
- gtagViewCart(cartObject)
+ viewCart(cartObject)
}
}, [cartObject])
@@ -26,11 +25,11 @@ export function GaCartStartCheckoutLinkOrButton(
{
- gtagBeginCheckout(cart)
+ beginCheckout(cart)
return onStart?.(e, cart)
}}
/>
)
}
-export const Plugin = GaCartStartCheckoutLinkOrButton
+export const Plugin = GoogleDatalayerCartStartCheckoutLinkOrButton
diff --git a/packages/googleanalytics/plugins/GaPaymentMethodButton.tsx b/packages/google-datalayer/plugins/GoogleDatalayerPaymentMethodButton.tsx
similarity index 66%
rename from packages/googleanalytics/plugins/GaPaymentMethodButton.tsx
rename to packages/google-datalayer/plugins/GoogleDatalayerPaymentMethodButton.tsx
index 9388e6a134..a8be0a075e 100644
--- a/packages/googleanalytics/plugins/GaPaymentMethodButton.tsx
+++ b/packages/google-datalayer/plugins/GoogleDatalayerPaymentMethodButton.tsx
@@ -1,15 +1,14 @@
import { useCartQuery } from '@graphcommerce/magento-cart'
import { PaymentMethodButtonProps } from '@graphcommerce/magento-cart-payment-method'
import { GetPaymentMethodContextDocument } from '@graphcommerce/magento-cart-payment-method/PaymentMethodContext/GetPaymentMethodContext.gql'
-import { IfConfig, PluginProps } from '@graphcommerce/next-config'
-import { gtagAddPaymentInfo } from '../events/gtagAddPaymentInfo/gtagAddPaymentInfo'
+import { PluginProps } from '@graphcommerce/next-config'
+import { addPaymentInfo } from '../events/add_payment_info'
export const component = 'PaymentMethodButton'
export const exported = '@graphcommerce/magento-cart-payment-method'
-export const ifConfig: IfConfig = 'googleAnalyticsId'
// @todo This plugin can probably be migrated to the actual form that is submitted.
-function GaPaymentMethodButton(props: PluginProps) {
+function GoogleDatalayerPaymentMethodButton(props: PluginProps) {
const { Prev, onSubmitSuccessful, ...rest } = props
const methodContext = useCartQuery(GetPaymentMethodContextDocument)
@@ -17,11 +16,11 @@ function GaPaymentMethodButton(props: PluginProps) {
{
- gtagAddPaymentInfo(methodContext.data?.cart)
+ addPaymentInfo(methodContext.data?.cart)
return onSubmitSuccessful?.()
}}
/>
)
}
-export const Plugin = GaPaymentMethodButton
+export const Plugin = GoogleDatalayerPaymentMethodButton
diff --git a/packages/googleanalytics/plugins/GaPaymentMethodContextProvider.tsx b/packages/google-datalayer/plugins/GoogleDatalayerPaymentMethodContextProvider.tsx
similarity index 50%
rename from packages/googleanalytics/plugins/GaPaymentMethodContextProvider.tsx
rename to packages/google-datalayer/plugins/GoogleDatalayerPaymentMethodContextProvider.tsx
index 5c95de61d3..89b3500f96 100644
--- a/packages/googleanalytics/plugins/GaPaymentMethodContextProvider.tsx
+++ b/packages/google-datalayer/plugins/GoogleDatalayerPaymentMethodContextProvider.tsx
@@ -1,21 +1,22 @@
import type { PaymentMethodContextProviderProps } from '@graphcommerce/magento-cart-payment-method'
-import type { IfConfig, PluginProps } from '@graphcommerce/next-config'
-import { gtagAddPurchaseInfo } from '../events/gtagAddPurchaseInfo/gtagAddPurchaseInfo'
+import type { PluginProps } from '@graphcommerce/next-config'
+import { purchase } from '../events/purchase'
export const component = 'PaymentMethodContextProvider'
export const exported = '@graphcommerce/magento-cart-payment-method'
-export const ifConfig: IfConfig = 'googleAnalyticsId'
-function GaPaymentMethodContextProvider(props: PluginProps) {
+function GoogleDatalayerPaymentMethodContextProvider(
+ props: PluginProps,
+) {
const { Prev, onSuccess, ...rest } = props
return (
{
- gtagAddPurchaseInfo(orderNumber, cart)
+ purchase(orderNumber, cart)
return onSuccess?.(orderNumber, cart)
}}
/>
)
}
-export const Plugin = GaPaymentMethodContextProvider
+export const Plugin = GoogleDatalayerPaymentMethodContextProvider
diff --git a/packages/google-datalayer/plugins/GoogleDatalayerProductListItem.tsx b/packages/google-datalayer/plugins/GoogleDatalayerProductListItem.tsx
new file mode 100644
index 0000000000..cb0fbef648
--- /dev/null
+++ b/packages/google-datalayer/plugins/GoogleDatalayerProductListItem.tsx
@@ -0,0 +1,28 @@
+import { ProductListItemReal } from '@graphcommerce/magento-product'
+import { PluginProps } from '@graphcommerce/next-config'
+import { useEventCallback } from '@mui/material'
+import { ComponentProps } from 'react'
+import { useListItemHandler } from '../components/AnalyticsItemList'
+
+export const component = 'ProductListItemReal'
+export const exported = '@graphcommerce/magento-product'
+
+function GoogleDatalayerProductListItem(
+ props: PluginProps>,
+) {
+ const { Prev, onClick, ...rest } = props
+ const { sku, price_range, name } = rest
+ const handle = useListItemHandler({ sku, price_range, name })
+
+ return (
+ >((e, item) => {
+ handle()
+ return onClick?.(e, item)
+ })}
+ />
+ )
+}
+
+export const Plugin = GoogleDatalayerProductListItem
diff --git a/packages/google-datalayer/plugins/GoogleDatalayerProductListItemsBase.tsx b/packages/google-datalayer/plugins/GoogleDatalayerProductListItemsBase.tsx
new file mode 100644
index 0000000000..be39d69aa8
--- /dev/null
+++ b/packages/google-datalayer/plugins/GoogleDatalayerProductListItemsBase.tsx
@@ -0,0 +1,17 @@
+import type { ProductItemsGridProps } from '@graphcommerce/magento-product'
+import { PluginProps } from '@graphcommerce/next-config'
+import { ItemList } from '../components/AnalyticsItemList'
+
+export const component = 'ProductListItemsBase'
+export const exported = '@graphcommerce/magento-product'
+
+export function GoogleDatalayerProductListItemsBase(props: PluginProps) {
+ const { Prev, ...rest } = props
+ return (
+
+
+
+ )
+}
+
+export const Plugin = GoogleDatalayerProductListItemsBase
diff --git a/packages/google-datalayer/plugins/GoogleDatalayerRemoveItemFromCart.tsx b/packages/google-datalayer/plugins/GoogleDatalayerRemoveItemFromCart.tsx
new file mode 100644
index 0000000000..4809bd1a7e
--- /dev/null
+++ b/packages/google-datalayer/plugins/GoogleDatalayerRemoveItemFromCart.tsx
@@ -0,0 +1,29 @@
+import type { RemoveItemFromCart as Original } from '@graphcommerce/magento-cart-items'
+import { ReactPlugin } from '@graphcommerce/next-config'
+import { removeFromCart } from '../events/remove_from_cart'
+
+export const component = 'RemoveItemFromCart'
+export const exported =
+ '@graphcommerce/magento-cart-items/components/RemoveItemFromCart/RemoveItemFromCart'
+
+export const GoogleDatalayerRemoveItemFromCart: ReactPlugin = (props) => {
+ const { Prev, uid, quantity, prices, product, buttonProps } = props
+
+ return (
+ {
+ removeFromCart({
+ __typename: 'Cart',
+ items: [{ uid, __typename: 'SimpleCartItem', product, quantity, prices }],
+ })
+ buttonProps?.onClick?.(e)
+ },
+ }}
+ />
+ )
+}
+
+export const Plugin = GoogleDatalayerRemoveItemFromCart
diff --git a/packages/google-datalayer/plugins/GoogleDatalayerRemoveItemFromCartFab.tsx b/packages/google-datalayer/plugins/GoogleDatalayerRemoveItemFromCartFab.tsx
new file mode 100644
index 0000000000..82d687c5a0
--- /dev/null
+++ b/packages/google-datalayer/plugins/GoogleDatalayerRemoveItemFromCartFab.tsx
@@ -0,0 +1,29 @@
+import type { RemoveItemFromCartFab as Original } from '@graphcommerce/magento-cart-items'
+import { ReactPlugin } from '@graphcommerce/next-config'
+import { removeFromCart } from '../events/remove_from_cart'
+
+export const component = 'RemoveItemFromCartFab'
+export const exported =
+ '@graphcommerce/magento-cart-items/components/RemoveItemFromCart/RemoveItemFromCartFab'
+
+export const GoogleDatalayerRemoveItemFromCartFab: ReactPlugin = (props) => {
+ const { Prev, uid, quantity, prices, product, fabProps } = props
+
+ return (
+ {
+ removeFromCart({
+ __typename: 'Cart',
+ items: [{ uid, __typename: 'SimpleCartItem', product, quantity, prices }],
+ })
+ fabProps?.onClick?.(e)
+ },
+ }}
+ />
+ )
+}
+
+export const Plugin = GoogleDatalayerRemoveItemFromCartFab
diff --git a/packages/googleanalytics/plugins/GaShippingMethodForm.tsx b/packages/google-datalayer/plugins/GoogleDatalayerShippingMethodForm.tsx
similarity index 54%
rename from packages/googleanalytics/plugins/GaShippingMethodForm.tsx
rename to packages/google-datalayer/plugins/GoogleDatalayerShippingMethodForm.tsx
index 1ec359ab7d..c6076d864a 100644
--- a/packages/googleanalytics/plugins/GaShippingMethodForm.tsx
+++ b/packages/google-datalayer/plugins/GoogleDatalayerShippingMethodForm.tsx
@@ -1,23 +1,22 @@
import { ShippingMethodFormProps } from '@graphcommerce/magento-cart-shipping-method'
-import { IfConfig, PluginProps } from '@graphcommerce/next-config'
-import { gtagAddShippingInfo } from '../events/gtagAddShippingInfo/gtagAddShippingInfo'
+import { PluginProps } from '@graphcommerce/next-config'
+import { addShippingInfo } from '../events/add_shipping_info'
export const component = 'ShippingMethodForm'
export const exported = '@graphcommerce/magento-cart-shipping-method'
-export const ifConfig: IfConfig = 'googleAnalyticsId'
/** When the ShippingMethod is submitted the result is sent to Google Analytics */
-export function GaShippingMethodForm(props: PluginProps) {
+export function GoogleDatalayerShippingMethodForm(props: PluginProps) {
const { Prev, onComplete, ...rest } = props
return (
{
- gtagAddShippingInfo(result.data?.setShippingMethodsOnCart?.cart)
+ addShippingInfo(result.data?.setShippingMethodsOnCart?.cart)
return onComplete?.(result, variables)
}}
/>
)
}
-export const Plugin = GaShippingMethodForm
+export const Plugin = GoogleDatalayerShippingMethodForm
diff --git a/packages/google-datalayer/plugins/GoogleDatalayerUpdateItemQuantity.tsx b/packages/google-datalayer/plugins/GoogleDatalayerUpdateItemQuantity.tsx
new file mode 100644
index 0000000000..d400b5651d
--- /dev/null
+++ b/packages/google-datalayer/plugins/GoogleDatalayerUpdateItemQuantity.tsx
@@ -0,0 +1,65 @@
+import type { UpdateItemQuantityProps } from '@graphcommerce/magento-cart-items'
+import { PluginProps } from '@graphcommerce/next-config'
+import { event } from '../lib/event'
+
+export const component = 'UpdateItemQuantity'
+export const exported =
+ '@graphcommerce/magento-cart-items/components/UpdateItemQuantity/UpdateItemQuantity'
+
+/**
+ * When a product is added to the Cart, by using the + button on cart page, send a Google Analytics
+ * event
+ */
+function GoogleDatalayerUpdateItemQuantity(props: PluginProps) {
+ const { Prev, formOptions, quantity, ...rest } = props
+
+ return (
+ {
+ const original = formOptions?.onComplete?.(data, variables)
+ const diffQuantity = variables.quantity - quantity
+ if (diffQuantity === 0) return original
+
+ const itemId = variables.uid
+ const addedItem = data.data?.updateCartItems?.cart.items?.find(
+ (item) => item?.uid === itemId,
+ )
+
+ if (addedItem && addedItem.prices && addedItem.prices.row_total_including_tax.value) {
+ // we need to manually calculate pricePerItemInclTax (https://github.com/magento/magento2/issues/33848)
+ const pricePerItemInclTax =
+ addedItem.prices.row_total_including_tax.value / addedItem.quantity
+ const addToCartValue = pricePerItemInclTax * diffQuantity
+
+ event('add_to_cart', {
+ currency: addedItem?.prices?.price.currency,
+ value: addToCartValue,
+ items: [
+ {
+ item_id: addedItem?.product.sku,
+ item_name: addedItem?.product.name,
+ currency: addedItem?.prices?.price.currency,
+ price: pricePerItemInclTax,
+ quantity: variables.quantity,
+ discount: addedItem?.prices?.discounts?.reduce(
+ // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
+ (sum, discount) => sum + (discount?.amount?.value ?? 0),
+ 0,
+ ),
+ },
+ ],
+ })
+ }
+
+ return original
+ },
+ }}
+ />
+ )
+}
+
+export const Plugin = GoogleDatalayerUpdateItemQuantity
diff --git a/packages/googleanalytics/plugins/GaViewItem.tsx b/packages/google-datalayer/plugins/GoogleDatalayerViewItem.tsx
similarity index 56%
rename from packages/googleanalytics/plugins/GaViewItem.tsx
rename to packages/google-datalayer/plugins/GoogleDatalayerViewItem.tsx
index 3dad858841..d4ae87ab9d 100644
--- a/packages/googleanalytics/plugins/GaViewItem.tsx
+++ b/packages/google-datalayer/plugins/GoogleDatalayerViewItem.tsx
@@ -1,27 +1,29 @@
import type { ProductPageMeta } from '@graphcommerce/magento-product'
-import { IfConfig, PluginProps } from '@graphcommerce/next-config'
+import { PluginProps } from '@graphcommerce/next-config'
import { useMemoObject } from '@graphcommerce/next-ui'
-import { useEffect } from 'react'
-import { productToGtagItem } from '../events/productToGtagItem/productToGtagItem'
+import React, { useEffect } from 'react'
+import { productToItem } from '../lib'
+import { event } from '../lib/event'
export const component = 'ProductPageMeta'
export const exported = '@graphcommerce/magento-product'
-export const ifConfig: IfConfig = 'googleAnalyticsId'
/** When a product is added to the Cart, send a Google Analytics event */
-function GaViewItem(props: PluginProps>) {
+function GoogleDatalayerViewItem(props: PluginProps>) {
const { Prev, product } = props
const { price_range } = product
const viewItem = useMemoObject({
currency: price_range.minimum_price.final_price.currency,
value: price_range.minimum_price.final_price.value,
- items: [productToGtagItem(product)],
+ items: [productToItem(product)],
})
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
- useEffect(() => globalThis.gtag?.('event', 'view_item', viewItem), [viewItem])
+ useEffect(() => {
+ event('view_item', viewItem)
+ }, [viewItem])
return
}
-export const Plugin = GaViewItem
+export const Plugin = GoogleDatalayerViewItem
diff --git a/packages/google-datalayer/tsconfig.json b/packages/google-datalayer/tsconfig.json
new file mode 100644
index 0000000000..7398153dd6
--- /dev/null
+++ b/packages/google-datalayer/tsconfig.json
@@ -0,0 +1,5 @@
+{
+ "exclude": ["**/node_modules", "**/.*/"],
+ "include": ["**/*.ts", "**/*.tsx"],
+ "extends": "@graphcommerce/typescript-config-pwa/nextjs.json"
+}
diff --git a/packages/googleanalytics/README.md b/packages/googleanalytics/README.md
index 16a08a7422..00ccb5217a 100644
--- a/packages/googleanalytics/README.md
+++ b/packages/googleanalytics/README.md
@@ -20,3 +20,7 @@ Besides the GA4 integration it also tracks the following events:
Configure the following ([configuration values](./Config.graphqls)) in your
graphcommerce.config.js
+
+Make sure you also configure the 'Page changes based on browser history events.'
+configuration in Google Analytics, see
+[docs](https://developers.google.com/analytics/devguides/collection/ga4/single-page-applications?implementation=browser-history#implement_single-page_application_measurement).
diff --git a/packages/googleanalytics/components/GoogleAnalyticsItemList.tsx b/packages/googleanalytics/components/GoogleAnalyticsItemList.tsx
deleted file mode 100644
index 52f6d7db73..0000000000
--- a/packages/googleanalytics/components/GoogleAnalyticsItemList.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import { nonNullable, useMemoObject } from '@graphcommerce/next-ui'
-import { useEventCallback } from '@mui/material'
-import React, { useContext, useEffect, useMemo } from 'react'
-import { ProductToGtagItemFragment } from '../events/productToGtagItem/ProductToGtagItem.gql'
-import { GtagItem, productToGtagItem } from '../events/productToGtagItem/productToGtagItem'
-
-export type UseGtagViewItemListProps<
- P extends ProductToGtagItemFragment = ProductToGtagItemFragment,
-> = {
- title: string
- items?: (P | null | undefined)[] | null
- listId?: string
-}
-
-export type ViewItemList = {
- item_list_id: string
- item_list_name: string
- items: GtagItem[]
-}
-
-const GoogleAnalyticsItemListContext = React.createContext<{
- item_list_id: string
- item_list_name: string
-}>({ item_list_id: '', item_list_name: '' })
-
-export function useGoogleAnalyticsListItemHandler(item: ProductToGtagItemFragment) {
- const { item_list_id, item_list_name } = useContext(GoogleAnalyticsItemListContext)
- return useEventCallback(() =>
- globalThis.gtag?.('event', 'select_item', {
- item_list_id,
- item_list_name,
- items: productToGtagItem(item),
- }),
- )
-}
-
-export function GoogleAnalyticsItemList<
- P extends ProductToGtagItemFragment = ProductToGtagItemFragment,
->(props: UseGtagViewItemListProps & { children: React.ReactNode }) {
- const { title, items, listId, children } = props
-
- const eventData: ViewItemList = useMemoObject({
- item_list_id: listId ?? title?.toLowerCase().replace(/\s/g, '_'),
- item_list_name: title,
- items: items?.map((item) => (item ? productToGtagItem(item) : null)).filter(nonNullable) ?? [],
- })
-
- useEffect(() => globalThis.gtag?.('event', 'view_item_list', eventData), [eventData])
-
- const value = useMemo(
- () => ({
- item_list_id: listId ?? title?.toLowerCase().replace(/\s/g, '_'),
- item_list_name: title ?? listId,
- }),
- [listId, title],
- )
-
- return (
-
- {children}
-
- )
-}
diff --git a/packages/googleanalytics/components/index.ts b/packages/googleanalytics/components/index.ts
index aa493a4775..94af146d46 100644
--- a/packages/googleanalytics/components/index.ts
+++ b/packages/googleanalytics/components/index.ts
@@ -1,2 +1 @@
export * from './GoogleAnalyticsScript'
-export * from './GoogleAnalyticsItemList'
diff --git a/packages/googleanalytics/events/gtagAddToCart/gtagAddToCart.ts b/packages/googleanalytics/events/gtagAddToCart/gtagAddToCart.ts
deleted file mode 100644
index 2a96fc25e2..0000000000
--- a/packages/googleanalytics/events/gtagAddToCart/gtagAddToCart.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import type { FetchResult } from '@graphcommerce/graphql'
-import { AddProductsToCartMutation, AddProductsToCartFields } from '@graphcommerce/magento-product'
-
-export const gtagAddToCart = (
- result: FetchResult,
- variables: AddProductsToCartFields,
-) => {
- const addedItem = result.data?.addProductsToCart?.cart.items?.slice(-1)[0]
-
- if (addedItem && addedItem.prices && addedItem.prices.row_total_including_tax.value) {
- // we need to manually calculate pricePerItemInclTax (https://github.com/magento/magento2/issues/33848)
- const pricePerItemInclTax = addedItem.prices.row_total_including_tax.value / addedItem.quantity
- const addToCartValue = pricePerItemInclTax * variables.cartItems[0].quantity
-
- globalThis.gtag?.('event', 'add_to_cart', {
- currency: addedItem?.prices?.price.currency,
- value: addToCartValue,
- items: [
- {
- item_id: addedItem?.product.sku,
- item_name: addedItem?.product.name,
- currency: addedItem?.prices?.price.currency,
- price: pricePerItemInclTax,
- quantity: variables.cartItems[0].quantity,
- discount: addedItem?.prices?.discounts?.reduce(
- // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
- (sum, discount) => sum + (discount?.amount?.value ?? 0),
- 0,
- ),
- },
- ],
- })
- }
-}
diff --git a/packages/googleanalytics/index.ts b/packages/googleanalytics/index.ts
deleted file mode 100644
index 4a9d3e721e..0000000000
--- a/packages/googleanalytics/index.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export * from './components'
-export * from './events/gtagBeginCheckout/gtagBeginCheckout'
-export * from './events/gtagAddPaymentInfo/gtagAddPaymentInfo'
-export * from './events/gtagAddPurchaseInfo/gtagAddPurchaseInfo'
-export * from './events/gtagAddShippingInfo/gtagAddShippingInfo'
-export * from './events/gtagAddToCart/gtagAddToCart'
diff --git a/packages/googleanalytics/package.json b/packages/googleanalytics/package.json
index 78a574de3c..592f8b6439 100644
--- a/packages/googleanalytics/package.json
+++ b/packages/googleanalytics/package.json
@@ -14,6 +14,9 @@
"devDependencies": {
"@types/gtag.js": "^0.0.18"
},
+ "dependencies": {
+ "@graphcommerce/google-datalayer": "8.0.4-canary.0"
+ },
"peerDependencies": {
"@graphcommerce/eslint-config-pwa": "^8.0.4-canary.0",
"@graphcommerce/graphql-mesh": "^8.0.4-canary.0",
diff --git a/packages/googleanalytics/plugins/GaAddProductsToCartForm.tsx b/packages/googleanalytics/plugins/GaAddProductsToCartForm.tsx
deleted file mode 100644
index 542df4df66..0000000000
--- a/packages/googleanalytics/plugins/GaAddProductsToCartForm.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { AddProductsToCartFormProps } from '@graphcommerce/magento-product'
-import { IfConfig, PluginProps } from '@graphcommerce/next-config'
-import { gtagAddToCart } from '../events/gtagAddToCart/gtagAddToCart'
-
-export const component = 'AddProductsToCartForm'
-export const exported = '@graphcommerce/magento-product'
-export const ifConfig: IfConfig = 'googleAnalyticsId'
-
-/** When a product is added to the Cart, send a Google Analytics event */
-function GaAddProductsToCartForm(props: PluginProps) {
- const { Prev, onComplete, ...rest } = props
-
- return (
- {
- gtagAddToCart(data, variables)
- return onComplete?.(data, variables)
- }}
- />
- )
-}
-
-export const Plugin = GaAddProductsToCartForm
diff --git a/packages/googleanalytics/plugins/GaCartStartCheckout.tsx b/packages/googleanalytics/plugins/GaCartStartCheckout.tsx
deleted file mode 100644
index fb6f0cdeb8..0000000000
--- a/packages/googleanalytics/plugins/GaCartStartCheckout.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { CartStartCheckoutProps } from '@graphcommerce/magento-cart'
-import { IfConfig, PluginProps } from '@graphcommerce/next-config'
-import { gtagBeginCheckout } from '../events/gtagBeginCheckout/gtagBeginCheckout'
-
-export const component = 'CartStartCheckout'
-export const exported = '@graphcommerce/magento-cart'
-export const ifConfig: IfConfig = 'googleAnalyticsId'
-
-export function GaCartStartCheckout(props: PluginProps) {
- const { Prev, onStart, ...rest } = props
- return (
- {
- gtagBeginCheckout(cart)
- return onStart?.(e, cart)
- }}
- />
- )
-}
-
-export const Plugin = GaCartStartCheckout
diff --git a/packages/googleanalytics/plugins/GaProductListItem.tsx b/packages/googleanalytics/plugins/GaProductListItem.tsx
deleted file mode 100644
index 042f63aefa..0000000000
--- a/packages/googleanalytics/plugins/GaProductListItem.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { ProductListItemProps } from '@graphcommerce/magento-product'
-import { IfConfig, PluginProps } from '@graphcommerce/next-config'
-import { useEventCallback } from '@mui/material'
-import { useGoogleAnalyticsListItemHandler } from '../components/GoogleAnalyticsItemList'
-
-export const component = 'ProductListItem'
-export const exported = '@graphcommerce/magento-product'
-export const ifConfig: IfConfig = 'googleAnalyticsId'
-
-function GaProductListItemsBase(props: PluginProps) {
- const { Prev, onClick, ...rest } = props
- const handle = useGoogleAnalyticsListItemHandler(rest)
-
- return (
- {
- handle()
- onClick?.(e, item)
- })}
- />
- )
-}
-
-export const Plugin = GaProductListItemsBase
diff --git a/packages/googleanalytics/plugins/GaProductListItemsBase.tsx b/packages/googleanalytics/plugins/GaProductListItemsBase.tsx
deleted file mode 100644
index 8390a7b8db..0000000000
--- a/packages/googleanalytics/plugins/GaProductListItemsBase.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import type { ProductItemsGridProps } from '@graphcommerce/magento-product'
-import { IfConfig, PluginProps } from '@graphcommerce/next-config'
-import { GoogleAnalyticsItemList } from '../components/GoogleAnalyticsItemList'
-
-export const component = 'ProductListItemsBase'
-export const exported = '@graphcommerce/magento-product'
-export const ifConfig: IfConfig = 'googleAnalyticsId'
-
-export function GaProductListItemsBase(props: PluginProps) {
- const { Prev, ...rest } = props
- return (
-
-
-
- )
-}
-
-export const Plugin = GaProductListItemsBase
diff --git a/packages/googleanalytics/plugins/GaRemoveItemFromCart.tsx b/packages/googleanalytics/plugins/GaRemoveItemFromCart.tsx
deleted file mode 100644
index b224d8111e..0000000000
--- a/packages/googleanalytics/plugins/GaRemoveItemFromCart.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import type { RemoveItemFromCart } from '@graphcommerce/magento-cart-items'
-import { IfConfig, ReactPlugin } from '@graphcommerce/next-config'
-import { gtagRemoveFromCart } from '../events/gtagRemoveFromCart/gtagRemoveFromCart'
-
-export const component = 'RemoveItemFromCart'
-export const exported = '@graphcommerce/magento-cart-items/components/RemoveItemFromCart/RemoveItemFromCart'
-export const ifConfig: IfConfig = 'googleAnalyticsId'
-
-export const GaRemoveItemFromCart: ReactPlugin = (props) => {
- const { Prev, uid, quantity, prices, product, buttonProps } = props
-
- return (
- {
- gtagRemoveFromCart({
- __typename: 'Cart',
- items: [{ uid, __typename: 'SimpleCartItem', product, quantity, prices }],
- })
- buttonProps?.onClick?.(e)
- },
- }}
- />
- )
-}
-
-export const Plugin = GaRemoveItemFromCart
diff --git a/packages/googleanalytics/plugins/GaRemoveItemFromCartFab.tsx b/packages/googleanalytics/plugins/GaRemoveItemFromCartFab.tsx
deleted file mode 100644
index 5677bc912b..0000000000
--- a/packages/googleanalytics/plugins/GaRemoveItemFromCartFab.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import type { RemoveItemFromCartFab } from '@graphcommerce/magento-cart-items'
-import { IfConfig, ReactPlugin } from '@graphcommerce/next-config'
-import { gtagRemoveFromCart } from '../events/gtagRemoveFromCart/gtagRemoveFromCart'
-
-export const component = 'RemoveItemFromCartFab'
-export const exported = '@graphcommerce/magento-cart-items/components/RemoveItemFromCart/RemoveItemFromCartFab'
-export const ifConfig: IfConfig = 'googleAnalyticsId'
-
-export const GaRemoveItemFromCartFab: ReactPlugin = (props) => {
- const { Prev, uid, quantity, prices, product, fabProps } = props
-
- return (
- {
- gtagRemoveFromCart({
- __typename: 'Cart',
- items: [{ uid, __typename: 'SimpleCartItem', product, quantity, prices }],
- })
- fabProps?.onClick?.(e)
- },
- }}
- />
- )
-}
-
-export const Plugin = GaRemoveItemFromCartFab
diff --git a/packages/googleanalytics/plugins/GaUpdateItemQuantity.tsx b/packages/googleanalytics/plugins/GaUpdateItemQuantity.tsx
deleted file mode 100644
index 821b5499ee..0000000000
--- a/packages/googleanalytics/plugins/GaUpdateItemQuantity.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import type { UpdateItemQuantityProps } from '@graphcommerce/magento-cart-items'
-import { IfConfig, PluginProps } from '@graphcommerce/next-config'
-
-export const component = 'UpdateItemQuantity'
-export const exported = '@graphcommerce/magento-cart-items/components/UpdateItemQuantity/UpdateItemQuantity'
-export const ifConfig: IfConfig = 'googleAnalyticsId'
-
-/**
- * When a product is added to the Cart, by using the + button on cart page, send a Google Analytics
- * event
- */
-function GaUpdateItemQuantity(props: PluginProps) {
- const { Prev, onComplete, quantity, ...rest } = props
-
- return (
- {
- const original = onComplete?.(data, variables)
- const diffQuantity = variables.quantity - quantity
- if (diffQuantity === 0) return original
-
- const itemId = variables.uid
- const addedItem = data.data?.updateCartItems?.cart.items?.find(
- (item) => item?.uid === itemId,
- )
-
- if (addedItem && addedItem.prices && addedItem.prices.row_total_including_tax.value) {
- // we need to manually calculate pricePerItemInclTax (https://github.com/magento/magento2/issues/33848)
- const pricePerItemInclTax =
- addedItem.prices.row_total_including_tax.value / addedItem.quantity
- const addToCartValue = pricePerItemInclTax * diffQuantity
-
- globalThis.gtag?.('event', 'add_to_cart', {
- currency: addedItem?.prices?.price.currency,
- value: addToCartValue,
- items: [
- {
- item_id: addedItem?.product.sku,
- item_name: addedItem?.product.name,
- currency: addedItem?.prices?.price.currency,
- price: pricePerItemInclTax,
- quantity: variables.quantity,
- discount: addedItem?.prices?.discounts?.reduce(
- // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
- (sum, discount) => sum + (discount?.amount?.value ?? 0),
- 0,
- ),
- },
- ],
- })
- }
-
- return original
- }}
- />
- )
-}
-
-export const Plugin = GaUpdateItemQuantity
diff --git a/packages/googletagmanager/components/GoogleTagManagerScript.tsx b/packages/googletagmanager/components/GoogleTagManagerScript.tsx
index 19bec20dbf..81288e7952 100644
--- a/packages/googletagmanager/components/GoogleTagManagerScript.tsx
+++ b/packages/googletagmanager/components/GoogleTagManagerScript.tsx
@@ -1,23 +1,10 @@
import { useStorefrontConfig } from '@graphcommerce/next-ui'
-import { useRouter } from 'next/router'
import Script from 'next/script'
-import { useEffect } from 'react'
export function GoogleTagManagerScript() {
- const { events } = useRouter()
const id =
useStorefrontConfig().googleTagmanagerId ?? import.meta.graphCommerce.googleTagmanagerId
- useEffect(() => {
- const onRouteChangeComplete = (url: string) => {
- const dataLayer = globalThis.dataLayer as Record[] | undefined
- dataLayer?.push({ event: 'pageview', page: url })
- }
-
- events.on('routeChangeComplete', onRouteChangeComplete)
- return () => events.off('routeChangeComplete', onRouteChangeComplete)
- }, [events])
-
return (
<>