Skip to content

Instantly share code, notes, and snippets.

@elephantsneverforget
Created November 10, 2022 17:23
Show Gist options
  • Save elephantsneverforget/77ffd7ba708719ee8958ac4ddd73ce70 to your computer and use it in GitHub Desktop.
Save elephantsneverforget/77ffd7ba708719ee8958ac4ddd73ce70 to your computer and use it in GitHub Desktop.
/**
* Google Tag Manager
* @returns null
*/
import {
ClientAnalytics,
flattenConnection,
loadScript,
useCart,
useUrl,
} from '@shopify/hydrogen';
import Analytics from 'analytics';
import googleTagManager from '@analytics/google-tag-manager';
import {doNotTrackEnabled} from 'analytics-plugin-do-not-track';
import {useEffect} from 'react';
import {v4 as uuidv4} from 'uuid';
import {getIdFromGid} from 'Js/lib/gid';
import Cookies from 'js-cookie';
import {getCookie, getCookiFromPartial} from 'Js/lib/cookies';
import dynamicYield from 'Js/lib/dynamic-yield-tracking.js';
let init = false;
let prevUrl: string | null = null;
let elevarScriptInit = false;
export default function GTM({containerId}: {containerId: string}) {
const url = useUrl();
const {cost, lines, attributes} = useCart();
useEffect(() => {
if (!elevarScriptInit) {
elevarScriptInit = true;
loadScript(
'https://shopify-gtm-suite.getelevar.com/shops/6c9391c966ab7077151d58ba302717bdf46ebca6/events.js',
).then((loaded: boolean) => {
if (loaded && window.ElevarGtmSuiteListener) {
window.ElevarGtmSuiteListener.handlers.listen({
ssUrl: 'https://4ieigxsa2ffkf7izglms.skims.com',
});
}
});
}
}, []);
// Sets session store with utm params
useEffect(() => {
if (
url.search.includes('utm_') ||
url.search.includes('fbclid') ||
url.search.includes('gclid') ||
url.search.includes('ttclid') ||
url.search.includes('irclid')
) {
url.searchParams.forEach((value: string, key: string) => {
if (!key || !value) return;
const prevItem = window.sessionStorage.getItem(key);
if (prevItem && prevItem === value) return;
window.sessionStorage.setItem(key, value);
});
}
if (url.search.includes('clientId')) {
const attentiveClientId = url.searchParams.get('clientId');
if (attentiveClientId) {
Cookies.set('__attentive_client_user_id', attentiveClientId, {
expires: 30,
secure: true,
});
}
}
}, [url]);
useEffect(() => {
if (!init) {
init = true;
const dontTrack = doNotTrackEnabled();
const analytics = Analytics({
app: 'skims-hydrogen',
plugins: [
googleTagManager({
containerId,
enabled: !dontTrack,
}),
],
});
// Listen for events from Hydrogen
ClientAnalytics.subscribe(
ClientAnalytics.eventNames.PAGE_VIEW,
(payload) => {
const device = parseDeviceInfo();
const {shopify, normalizedRscUrl: payloadUrl} = payload;
const {
customer,
customerCurrencyCode = 'USD',
userId,
} = shopify || {};
const userProperties = parseCustomerData(customer, userId);
const {cartTotal, items} = parseCartData(cost, lines, attributes);
const marketing = parseMarketingInfo(customer, userId);
// Ignore sub page loads
if (prevUrl === payloadUrl) return;
prevUrl = payloadUrl;
console.log('PAGE_VIEW');
analytics.page({event: 'dl_route_change'});
analytics.page({
event: 'dl_user_data',
device,
page: {
title: '',
},
event_id: uuidv4(),
event_time: new Date().toISOString(),
cart_total: cartTotal,
user_properties: userProperties,
marketing,
ecommerce: {
cart_contents: {
products: items,
},
currencyCode: customerCurrencyCode,
},
});
},
);
ClientAnalytics.subscribe('VIEW_PLP', (payload) => {
const {shopify} = payload;
const {
customer,
productListResults,
customerCurrencyCode = 'USD',
userId,
} = shopify || {};
const userProperties = parseCustomerData(customer, userId);
const plpProducts = productListResults.map(
(product: any, idx: number) => {
const productId = getIdFromGid(product.id, 'Product');
const [variant] = flattenConnection(product.variants) as any;
const {priceV2, compareAtPriceV2, image, title, id} =
variant || ({} as any);
const variantId = getIdFromGid(id, 'ProductVariant');
return {
id: variant.sku || productId,
name: title,
brand: product?.vendor || '',
category: product?.productType || '',
variant: title,
price: priceV2?.amount || '',
quantity: 1,
product_id: productId,
position: idx,
variant_id: variantId,
compare_at_price: compareAtPriceV2?.amount || '',
image: image?.url || '',
inventory: '',
};
},
);
const marketing = parseMarketingInfo(customer, userId);
console.log('VIEW_PLP');
analytics.page({
event: 'dl_view_item_list',
event_id: uuidv4(),
event_time: new Date().toISOString(),
user_properties: userProperties,
marketing,
ecommerce: {
currencyCode: customerCurrencyCode,
impressions: plpProducts,
},
});
});
ClientAnalytics.subscribe('VIEW_SEACH_RESULTS', (payload) => {
const {shopify} = payload;
const {
customer,
customerCurrencyCode = 'USD',
productListResults,
userId,
} = shopify || {};
const userProperties = parseCustomerData(customer, userId);
const searchProducts = productListResults
.filter((product: any) => product)
.map((product: any, idx: number) => {
const productId = getIdFromGid(product.id, 'Product');
const [variant] = flattenConnection(product.variants) as any;
const {priceV2, compareAtPriceV2, image, title, id} =
variant || ({} as any);
const variantId = getIdFromGid(id, 'ProductVariant');
return {
id: variant.sku || productId,
name: title,
brand: product?.vendor || '',
category: product?.productType || '',
variant: title,
price: priceV2?.amount || '',
quantity: 1,
product_id: productId,
position: idx,
variant_id: variantId,
compare_at_price: compareAtPriceV2?.amount || '',
image: image?.url || '',
inventory: '',
};
});
const marketing = parseMarketingInfo(customer, userId);
console.log('VIEW_SEACH_RESULTS');
analytics.page({
event: 'dl_view_search_results',
event_id: uuidv4(),
event_time: new Date().toISOString(),
user_properties: userProperties,
marketing,
ecommerce: {
currencyCode: customerCurrencyCode,
actionField: {list: 'search results'},
impressions: searchProducts,
},
});
});
ClientAnalytics.subscribe('SELECT_ITEM', (payload) => {
const {shopify} = payload;
const {
customer,
customerCurrencyCode = 'USD',
selectedProduct,
userId,
} = shopify || {};
const userProperties = parseCustomerData(customer, userId);
const productId = selectedProduct.id.toString();
const [variant] = selectedProduct.variants as any;
const {price, compareAtPrice, title, id} = variant || ({} as any);
const variantId = id.toString();
const productData = {
id: variant.sku || productId,
name: selectedProduct.title,
brand: selectedProduct?.vendor || '',
category: selectedProduct?.productType || '',
variant: title,
price: price?.amount || '',
quantity: 1,
product_id: productId,
variant_id: variantId,
compare_at_price: compareAtPrice?.amount || price?.amount || '',
image: selectedProduct.previewImageUrl || '',
inventory: '',
};
const marketing = parseMarketingInfo(customer, userId);
console.log('SELECT_ITEM');
analytics.page({
event: 'dl_select_item',
event_id: uuidv4(),
event_time: new Date().toISOString(),
user_properties: userProperties,
marketing,
ecommerce: {
currencyCode: customerCurrencyCode,
click: {
actionField: {
list: window?.location?.pathname || '',
action: 'click',
},
products: [productData],
},
},
});
});
ClientAnalytics.subscribe(
ClientAnalytics.eventNames.VIEWED_PRODUCT,
(payload) => {
const device = parseDeviceInfo();
const {shopify, referrer} = payload;
const {
customer,
customerCurrencyCode = 'USD',
products: [product],
userId,
} = shopify || {};
const referrerUrl = referrer ? new URL(referrer) : {pathname: null};
const userProperties = parseCustomerData(customer, userId);
const productId = getIdFromGid(product.id, 'Product');
const variantId = getIdFromGid(product.variantId, 'ProductVariant');
const productData = {
id: product.sku || productId,
name: product?.name || '',
brand: product?.brand || '',
category: product?.category || '',
variant: product.variant,
price: product?.price || '',
quantity: 1,
product_id: productId,
variant_id: variantId,
compare_at_price: product?.compareAtPrice || '',
image: product?.image?.url || '',
inventory: '',
};
const marketing = parseMarketingInfo(customer, userId);
console.log('VIEWED_PRODUCT');
analytics.page({
event: 'dl_view_item',
device,
event_id: uuidv4(),
event_time: new Date().toISOString(),
user_properties: userProperties,
marketing,
ecommerce: {
currencyCode: customerCurrencyCode,
detail: {
actionField: {
list: referrerUrl?.pathname || referrer || '',
action: 'detail',
},
products: [productData],
},
},
});
},
);
ClientAnalytics.subscribe(
ClientAnalytics.eventNames.ADD_TO_CART,
(payload) => {
const {addedCartLines, cart = {}, shopify, referrer} = payload;
const {
customer,
customerCurrencyCode = 'USD',
userId,
} = shopify || {};
const referrerUrl = referrer ? new URL(referrer) : {pathname: null};
const userProperties = parseCustomerData(customer, userId);
const payloadCartLines = flattenConnection(cart.lines);
const {items} = parseCartData(
cart.cost,
payloadCartLines,
cart.attributes,
);
const merchandiseIds = addedCartLines.map(
(item: any) => item.merchandiseId,
);
const addedProducts = items.filter((item: any) => {
const hasJustAdded = merchandiseIds.includes(item.merchandiseId);
if (hasJustAdded) {
delete item.merchandiseId;
delete item.position;
return item;
}
return false;
});
const marketing = parseMarketingInfo(customer, userId);
dynamicYield.addItem(addedProducts[0], items, customerCurrencyCode);
console.log('ADD_TO_CART');
analytics.page({
event: 'dl_add_to_cart',
user_properties: userProperties,
event_id: uuidv4(),
event_time: new Date().toISOString(),
marketing,
ecommerce: {
currencyCode: customerCurrencyCode,
add: {
actionField: {
list: referrerUrl?.pathname || referrer || '',
},
products: addedProducts, // Array of added products
},
},
});
},
);
ClientAnalytics.subscribe('REMOVE_FROM_CART_CUSTOM', (payload) => {
const {cart = {}, shopify, referrer, removedCartItems} = payload;
const {customer, customerCurrencyCode = 'USD', userId} = shopify || {};
const referrerUrl = referrer ? new URL(referrer) : {pathname: null};
const userProperties = parseCustomerData(customer, userId);
const {items} = parseCartData(
cart.cost,
removedCartItems,
cart.attributes,
);
const removedProducts = items.map((item: any) => {
delete item.merchandiseId;
delete item.position;
return item;
});
const marketing = parseMarketingInfo(customer, userId);
dynamicYield.removeItem(
removedCartItems[0],
items,
customerCurrencyCode,
);
console.log('REMOVE_FROM_CART');
analytics.page({
event: 'dl_add_to_cart',
user_properties: userProperties,
event_id: uuidv4(),
event_time: new Date().toISOString(),
marketing,
ecommerce: {
currencyCode: customerCurrencyCode,
remove: {
actionField: {
list: referrerUrl?.pathname || referrer || '',
action: 'remove',
},
products: removedProducts, // Array of removed products
},
},
});
});
ClientAnalytics.subscribe('VIEW_CART_CUSTOM', (payload) => {
const {shopify} = payload;
const {
cart = {},
customer,
customerCurrencyCode = 'USD',
userId,
} = shopify || {};
const userProperties = parseCustomerData(customer, userId);
const {cartTotal, items} = parseCartData(
cart.cost,
cart.lines,
cart.attributes,
);
const sanitizedCartItems = items.map((item: any) => {
delete item.merchandiseId;
return item;
});
const marketing = parseMarketingInfo(customer, userId);
console.log('VIEW_CART');
analytics.page({
event: 'dl_view_cart',
user_properties: userProperties,
event_id: uuidv4(),
event_time: new Date().toISOString(),
cart_total: cartTotal,
marketing,
ecommerce: {
currencyCode: customerCurrencyCode,
actionField: {
list: 'Shopping Cart',
},
impressions: sanitizedCartItems, // Array of cart products
},
});
});
ClientAnalytics.subscribe('ACCOUNT_LOGIN', (payload) => {
const {shopify} = payload;
const {customer, userId} = shopify || {};
const userProperties = parseCustomerData(customer, userId);
const marketing = parseMarketingInfo(customer, userId);
dynamicYield.login(customer.email);
console.log('ACCOUNT_LOGIN');
analytics.page({
event: 'dl_login',
page: {title: 'Login'},
user_properties: userProperties,
event_id: uuidv4(),
event_time: new Date().toISOString(),
marketing,
});
});
} // Init wrapper end
}, [containerId, attributes, cost, lines]);
function parseCustomerData(data: any, uuid: string) {
const [address] = flattenConnection(data?.addresses || []) as any;
const orders = flattenConnection(data?.orders || []);
const customerId = data?.id ? getIdFromGid(data.id, 'Customer') : '';
return {
customer_address_1: address?.address1 || '',
customer_address_2: address?.address2 || '',
customer_city: address?.city || '',
customer_country: address?.country || '',
customer_email: data?.email || '',
customer_first_name: data?.firstName || '',
customer_id: customerId || '',
customer_last_name: data?.lastName || '',
customer_order_count: orders?.length.toString() || '0',
customer_phone: data?.phone || '',
customer_province: address?.province || '',
customer_province_code: address?.provinceCode || '',
customer_tags: data?.tags?.join(', '),
customer_total_spent:
orders
?.reduce(
(prev: number, curr: any) =>
prev + parseFloat(curr.currentTotalPrice.amount || 0),
0,
)
.toString() || '0',
customer_zip: address?.zip || '',
user_consent: '',
visitor_type: customerId ? 'logged_in' : 'guest',
user_id: customerId || uuid,
};
}
function parseDeviceInfo() {
if (!window) return {};
const deviceInfo = {
screen_resolution: `${window.screen.width * window.devicePixelRatio}x${
window.screen.height * window.devicePixelRatio
}`,
viewport_size: `${window.screen.width}x${window.screen.height}`,
encoding: document.characterSet || 'UTF-8',
language: window.navigator.language,
colors: screen.colorDepth,
};
return deviceInfo;
}
function parseMarketingInfo(customer: any, uuid: string) {
if (!window) return {};
const urlParams = new URLSearchParams(window.location.search);
const utmParams: any = {};
urlParams.forEach((value: string, key: string) => {
if (key.includes('utm_')) {
utmParams[key] = value;
}
});
const gaId = getCookiFromPartial('_ga_');
const data = {
_fbp: getCookie('_fbp') || '',
_fbc: getCookie('_fbc') || '',
_ga: getCookie('_ga') || '',
_gaexp: getCookie('_gaexp') || '',
_gid: getCookie('_gid') || '',
__utma: getCookie('__utma') || '',
ttclid: getCookie('ttclid') || '',
crto_mapped_user_id: getCookie('crto_mapped_user_id') || '',
crto_is_user_optout: getCookie('crto_is_user_optout') || '',
user_id: customer?.id ? getIdFromGid(customer.id, 'Customer') : uuid,
...utmParams,
};
if (gaId) {
data[gaId.name] = gaId.value;
}
return data;
}
function parseCartData(cartCost: any, cartLines: any[], cartAttributes: any) {
return {
attributes: cartAttributes || [],
cartTotal: cartCost?.totalAmount?.amount || '0',
currencyCode: cartCost?.totalAmount?.currencyCode || 'USD',
items: cartLines.map((item, idx: number) => {
const {merchandise, quantity} = item as any;
const {product, title, priceV2, compareAtPriceV2, image, id} =
merchandise;
const productId = getIdFromGid(product.id, 'Product');
const variantId = getIdFromGid(id, 'ProductVariant');
return {
merchandiseId: id,
id: merchandise.sku || productId,
name: product?.title || '',
brand: product?.vendor || '',
category: product?.productType || '',
variant: title,
price: priceV2?.amount || '',
position: idx,
quantity,
product_id: productId,
variant_id: variantId,
compare_at_price: compareAtPriceV2?.amount || '',
image: image?.url || '',
inventory: '',
};
}),
};
}
return null;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment