diff --git a/components/Current/Header/LoginButton.tsx b/components/Current/Header/LoginButton.tsx index d74d63e80..7ddbe427f 100644 --- a/components/Current/Header/LoginButton.tsx +++ b/components/Current/Header/LoginButton.tsx @@ -1,6 +1,6 @@ "use client" -import { PropsWithChildren, useEffect } from "react" +import { PropsWithChildren } from "react" import { login } from "@/constants/routes/handleAuth" @@ -31,17 +31,6 @@ export default function LoginButton({ ? `${login[lang]}?redirectTo=${encodeURIComponent(pathName)}` : login[lang] - useEffect(() => { - document - .getElementById(trackingId) - ?.addEventListener("click", () => trackLoginClick(position)) - return () => { - document - .getElementById(trackingId) - ?.removeEventListener("click", () => trackLoginClick(position)) - } - }, [position, trackingId]) - return ( trackLoginClick(position)} > {children} diff --git a/components/TempDesignSystem/Link/index.tsx b/components/TempDesignSystem/Link/index.tsx index 23cf15e46..e45d08954 100644 --- a/components/TempDesignSystem/Link/index.tsx +++ b/components/TempDesignSystem/Link/index.tsx @@ -4,6 +4,7 @@ import { usePathname, useRouter } from "next/navigation" import { startTransition, useCallback } from "react" import useRouterTransitionStore from "@/stores/router-transition" +import useTrackingStore from "@/stores/tracking" import { trackClick, trackPageViewStart } from "@/utils/tracking" @@ -27,6 +28,7 @@ export default function Link({ ...props }: LinkProps) { const currentPageSlug = usePathname() + const { setInitialPageLoadTime } = useTrackingStore() let isActive = active || currentPageSlug === href if (partialMatch && !isActive) { @@ -72,6 +74,7 @@ export default function Link({ return } e.preventDefault() + setInitialPageLoadTime(Date.now()) trackPageViewStart() startTransition(() => { startRouterTransition() diff --git a/components/TrackingSDK/Client.tsx b/components/TrackingSDK/Client.tsx index 37933ba96..9239c9e70 100644 --- a/components/TrackingSDK/Client.tsx +++ b/components/TrackingSDK/Client.tsx @@ -1,7 +1,7 @@ "use client" import { usePathname } from "next/navigation" -import { useCallback, useEffect } from "react" +import { useCallback, useEffect, useRef } from "react" import { webviews } from "@/constants/routes/webviews" import useTrackingStore from "@/stores/tracking" @@ -10,11 +10,15 @@ import { createSDKPageObject } from "@/utils/tracking" import { TrackingSDKProps } from "@/types/components/tracking" -export default function TrackingSDK({ pageData, userData }: TrackingSDKProps) { +export default function TrackingSDK({ + pageData, + userData, + hotelInfo, +}: TrackingSDKProps) { const pathName = usePathname() const isWebview = webviews.includes(pathName) - const { hasRun, setHasRun } = useTrackingStore() - + const { hasRun, setHasRun, getPageLoadTime } = useTrackingStore() + const hasRunLocal = useRef(false) const CookiebotCallbackOnAccept = useCallback(() => { const cookie = window._satellite.cookie.get("CookieConsent") @@ -40,31 +44,36 @@ export default function TrackingSDK({ pageData, userData }: TrackingSDKProps) { } useEffect(() => { - if (!hasRun) { - const perfObserver = new PerformanceObserver((observedEntries) => { - const entry = observedEntries.getEntriesByType("navigation")[0] - - if (entry && window.adobeDataLayer) { - const trackingData = { ...pageData, pathName } - const pageObject = createSDKPageObject(trackingData) - - window.adobeDataLayer.push({ - event: "pageView", - pageInfo: pageObject, - userInfo: userData, - }) - perfObserver.disconnect() - } - }) - - perfObserver.observe({ - type: "navigation", - buffered: true, - }) - + if (!hasRun && !hasRunLocal.current) { + hasRunLocal.current = true setHasRun() + + if (window.adobeDataLayer) { + const trackingData = { + ...pageData, + pathName, + pageLoadTime: getPageLoadTime(), + } + + const pageObject = createSDKPageObject(trackingData) + console.log("TRACKING: Tracking pageView", pageObject, userData) + window.adobeDataLayer.push({ + event: "pageView", + pageInfo: pageObject, + userInfo: userData, + hotelInfo: hotelInfo, + }) + } } - }, [pathName, pageData, userData, hasRun, setHasRun]) + }, [ + pathName, + pageData, + userData, + hasRun, + setHasRun, + hotelInfo, + getPageLoadTime, + ]) useEffect(() => { // handle consent diff --git a/components/TrackingSDK/RouterTransition.tsx b/components/TrackingSDK/RouterTransition.tsx index cb2597c20..a664e8677 100644 --- a/components/TrackingSDK/RouterTransition.tsx +++ b/components/TrackingSDK/RouterTransition.tsx @@ -4,8 +4,9 @@ import { usePathname } from "next/navigation" import { startTransition, useEffect, useOptimistic, useState } from "react" import useRouterTransitionStore from "@/stores/router-transition" +import useTrackingStore from "@/stores/tracking" -import { createSDKPageObject } from "@/utils/tracking" +import { createSDKPageObject, trackPageViewStart } from "@/utils/tracking" import { TrackingSDKProps } from "@/types/components/tracking" @@ -20,15 +21,28 @@ type TransitionStatus = keyof typeof TransitionStatusEnum export default function RouterTransition({ pageData, userData, + hotelInfo, }: TrackingSDKProps) { const [loading, setLoading] = useOptimistic(false) const [status, setStatus] = useState( TransitionStatusEnum.NotRun ) + const { getPageLoadTime } = useTrackingStore() const pathName = usePathname() const { isTransitioning, stopRouterTransition } = useRouterTransitionStore() + // useEffect(() => { + // const handleStart = () => { + // trackPageViewStart() + // setInitialPageLoadTime(Date.now()) + // } + + // return () => { + // handleStart() + // } + // }, [pathName]) + useEffect(() => { if (isTransitioning && status === TransitionStatusEnum.NotRun) { startTransition(() => { @@ -48,13 +62,21 @@ export default function RouterTransition({ status === TransitionStatusEnum.Done ) { if (window.adobeDataLayer) { - const trackingData = { ...pageData, pathName } + const trackingData = { + ...pageData, + pathName, + pageLoadTime: getPageLoadTime(), + } const pageObject = createSDKPageObject(trackingData) - + console.log( + "TRACKING: Tracking RouterTransition pageViewEnd", + pageObject + ) window.adobeDataLayer.push({ event: "pageViewEnd", pageInfo: pageObject, userInfo: userData, + hotelInfo: hotelInfo, }) } } @@ -67,6 +89,8 @@ export default function RouterTransition({ pageData, pathName, userData, + hotelInfo, + getPageLoadTime, ]) return null diff --git a/components/TrackingSDK/index.tsx b/components/TrackingSDK/index.tsx index d267341b6..f38c2bd0e 100644 --- a/components/TrackingSDK/index.tsx +++ b/components/TrackingSDK/index.tsx @@ -4,7 +4,10 @@ import RouterTransition from "@/components/TrackingSDK/RouterTransition" import TrackingSDKClient from "./Client" -import { TrackingSDKPageData } from "@/types/components/tracking" +import { + TrackingSDKHotelInfo, + TrackingSDKPageData, +} from "@/types/components/tracking" export const preloadUserTracking = () => { void serverClient().user.tracking() @@ -12,15 +15,25 @@ export const preloadUserTracking = () => { export default async function TrackingSDK({ pageData, + hotelInfo, }: { pageData: TrackingSDKPageData + hotelInfo?: TrackingSDKHotelInfo }) { const userTrackingData = await serverClient().user.tracking() return ( <> - - + + ) } diff --git a/lib/graphql/Query/AccountPage/AccountPage.graphql b/lib/graphql/Query/AccountPage/AccountPage.graphql index d18f69bff..f94b5bf61 100644 --- a/lib/graphql/Query/AccountPage/AccountPage.graphql +++ b/lib/graphql/Query/AccountPage/AccountPage.graphql @@ -20,6 +20,9 @@ query GetAccountPage($locale: String!, $uid: String!) { created_at updated_at } + page_settings { + tracking_page_name + } } } diff --git a/lib/graphql/Query/ContentPage/ContentPage.graphql b/lib/graphql/Query/ContentPage/ContentPage.graphql index 150429ee0..ea2d311a6 100644 --- a/lib/graphql/Query/ContentPage/ContentPage.graphql +++ b/lib/graphql/Query/ContentPage/ContentPage.graphql @@ -37,6 +37,9 @@ query GetContentPage($locale: String!, $uid: String!) { created_at updated_at } + page_settings { + tracking_page_name + } } } diff --git a/lib/graphql/Query/HotelPage/HotelPage.graphql b/lib/graphql/Query/HotelPage/HotelPage.graphql index dee41c6a8..b1713133d 100644 --- a/lib/graphql/Query/HotelPage/HotelPage.graphql +++ b/lib/graphql/Query/HotelPage/HotelPage.graphql @@ -31,6 +31,9 @@ query GetHotelPage($locale: String!, $uid: String!) { } } } + page_settings { + tracking_page_name + } } } diff --git a/lib/graphql/Query/LoyaltyPage/LoyaltyPage.graphql b/lib/graphql/Query/LoyaltyPage/LoyaltyPage.graphql index 7a0d8650a..40aed7835 100644 --- a/lib/graphql/Query/LoyaltyPage/LoyaltyPage.graphql +++ b/lib/graphql/Query/LoyaltyPage/LoyaltyPage.graphql @@ -33,6 +33,9 @@ query GetLoyaltyPage($locale: String!, $uid: String!) { created_at updated_at } + page_settings { + tracking_page_name + } } } diff --git a/server/routers/contentstack/accountPage/output.ts b/server/routers/contentstack/accountPage/output.ts index b0703784f..8f5e0e6ba 100644 --- a/server/routers/contentstack/accountPage/output.ts +++ b/server/routers/contentstack/accountPage/output.ts @@ -46,6 +46,9 @@ export const accountPageSchema = z.object({ heading: z.string().nullable(), title: z.string(), url: z.string(), + page_settings: z.object({ + tracking_page_name: z.string().nullable(), + }), system: systemSchema.merge( z.object({ created_at: z.string(), diff --git a/server/routers/contentstack/accountPage/query.ts b/server/routers/contentstack/accountPage/query.ts index 612dd37c2..9100f888f 100644 --- a/server/routers/contentstack/accountPage/query.ts +++ b/server/routers/contentstack/accountPage/query.ts @@ -191,11 +191,16 @@ export const accountPageQueryRouter = router({ const tracking: TrackingSDKPageData = { pageId: validatedAccountPage.data.account_page.system.uid, - lang: validatedAccountPage.data.account_page.system.locale as Lang, + domainLanguage: validatedAccountPage.data.account_page.system + .locale as Lang, publishedDate: validatedAccountPage.data.account_page.system.updated_at, createdDate: validatedAccountPage.data.account_page.system.created_at, channel: TrackingChannelEnum["scandic-friends"], pageType: `member${parsedtitle}page`, + pageName: + validatedAccountPage.data.account_page.page_settings.tracking_page_name, + siteSections: + validatedAccountPage.data.account_page.page_settings.tracking_page_name, // Always the same as pageName for this page } return { diff --git a/server/routers/contentstack/contentPage/output.ts b/server/routers/contentstack/contentPage/output.ts index 2be699b43..bf45d9631 100644 --- a/server/routers/contentstack/contentPage/output.ts +++ b/server/routers/contentstack/contentPage/output.ts @@ -18,6 +18,7 @@ import { shortcutsRefsSchema, shortcutsSchema, } from "../schemas/blocks/shortcuts" +import { textColsRefsSchema, textColsSchema } from "../schemas/blocks/textCols" import { tempImageVaultAssetSchema } from "../schemas/imageVault" import { contentRefsSchema as sidebarContentRefsSchema, @@ -31,7 +32,6 @@ import { import { systemSchema } from "../schemas/system" import { ContentPageEnum } from "@/types/enums/contentPage" -import { textColsRefsSchema, textColsSchema } from "../schemas/blocks/textCols" // Block schemas export const contentPageCards = z @@ -58,9 +58,11 @@ export const contentPageShortcuts = z }) .merge(shortcutsSchema) -export const contentPageTextCols = z.object({ - __typename: z.literal(ContentPageEnum.ContentStack.blocks.TextCols), -}).merge(textColsSchema) +export const contentPageTextCols = z + .object({ + __typename: z.literal(ContentPageEnum.ContentStack.blocks.TextCols), + }) + .merge(textColsSchema) export const blocksSchema = z.discriminatedUnion("__typename", [ contentPageCards, @@ -113,6 +115,9 @@ export const contentPageSchema = z.object({ updated_at: z.string(), }) ), + page_settings: z.object({ + tracking_page_name: z.string().nullable(), + }), }), }) diff --git a/server/routers/contentstack/contentPage/query.ts b/server/routers/contentstack/contentPage/query.ts index 79ef83a4e..5414dbe6b 100644 --- a/server/routers/contentstack/contentPage/query.ts +++ b/server/routers/contentstack/contentPage/query.ts @@ -63,11 +63,14 @@ export const contentPageQueryRouter = router({ const tracking: TrackingSDKPageData = { pageId: contentPage.data.content_page.system.uid, - lang: contentPage.data.content_page.system.locale as Lang, + domainLanguage: contentPage.data.content_page.system.locale as Lang, publishedDate: contentPage.data.content_page.system.updated_at, createdDate: contentPage.data.content_page.system.created_at, channel: TrackingChannelEnum["static-content-page"], pageType: "staticcontentpage", + pageName: contentPage.data.content_page.page_settings.tracking_page_name, + siteSections: + contentPage.data.content_page.page_settings.tracking_page_name, // Always the same as pageName for this page } return { diff --git a/server/routers/contentstack/hotelPage/output.ts b/server/routers/contentstack/hotelPage/output.ts index b07121934..06c029d4e 100644 --- a/server/routers/contentstack/hotelPage/output.ts +++ b/server/routers/contentstack/hotelPage/output.ts @@ -22,5 +22,8 @@ export const hotelPageSchema = z.object({ hotel_page_id: z.string(), title: z.string(), url: z.string(), + page_settings: z.object({ + tracking_page_name: z.string().nullable(), + }), }), }) diff --git a/server/routers/contentstack/loyaltyPage/output.ts b/server/routers/contentstack/loyaltyPage/output.ts index e4ddfe35a..af37613f6 100644 --- a/server/routers/contentstack/loyaltyPage/output.ts +++ b/server/routers/contentstack/loyaltyPage/output.ts @@ -178,6 +178,9 @@ export const loyaltyPageSchema = z.object({ updated_at: z.string(), }) ), + page_settings: z.object({ + tracking_page_name: z.string().nullable(), + }), }) .transform((data) => { return { @@ -187,6 +190,7 @@ export const loyaltyPageSchema = z.object({ preamble: data.preamble, sidebar: data.sidebar ? data.sidebar : [], system: data.system, + pageSettings: data.page_settings, } }), }) diff --git a/server/routers/contentstack/loyaltyPage/query.ts b/server/routers/contentstack/loyaltyPage/query.ts index e6d181d76..2fe467a64 100644 --- a/server/routers/contentstack/loyaltyPage/query.ts +++ b/server/routers/contentstack/loyaltyPage/query.ts @@ -176,11 +176,13 @@ export const loyaltyPageQueryRouter = router({ const loyaltyTrackingData: TrackingSDKPageData = { pageId: loyaltyPage.system.uid, - lang: loyaltyPage.system.locale as Lang, + domainLanguage: loyaltyPage.system.locale as Lang, publishedDate: loyaltyPage.system.updated_at, createdDate: loyaltyPage.system.created_at, channel: TrackingChannelEnum["scandic-friends"], pageType: "loyaltycontentpage", + pageName: loyaltyPage.pageSettings.tracking_page_name, + siteSections: loyaltyPage.pageSettings.tracking_page_name, // Always the same as pageName for this page } getLoyaltyPageSuccessCounter.add(1, metricsVariables) console.info( diff --git a/stores/tracking.ts b/stores/tracking.ts index 97ea29ac0..8e4851e4e 100644 --- a/stores/tracking.ts +++ b/stores/tracking.ts @@ -5,11 +5,20 @@ import { create } from "zustand" interface TrackingStoreState { hasRun: boolean setHasRun: () => void + initialStartTime: number + setInitialPageLoadTime: (time: number) => void + getPageLoadTime: () => number } -const useTrackingStore = create((set) => ({ +const useTrackingStore = create((set, get) => ({ hasRun: false, + initialStartTime: Date.now(), + setInitialPageLoadTime: (time) => set({ initialStartTime: time }), setHasRun: () => set(() => ({ hasRun: true })), + getPageLoadTime: () => { + const { initialStartTime } = get() + return (Date.now() - initialStartTime) / 1000 + }, })) export default useTrackingStore diff --git a/types/components/tracking.ts b/types/components/tracking.ts index 92128d939..7af334623 100644 --- a/types/components/tracking.ts +++ b/types/components/tracking.ts @@ -13,9 +13,14 @@ export type TrackingSDKPageData = { pageId: string createdDate: string publishedDate: string - lang: Lang + domainLanguage: Lang pageType: string channel: TrackingChannel + siteVersion?: "new-web" + pageName: string | null + domain?: string + siteSections: string | null + pageLoadTime?: number // Page load time in seconds } export enum LoginTypeEnum { @@ -35,16 +40,59 @@ export type TrackingSDKUserData = { loginAction?: "login success" } +export type TrackingSDKHotelInfo = { + hotelID?: string + arrivalData?: Date + departureDate?: Date + noAdults?: number + noChildren?: number + ageOfChildren?: string // "10", "2,5,10" + //rewardNight?: boolean + //bookingCode?: string + //bookingCodeAvailability?: boolean + leadTime?: number // Number of days from booking date until arrivalDate + noOfRoom?: number + //bonuscheque?: boolean + childBedPreference?: string + duration?: number // Number of nights to stay + availableResults?: number // Number of hotels to choose from after a city search + bookingTypeofDay?: string // Weekend or weekday + searchTerm?: string + roomPrice?: string + rateCode?: string + rateCodeCancellationRule?: string + rateCodeName?: string // Scandic Friends - full flex inkl. frukost + rateCodeType?: string // regular, promotion etc + revenueCurrencyCode?: string // SEK, DKK, NOK, EUR + roomTypeCode?: string + roomTypePosition?: number // Which position the room had in the list of available rooms + roomTypeName?: string + bedType?: string + bedTypePosition?: number // Which position the bed type had in the list of available bed types + breakfastOption?: string // "no breakfast" or "breakfast buffet" + bnr?: string // Booking number + analyticsrateCode?: string // flex, save, change + specialRoomType?: string // allergy room, pet-friendly, accesibillity room + //modifyValues?: string // ,roomtype:value>,bed: +} + export type TrackingSDKProps = { pageData: TrackingSDKPageData userData: TrackingSDKUserData + hotelInfo?: TrackingSDKHotelInfo } export type TrackingSDKData = TrackingSDKPageData & { pathName: string } +export type TrackingPosition = + | "top menu" + | "hamburger menu" + | "join scandic friends sidebar" + // Old tracking setup types: +// TODO: Remove this when we delete "current site" export type TrackingProps = { pageData: { pageId: string @@ -73,8 +121,3 @@ export type SiteSectionObject = { sitesection5: string sitesection6: string } - -export type TrackingPosition = - | "top menu" - | "hamburger menu" - | "join scandic friends sidebar" diff --git a/utils/tracking.ts b/utils/tracking.ts index bb3b15166..d65c4359c 100644 --- a/utils/tracking.ts +++ b/utils/tracking.ts @@ -2,6 +2,7 @@ import { TrackingPosition, TrackingSDKData } from "@/types/components/tracking" export function trackClick(name: string) { if (typeof window !== "undefined" && window.adobeDataLayer) { + console.log("TRACKING: Tracking click", name) window.adobeDataLayer.push({ event: "linkClick", cta: { @@ -13,6 +14,7 @@ export function trackClick(name: string) { export function trackPageViewStart() { if (typeof window !== "undefined" && window.adobeDataLayer) { + console.log("TRACKING: Tracking pageViewStart") window.adobeDataLayer.push({ event: "pageViewStart", }) @@ -21,6 +23,7 @@ export function trackPageViewStart() { export function trackLoginClick(position: TrackingPosition) { if (typeof window !== "undefined" && window.adobeDataLayer) { + console.log("TRACKING: Tracking loginStart, position", position) const loginEvent = { event: "loginStart", login: { @@ -33,26 +36,20 @@ export function trackLoginClick(position: TrackingPosition) { } } -export function createSDKPageObject(trackingData: TrackingSDKData) { +export function createSDKPageObject( + trackingData: TrackingSDKData +): TrackingSDKData { const [lang, ...segments] = trackingData.pathName .split("/") .filter((seg: string) => seg) const joinedSegments = segments.join("|") - const { host: domain } = window.location - const page_obj = { - pageType: trackingData.pageType, - pageName: joinedSegments, - pageId: trackingData.pageId, - channel: trackingData.channel, - siteSection: joinedSegments, - domain, - siteversion: "new-web", - domainlanguage: trackingData.lang ? trackingData.lang : lang, - createDate: trackingData.createdDate, - publishDate: trackingData.publishedDate, - // sessionid: "", // base on what? + return { + ...trackingData, + domain: window.location.host, + pageName: trackingData.pageName ?? joinedSegments, + siteSections: trackingData.siteSections ?? joinedSegments, + domainLanguage: trackingData.domainLanguage ?? lang, } - return page_obj }