Skip to content

Commit

Permalink
Vite migration, Hydrogen package updates, new sitemap generation, Kla…
Browse files Browse the repository at this point in the history
…viyo analytics, code improvements (#60)

Migration:
- Migrates Remix's application builder from Remix compiler to Vite. See [Remix Vite documentation](https://remix.run/docs/en/main/guides/vite)

Routine:
- Updates `hydrogen` packages to `2024.10` and all other packages to latest
- Adds additional consent settings required for hydrogen `2024.10` version
- Add `"type": "module"` to `package.json` and adjust file exports or file extensions accordingly

Improvement:
- Utilizes Hydrogen's `sitemap` to paginate sitemaps for product and collection pages. Previously, large stores would generate a singular sitemap that would not be functional due to the large number of server side requests. Also cleans up logic for other sitemaps and product feed.

New:
- Adds default `KlaviyoEvents` component to `Analytics`

Minor improvement:
- Updates the cart graphql query to accommodate product bundles
- Adds logic to automatically scroll to hash on navigation
- Checks for `PACK_SECRET_TOKEN` before the Pack client initializes

Minor fix:
- Fixes the currency code fallback for the view cart analytics event. Previously an empty cart had a currency code of "XXX"
- Corrects hook usage in `usePreviewModeCustomerFetch` from change in release v1.9.3

Minor cleanup:
- Graphql cleanup and adds `codegen` dependencies
- Minor misc code cleanup
  • Loading branch information
jeremyagabriel authored Dec 9, 2024
1 parent f1779f0 commit a7e7aff
Show file tree
Hide file tree
Showing 72 changed files with 23,364 additions and 39,639 deletions.
File renamed without changes.
3 changes: 2 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
node_modules
.vscode
/.cache
/build
/dist
/public/build
/public/build
12 changes: 12 additions & 0 deletions app/components/Analytics/Analytics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {AnalyticsEvent} from './constants';
import {ElevarEvents} from './ElevarEvents';
import {FueledEvents} from './FueledEvents';
import {GA4Events} from './GA4Events';
import {KlaviyoEvents} from './KlaviyoEvents';
import {MetaPixelEvents} from './MetaPixelEvents';
import {TikTokPixelEvents} from './TikTokPixelEvents';

Expand All @@ -31,6 +32,7 @@ export const Analytics = memo(() => {
const enabledFueled = false;
const enabledElevar = !!ENV.PUBLIC_ELEVAR_SIGNING_KEY;
const enabledGA4 = !!ENV.PUBLIC_GA4_TAG_ID;
const enabledKlaviyo = !!ENV.PUBLIC_KLAVIYO_API_KEY;
const enabledMetaPixel = !!ENV.PUBLIC_META_PIXEL_ID;
const enabledTikTokPixel = !!ENV.PUBLIC_TIKTOK_PIXEL_ID;

Expand Down Expand Up @@ -66,6 +68,16 @@ export const Analytics = memo(() => {
/>
)}

{enabledKlaviyo && (
<KlaviyoEvents
klaviyoApiKey={ENV.PUBLIC_KLAVIYO_API_KEY}
register={register}
subscribe={subscribe}
customer={customer}
debug={DEBUG}
/>
)}

{enabledMetaPixel && (
<MetaPixelEvents
metaPixelId={ENV.PUBLIC_META_PIXEL_ID}
Expand Down
6 changes: 5 additions & 1 deletion app/components/Analytics/ElevarEvents/ElevarEvents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export function ElevarEvents({
const settings: Record<string, any> = {};
const config = (
await import(
/* @vite-ignore */
`https://shopify-gtm-suite.getelevar.com/configs/${elevarSigningKey}/config.js`
)
).default;
Expand All @@ -73,7 +74,10 @@ export function ElevarEvents({
: config.script_src_custom_pages;

if (scriptUrl) {
const {handler} = await import(scriptUrl);
const {handler} = await import(
/* @vite-ignore */
scriptUrl
);
await handler(config, settings);
}
setScriptLoaded(true);
Expand Down
6 changes: 5 additions & 1 deletion app/components/Analytics/ElevarEvents/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,12 +331,16 @@ const viewCartEvent = ({
window.location.pathname) ||
(previousPath?.startsWith('/collections') && previousPath) ||
'';
const cartCurrencyCode = cart?.cost?.totalAmount?.currencyCode;
const event = {
event: 'dl_view_cart',
user_properties: generateUserProperties({customer}),
cart_total: cart?.cost?.totalAmount?.amount || '0.0',
ecommerce: {
currencyCode: cart?.cost?.totalAmount?.currencyCode || shop?.currency,
currencyCode:
cartCurrencyCode && cartCurrencyCode !== 'XXX'
? cartCurrencyCode
: shop?.currency,
actionField: {list: 'Shopping Cart'},
impressions:
flattenConnection(cart?.lines)?.slice(0, 7).map(mapCartLine(list)) ||
Expand Down
6 changes: 5 additions & 1 deletion app/components/Analytics/FueledEvents/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,11 +328,15 @@ const viewCartEvent = ({
window.location.pathname) ||
(previousPath?.startsWith('/collections') && previousPath) ||
'';
const cartCurrencyCode = cart?.cost?.totalAmount?.currencyCode;
const event = {
event: 'dl_view_cart',
user_properties: generateUserProperties({customer}),
ecommerce: {
currency_code: cart?.cost?.totalAmount?.currencyCode || shop?.currency,
currencyCode:
cartCurrencyCode && cartCurrencyCode !== 'XXX'
? cartCurrencyCode
: shop?.currency,
actionField: {list: 'Shopping Cart'},
products:
flattenConnection(cart?.lines)?.slice(0, 12).map(mapCartLine(list)) ||
Expand Down
6 changes: 5 additions & 1 deletion app/components/Analytics/GA4Events/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,15 @@ const customerEvent = ({
(windowPathname.startsWith('/collections') && windowPathname) ||
(previousPath?.startsWith('/collections') && previousPath) ||
'';
const cartCurrencyCode = cart?.cost?.totalAmount?.currencyCode;
const event = {
event: 'user_data',
user_properties: generateUserProperties({customer}),
ecommerce: {
currency_code: cart?.cost?.totalAmount?.currencyCode || shop?.currency,
currencyCode:
cartCurrencyCode && cartCurrencyCode !== 'XXX'
? cartCurrencyCode
: shop?.currency,
cart_contents: {
products:
flattenConnection(cart?.lines)?.map(mapCartLine(list)) || [],
Expand Down
71 changes: 71 additions & 0 deletions app/components/Analytics/KlaviyoEvents/KlaviyoEvents.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import {useEffect} from 'react';

import {useLoadScript} from '~/hooks';

import {AnalyticsEvent} from '../constants';

import {
ANALYTICS_NAME,
addToCartEvent,
customerSubscribeEvent,
viewProductEvent,
} from './events';

type Data = Record<string, any>;

export function KlaviyoEvents({
register,
subscribe,
customer,
debug = false,
klaviyoApiKey,
}: {
register: (key: string) => {ready: () => void};
subscribe: (arg0: any, arg1: any) => void;
customer?: Record<string, any> | null;
debug?: boolean;
klaviyoApiKey: string;
}) {
let ready: (() => void) | undefined = undefined;
if (register) {
ready = register(ANALYTICS_NAME).ready;
}

const scriptStatus = useLoadScript(
{
id: 'klaviyo-script',
src: `https://static.klaviyo.com/onsite/js/klaviyo.js?company_id=${klaviyoApiKey}`,
},
'body',
!!klaviyoApiKey,
);

useEffect(() => {
if (!klaviyoApiKey) {
console.error(
`${ANALYTICS_NAME}: ❌ error: \`klaviyoApiKey\` must be passed in.`,
);
}
if (scriptStatus !== 'done') return;
if (!ready || !subscribe) {
console.error(
`${ANALYTICS_NAME}: ❌ error: \`register\` and \`subscribe\` must be passed in from Hydrogen's useAnalytics hook.`,
);
return;
}
/* register analytics events only until script is ready */
subscribe(AnalyticsEvent.PRODUCT_VIEWED, (data: Data) => {
viewProductEvent({...data, customer, debug});
});
subscribe(AnalyticsEvent.PRODUCT_ADD_TO_CART, (data: Data) => {
addToCartEvent({...data, customer, debug});
});
subscribe(AnalyticsEvent.CUSTOMER_SUBSCRIBED, (data: Data) => {
customerSubscribeEvent({...data, debug});
});
ready();
if (debug) console.log(`${ANALYTICS_NAME}: 🔄 subscriptions are ready.`);
}, [customer?.id, debug, scriptStatus]);

return null;
}
158 changes: 158 additions & 0 deletions app/components/Analytics/KlaviyoEvents/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import {AnalyticsEvent} from '../constants';

import {
pathWithoutLocalePrefix,
mapCartLine,
mapProductPageVariant,
} from './utils';

export const ANALYTICS_NAME = 'KlaviyoEvents';

const logSubscription = ({
data,
analyticsEvent,
}: {
data: Record<string, any>;
analyticsEvent: string;
}) => {
console.log(
`${ANALYTICS_NAME}: 📥 subscribed to analytics for \`${analyticsEvent}\`:`,
data,
);
};

const logError = ({
analyticsEvent,
message = 'Unknown error',
}: {
analyticsEvent: string;
message?: string | unknown;
}) => {
console.error(
`${ANALYTICS_NAME}: ❌ error from \`${analyticsEvent}\`: ${message}`,
);
};

export const identifyCustomer = async ({
email,
debug,
}: {
email: string;
debug?: boolean;
}) => {
if (!window.klaviyo) return;
await window.klaviyo.identify({email});
if (debug)
console.log(
`${ANALYTICS_NAME}: 🚀 event emitted for \`identify customer\`:`,
{email},
);
};

export const emitEvent = async ({
email,
event,
properties,
debug,
}: {
email: string;
event: string;
properties: Record<string, any> | null;
debug?: boolean;
}) => {
if (!window.klaviyo) return;
const klaviyoIdentified = await window.klaviyo.isIdentified();
if (klaviyoIdentified) {
window.klaviyo.push(['track', event, properties]);
} else if (email) {
await window.klaviyo.identify({email});
window.klaviyo.push(['track', event, properties]);
}
if (debug)
console.log(`${ANALYTICS_NAME}: 🚀 event emitted for \`${event}\`:`, {
event,
email,
properties,
});
};

const viewProductEvent = ({
debug,
...data
}: Record<string, any> & {debug?: boolean}) => {
const analyticsEvent = AnalyticsEvent.PRODUCT_VIEWED;
try {
if (debug) logSubscription({data, analyticsEvent});

const {customer} = data;
const {selectedVariant} = data.customData;
const previousPath = sessionStorage.getItem('PREVIOUS_PATH');
const list = previousPath?.startsWith('/collections') ? previousPath : '';
emitEvent({
email: customer?.email,
event: 'Viewed Product',
properties: mapProductPageVariant(list)(selectedVariant),
debug,
});
} catch (error) {
logError({
analyticsEvent,
message: error instanceof Error ? error.message : error,
});
}
};

const addToCartEvent = ({
debug,
...data
}: Record<string, any> & {debug?: boolean}) => {
const analyticsEvent = AnalyticsEvent.PRODUCT_ADD_TO_CART;
try {
if (debug) logSubscription({data, analyticsEvent});

const {currentLine, customer} = data;
if (!currentLine)
throw new Error('`cart` and/or `currentLine` parameters are missing.');

const previousPath = sessionStorage.getItem('PREVIOUS_PATH');
const windowPathname = pathWithoutLocalePrefix(window.location.pathname);
const list =
(windowPathname.startsWith('/collections') && windowPathname) ||
(previousPath?.startsWith('/collections') && previousPath) ||
'';
const productProps = mapCartLine(list)(currentLine);
emitEvent({
email: customer?.email,
event: 'Added to Cart',
properties: productProps,
debug,
});
} catch (error) {
logError({
analyticsEvent,
message: error instanceof Error ? error.message : error,
});
}
};

const customerSubscribeEvent = ({
debug,
...data
}: Record<string, any> & {debug?: boolean}) => {
const analyticsEvent = AnalyticsEvent.CUSTOMER_SUBSCRIBED;
try {
if (debug) logSubscription({data, analyticsEvent});

const {email} = data;
if (!email) throw new Error('`email` parameter is missing.');

identifyCustomer({email, debug});
} catch (error) {
logError({
analyticsEvent,
message: error instanceof Error ? error.message : error,
});
}
};

export {addToCartEvent, customerSubscribeEvent, viewProductEvent};
1 change: 1 addition & 0 deletions app/components/Analytics/KlaviyoEvents/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {KlaviyoEvents} from './KlaviyoEvents';
Loading

0 comments on commit a7e7aff

Please sign in to comment.