From a8a67d5e358c5cfc4bd4e797010f141a4057a426 Mon Sep 17 00:00:00 2001 From: Christel Westerberg Date: Thu, 11 Jul 2024 10:12:46 +0200 Subject: [PATCH] feat: add tracking events for loyalty pages --- app/[lang]/(live)/layout.tsx | 3 + .../ContentType/LoyaltyPage/LoyaltyPage.tsx | 6 + components/Current/TrackingSDK.tsx | 111 ++++++++++++++++++ lib/graphql/Query/LoyaltyPage.graphql | 11 ++ .../routers/contentstack/loyaltyPage/query.ts | 27 +++++ types/components/tracking.ts | 27 +++++ types/window.d.ts | 1 + 7 files changed, 186 insertions(+) create mode 100644 components/Current/TrackingSDK.tsx diff --git a/app/[lang]/(live)/layout.tsx b/app/[lang]/(live)/layout.tsx index c7c523d6e..2cb8bdb64 100644 --- a/app/[lang]/(live)/layout.tsx +++ b/app/[lang]/(live)/layout.tsx @@ -43,6 +43,9 @@ export default async function RootLayout({ id="Cookiebot" src="https://consent.cookiebot.com/uc.js" /> + diff --git a/components/ContentType/LoyaltyPage/LoyaltyPage.tsx b/components/ContentType/LoyaltyPage/LoyaltyPage.tsx index dbd681e1f..c8230d818 100644 --- a/components/ContentType/LoyaltyPage/LoyaltyPage.tsx +++ b/components/ContentType/LoyaltyPage/LoyaltyPage.tsx @@ -1,5 +1,6 @@ import { serverClient } from "@/lib/trpc/server" +import TrackingSDK from "@/components/Current/TrackingSDK" import { Blocks } from "@/components/Loyalty/Blocks" import Sidebar from "@/components/Loyalty/Sidebar" import MaxWidth from "@/components/MaxWidth" @@ -14,6 +15,10 @@ export default async function LoyaltyPage({ lang }: LangParams) { if (!loyaltyPage) { return null } + + const loyaltyPageTracking = + await serverClient().contentstack.loyaltyPage.tracking() + return (
{loyaltyPage.sidebar.length ? ( @@ -26,6 +31,7 @@ export default async function LoyaltyPage({ lang }: LangParams) { ) : null} +
) } diff --git a/components/Current/TrackingSDK.tsx b/components/Current/TrackingSDK.tsx new file mode 100644 index 000000000..d4b3d8d89 --- /dev/null +++ b/components/Current/TrackingSDK.tsx @@ -0,0 +1,111 @@ +"use client" + +import { usePathname } from "next/navigation" +import { useEffect } from "react" + +import { + SiteSectionObject, + TrackingSDKData, + TrackingSDKProps, +} from "@/types/components/tracking" + +function createSDKPageObject(trackingData: TrackingSDKData) { + const [lang, ...segments] = trackingData.pathName + .split("/") + .filter((seg: string) => seg) + + function getSiteSections(segments: string[]): SiteSectionObject { + /* + Adobe expects the properties sitesection1 - sitessection6, hence the for-loop below + The segments ['explore-scandic', 'wifi'] should result in: + { + sitesection1: "explore-scandic", + sitesection2: "explore-scandic|wifi", + sitesection3: "explore-scandic|wifi|", + sitesection4: "explore-scandic|wifi||", + sitesection5: "explore-scandic|wifi|||", + sitesection6: "explore-scandic|wifi||||", + } + */ + const sitesections = {} as SiteSectionObject + for (let i = 0; i < 6; i++) { + const key = ("sitesection" + (i + 1)) as keyof SiteSectionObject + + sitesections[key] = segments.slice(0, i + 1).join("|") + if (i > 0 && !segments[i]) { + sitesections[key] = sitesections[key].concat( + "|".repeat(i + 1 - segments.length) + ) + } + } + return sitesections + } + const siteSections = getSiteSections(segments) + const { host: domain } = window.location + const page_obj = { + event: "pageView", + pageInfo: { + pageName: segments.join("|"), + pageType: "contentpage", + pageId: trackingData.pageId, + channel: "", + siteSections, + domain, + siteversion: "new-web", + domainlanguage: trackingData.lang ? trackingData.lang : lang, + createDate: trackingData.createdDate, + publishDate: trackingData.publishedDate, + // sessionid: "", // base on what? + }, + } + return page_obj +} + +export default function TrackingSDK({ pageData }: TrackingSDKProps) { + const pathName = usePathname() + + function CookiebotCallbackOnAccept() { + const cookie = window._satellite.cookie.get("CookieConsent") + + if (window.Cookiebot?.changed && window.adobe) { + if (cookie?.includes("statistics:true")) { + window.adobe.optIn.approve(window.adobe.OptInCategories.ANALYTICS, true) + } else { + window.adobe.optIn.deny(window.adobe.OptInCategories.ANALYTICS, true) + } + window.adobe.optIn.complete() + console.warn("window.load event explicitly dispatched.") + window.dispatchEvent(new Event("load")) + } + } + + function CookebotCallbackOnDecline() { + if (window.Cookiebot?.changed && window.adobe) { + window.adobe.optIn.deny(window.adobe.OptInCategories.ANALYTICS, true) + } + } + + useEffect(() => { + if (window.adobeDataLayer) { + const trackingData = { ...pageData, pathName } + const pageObject = createSDKPageObject(trackingData) + window.adobeDataLayer.push(pageObject) + } + }, [pathName, pageData]) + + useEffect(() => { + // handle consent + window.addEventListener("CookiebotOnAccept", CookiebotCallbackOnAccept) + window.addEventListener("CookiebotOnDecline", CookebotCallbackOnDecline) + + return () => { + window.removeEventListener("CookiebotOnAccept", CookiebotCallbackOnAccept) + window.removeEventListener( + "CookiebotOnDecline", + CookebotCallbackOnDecline + ) + } + }, []) + + return null +} diff --git a/lib/graphql/Query/LoyaltyPage.graphql b/lib/graphql/Query/LoyaltyPage.graphql index 5ef8479f8..db83c1390 100644 --- a/lib/graphql/Query/LoyaltyPage.graphql +++ b/lib/graphql/Query/LoyaltyPage.graphql @@ -362,3 +362,14 @@ query GetFiNoSvUrlsLoyaltyPage($uid: String!) { } } } + +query GetTrackingLoyaltyPage($locale: String!, $uid: String!) { + loyalty_page(locale: $locale, uid: $uid) { + system { + locale + created_at + uid + updated_at + } + } +} diff --git a/server/routers/contentstack/loyaltyPage/query.ts b/server/routers/contentstack/loyaltyPage/query.ts index fe39443cd..096fb0492 100644 --- a/server/routers/contentstack/loyaltyPage/query.ts +++ b/server/routers/contentstack/loyaltyPage/query.ts @@ -1,6 +1,7 @@ import { GetLoyaltyPage, GetLoyaltyPageRefs, + GetTrackingLoyaltyPage, } from "@/lib/graphql/Query/LoyaltyPage.graphql" import { request } from "@/lib/graphql/request" import { notFound } from "@/server/errors/trpc" @@ -29,6 +30,10 @@ import { LoyaltyCardsGridEnum, SidebarTypenameEnum, } from "@/types/components/loyalty/enums" +import { + TrackingChannelEnum, + TrackingSDKPageData, +} from "@/types/components/tracking" function makeImageVaultImage(image: any) { return image && !!Object.keys(image).length @@ -224,4 +229,26 @@ export const loyaltyPageQueryRouter = router({ // Assert LoyaltyPage type to get correct typings for RTE fields return validatedLoyaltyPage.data as LoyaltyPage }), + tracking: contentstackExtendedProcedureUID.query(async ({ ctx }) => { + const { lang, uid } = ctx + + const response = await request(GetTrackingLoyaltyPage, { + locale: lang, + uid, + }) + + if (!response.data) { + throw notFound(response) + } + + const loyaltyTrackingData: TrackingSDKPageData = { + pageId: response.data.loyalty_page.system.uid, + lang: response.data.loyalty_page.system.locale, + publishedDate: response.data.loyalty_page.system.published_at, + createdDate: response.data.loyalty_page.system.created_at, + channel: TrackingChannelEnum["scandic-friends"], + } + + return loyaltyTrackingData + }), }) diff --git a/types/components/tracking.ts b/types/components/tracking.ts index 18bde308b..035609867 100644 --- a/types/components/tracking.ts +++ b/types/components/tracking.ts @@ -1,5 +1,32 @@ import type { Lang } from "@/constants/languages" +export enum TrackingChannelEnum { + "scandic-friends" = "scandic-friends", +} + +export type TrackingChannel = keyof typeof TrackingChannelEnum + +export type TrackingSDKPageData = { + pageId: string + createdDate: string + publishedDate: string + lang: Lang + channel: TrackingChannel +} + +export type TrackingSDKProps = { + pageData: TrackingSDKPageData +} + +export type TrackingSDKData = { + lang: Lang + pathName: string + pageId: string + publishedDate: string + createdDate: string +} + +// Old tracking setup types: export type TrackingProps = { pageData: { pageId: string diff --git a/types/window.d.ts b/types/window.d.ts index 86fde9b5c..9480a78ce 100644 --- a/types/window.d.ts +++ b/types/window.d.ts @@ -1,5 +1,6 @@ interface Window { datalayer: { [key: string]: any } + adobeDataLayer: any[] _satellite: { cookie: { get: (s: string) => string } } adobe: { OptInCategories: { ANALYTICS: string }