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 }