Merged in feat/SW-1472-destination-tracking (pull request #1474)
Feat/SW-1472 destination tracking * feat(SW-1472): Added default tracking for destination overview page * feat(SW-1472): Added default tracking for destination country/city page * feat(SW-1472): moved tracking functions to different files for better overview * feat(SW-1472): added destination page tracking Approved-by: Fredrik Thorsson Approved-by: Matilda Landström
This commit is contained in:
@@ -7,7 +7,6 @@ import { getDestinationCityPage } from "@/lib/trpc/memoizedRequests"
|
|||||||
import Blocks from "@/components/Blocks"
|
import Blocks from "@/components/Blocks"
|
||||||
import Breadcrumbs from "@/components/Breadcrumbs"
|
import Breadcrumbs from "@/components/Breadcrumbs"
|
||||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
import TrackingSDK from "@/components/TrackingSDK"
|
|
||||||
|
|
||||||
import ExperienceList from "../ExperienceList"
|
import ExperienceList from "../ExperienceList"
|
||||||
import HotelDataContainer, { preload } from "../HotelDataContainer"
|
import HotelDataContainer, { preload } from "../HotelDataContainer"
|
||||||
@@ -16,6 +15,7 @@ import SidebarContentWrapper from "../SidebarContentWrapper"
|
|||||||
import DestinationPageSidePeek from "../Sidepeek"
|
import DestinationPageSidePeek from "../Sidepeek"
|
||||||
import StaticMap from "../StaticMap"
|
import StaticMap from "../StaticMap"
|
||||||
import TopImages from "../TopImages"
|
import TopImages from "../TopImages"
|
||||||
|
import DestinationTracking from "../Tracking"
|
||||||
import CityMap from "./CityMap"
|
import CityMap from "./CityMap"
|
||||||
import DestinationCityPageSkeleton from "./DestinationCityPageSkeleton"
|
import DestinationCityPageSkeleton from "./DestinationCityPageSkeleton"
|
||||||
|
|
||||||
@@ -65,6 +65,7 @@ export default async function DestinationCityPage() {
|
|||||||
<DestinationPageSidePeek
|
<DestinationPageSidePeek
|
||||||
buttonText={sidepeek_button_text}
|
buttonText={sidepeek_button_text}
|
||||||
sidePeekContent={sidepeek_content}
|
sidePeekContent={sidepeek_content}
|
||||||
|
location={city.name}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -85,7 +86,7 @@ export default async function DestinationCityPage() {
|
|||||||
/>
|
/>
|
||||||
</HotelDataContainer>
|
</HotelDataContainer>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
<TrackingSDK pageData={tracking} />
|
<DestinationTracking pageData={tracking} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import Blocks from "@/components/Blocks"
|
|||||||
import Breadcrumbs from "@/components/Breadcrumbs"
|
import Breadcrumbs from "@/components/Breadcrumbs"
|
||||||
import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton"
|
import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton"
|
||||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
import TrackingSDK from "@/components/TrackingSDK"
|
|
||||||
|
|
||||||
import CityDataContainer, { preload } from "../CityDataContainer"
|
import CityDataContainer, { preload } from "../CityDataContainer"
|
||||||
import CityListing from "../CityListing"
|
import CityListing from "../CityListing"
|
||||||
@@ -16,6 +15,7 @@ import SidebarContentWrapper from "../SidebarContentWrapper"
|
|||||||
import DestinationPageSidePeek from "../Sidepeek"
|
import DestinationPageSidePeek from "../Sidepeek"
|
||||||
import StaticMap from "../StaticMap"
|
import StaticMap from "../StaticMap"
|
||||||
import TopImages from "../TopImages"
|
import TopImages from "../TopImages"
|
||||||
|
import DestinationTracking from "../Tracking"
|
||||||
import CountryMap from "./CountryMap"
|
import CountryMap from "./CountryMap"
|
||||||
import DestinationCountryPageSkeleton from "./DestinationCountryPageSkeleton"
|
import DestinationCountryPageSkeleton from "./DestinationCountryPageSkeleton"
|
||||||
|
|
||||||
@@ -69,6 +69,7 @@ export default async function DestinationCountryPage() {
|
|||||||
<DestinationPageSidePeek
|
<DestinationPageSidePeek
|
||||||
buttonText={sidepeek_button_text}
|
buttonText={sidepeek_button_text}
|
||||||
sidePeekContent={sidepeek_content}
|
sidePeekContent={sidepeek_content}
|
||||||
|
location={translatedCountry}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<StaticMap
|
<StaticMap
|
||||||
@@ -86,7 +87,7 @@ export default async function DestinationCountryPage() {
|
|||||||
/>
|
/>
|
||||||
</CityDataContainer>
|
</CityDataContainer>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
<TrackingSDK pageData={tracking} />
|
<DestinationTracking pageData={tracking} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import { useCallback } from "react"
|
|||||||
|
|
||||||
import { useDestinationPageHotelsMapStore } from "@/stores/destination-page-hotels-map"
|
import { useDestinationPageHotelsMapStore } from "@/stores/destination-page-hotels-map"
|
||||||
|
|
||||||
|
import { trackMapClick } from "@/utils/tracking/destinationPage"
|
||||||
|
|
||||||
import styles from "./clusterMarker.module.css"
|
import styles from "./clusterMarker.module.css"
|
||||||
|
|
||||||
interface ClusterMarkerProps {
|
interface ClusterMarkerProps {
|
||||||
@@ -34,7 +36,8 @@ export default function ClusterMarker({
|
|||||||
if (onMarkerClick) {
|
if (onMarkerClick) {
|
||||||
onMarkerClick(position)
|
onMarkerClick(position)
|
||||||
}
|
}
|
||||||
}, [onMarkerClick, position])
|
trackMapClick(`cluster with hotelIds: ${hotelIds.join(",")}`)
|
||||||
|
}, [onMarkerClick, position, hotelIds])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AdvancedMarker
|
<AdvancedMarker
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { useCallback } from "react"
|
|||||||
import { useDestinationPageHotelsMapStore } from "@/stores/destination-page-hotels-map"
|
import { useDestinationPageHotelsMapStore } from "@/stores/destination-page-hotels-map"
|
||||||
|
|
||||||
import HotelMarkerByType from "@/components/Maps/Markers"
|
import HotelMarkerByType from "@/components/Maps/Markers"
|
||||||
|
import { trackMapClick } from "@/utils/tracking/destinationPage"
|
||||||
|
|
||||||
import HotelMapCard from "../../../HotelMapCard"
|
import HotelMapCard from "../../../HotelMapCard"
|
||||||
|
|
||||||
@@ -28,6 +29,7 @@ export default function Marker({ position, properties }: MarkerProps) {
|
|||||||
|
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useCallback(() => {
|
||||||
setClickedHotel(properties.id)
|
setClickedHotel(properties.id)
|
||||||
|
trackMapClick(properties.name)
|
||||||
}, [setClickedHotel, properties])
|
}, [setClickedHotel, properties])
|
||||||
|
|
||||||
function handleCloseCard() {
|
function handleCloseCard() {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { ChevronRightSmallIcon } from "@/components/Icons"
|
|||||||
import JsonToHtml from "@/components/JsonToHtml"
|
import JsonToHtml from "@/components/JsonToHtml"
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
import SidePeek from "@/components/TempDesignSystem/SidePeek"
|
import SidePeek from "@/components/TempDesignSystem/SidePeek"
|
||||||
|
import { trackOpenSidePeekOnDestinationPagesEvent } from "@/utils/tracking/destinationPage"
|
||||||
|
|
||||||
import type { DestinationCityPageData } from "@/types/trpc/routers/contentstack/destinationCityPage"
|
import type { DestinationCityPageData } from "@/types/trpc/routers/contentstack/destinationCityPage"
|
||||||
import type { DestinationCountryPageData } from "@/types/trpc/routers/contentstack/destinationCountryPage"
|
import type { DestinationCountryPageData } from "@/types/trpc/routers/contentstack/destinationCountryPage"
|
||||||
@@ -16,18 +17,25 @@ interface DestinationPageSidepeekProps {
|
|||||||
| DestinationCityPageData["sidepeek_content"]
|
| DestinationCityPageData["sidepeek_content"]
|
||||||
| DestinationCountryPageData["sidepeek_content"]
|
| DestinationCountryPageData["sidepeek_content"]
|
||||||
>
|
>
|
||||||
|
location: string
|
||||||
}
|
}
|
||||||
export default function DestinationPageSidepeek({
|
export default function DestinationPageSidepeek({
|
||||||
buttonText,
|
buttonText,
|
||||||
sidePeekContent,
|
sidePeekContent,
|
||||||
|
location,
|
||||||
}: DestinationPageSidepeekProps) {
|
}: DestinationPageSidepeekProps) {
|
||||||
const [sidePeekIsOpen, setSidePeekIsOpen] = useState(false)
|
const [sidePeekIsOpen, setSidePeekIsOpen] = useState(false)
|
||||||
const { heading, content } = sidePeekContent
|
const { heading, content } = sidePeekContent
|
||||||
|
|
||||||
|
function handleButtonClick() {
|
||||||
|
setSidePeekIsOpen(true)
|
||||||
|
trackOpenSidePeekOnDestinationPagesEvent(location)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
onPress={() => setSidePeekIsOpen(true)}
|
onPress={handleButtonClick}
|
||||||
theme="base"
|
theme="base"
|
||||||
variant="icon"
|
variant="icon"
|
||||||
intent="text"
|
intent="text"
|
||||||
@@ -40,6 +48,7 @@ export default function DestinationPageSidepeek({
|
|||||||
<SidePeek
|
<SidePeek
|
||||||
title={heading}
|
title={heading}
|
||||||
isOpen={sidePeekIsOpen}
|
isOpen={sidePeekIsOpen}
|
||||||
|
openInRoot
|
||||||
handleClose={() => setSidePeekIsOpen(false)}
|
handleClose={() => setSidePeekIsOpen(false)}
|
||||||
>
|
>
|
||||||
<JsonToHtml
|
<JsonToHtml
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useSearchParams } from "next/navigation"
|
||||||
|
import { useEffect } from "react"
|
||||||
|
|
||||||
|
import TrackingSDK from "@/components/TrackingSDK"
|
||||||
|
import { trackOpenMapView } from "@/utils/tracking/destinationPage"
|
||||||
|
|
||||||
|
import type { TrackingSDKPageData } from "@/types/components/tracking"
|
||||||
|
|
||||||
|
interface DestinationTrackingProps {
|
||||||
|
pageData: TrackingSDKPageData
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DestinationTracking({
|
||||||
|
pageData,
|
||||||
|
}: DestinationTrackingProps) {
|
||||||
|
const searchParams = useSearchParams()
|
||||||
|
const isMapView = searchParams.get("view") === "map"
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isMapView) {
|
||||||
|
trackOpenMapView(pageData.pageName, pageData.pageType)
|
||||||
|
}
|
||||||
|
}, [isMapView, pageData.pageName, pageData.pageType])
|
||||||
|
|
||||||
|
return <TrackingSDK pageData={pageData} />
|
||||||
|
}
|
||||||
@@ -19,10 +19,6 @@ import {
|
|||||||
import { systemSchema } from "../schemas/system"
|
import { systemSchema } from "../schemas/system"
|
||||||
|
|
||||||
import type { ImageVaultAsset } from "@/types/components/imageVault"
|
import type { ImageVaultAsset } from "@/types/components/imageVault"
|
||||||
import {
|
|
||||||
TrackingChannelEnum,
|
|
||||||
type TrackingSDKPageData,
|
|
||||||
} from "@/types/components/tracking"
|
|
||||||
import { DestinationCityPageEnum } from "@/types/enums/destinationCityPage"
|
import { DestinationCityPageEnum } from "@/types/enums/destinationCityPage"
|
||||||
|
|
||||||
const destinationCityPageDestinationSettingsSchema = z
|
const destinationCityPageDestinationSettingsSchema = z
|
||||||
@@ -119,80 +115,57 @@ export const blocksSchema = z.discriminatedUnion("__typename", [
|
|||||||
destinationCityPageContent,
|
destinationCityPageContent,
|
||||||
])
|
])
|
||||||
|
|
||||||
export const destinationCityPageSchema = z
|
export const destinationCityPageSchema = z.object({
|
||||||
.object({
|
destination_city_page: z.object({
|
||||||
destination_city_page: z.object({
|
title: z.string(),
|
||||||
title: z.string(),
|
destination_settings: destinationCityPageDestinationSettingsSchema,
|
||||||
destination_settings: destinationCityPageDestinationSettingsSchema,
|
heading: z.string(),
|
||||||
|
preamble: z.string(),
|
||||||
|
experiences: z
|
||||||
|
.object({
|
||||||
|
destination_experiences: z.array(z.string()),
|
||||||
|
})
|
||||||
|
.transform(({ destination_experiences }) => destination_experiences),
|
||||||
|
images: z
|
||||||
|
.array(z.object({ image: tempImageVaultAssetSchema }))
|
||||||
|
.transform((images) =>
|
||||||
|
images
|
||||||
|
.map((image) => image.image)
|
||||||
|
.filter((image): image is ImageVaultAsset => !!image)
|
||||||
|
),
|
||||||
|
has_sidepeek: z.boolean().default(false),
|
||||||
|
sidepeek_button_text: z.string().default(""),
|
||||||
|
sidepeek_content: z.object({
|
||||||
heading: z.string(),
|
heading: z.string(),
|
||||||
preamble: z.string(),
|
content: z.object({
|
||||||
experiences: z
|
json: z.any(),
|
||||||
.object({
|
embedded_itemsConnection: z.object({
|
||||||
destination_experiences: z.array(z.string()),
|
edges: z.array(
|
||||||
})
|
z.object({
|
||||||
.transform(({ destination_experiences }) => destination_experiences),
|
node: linkUnionSchema.transform((data) => {
|
||||||
images: z
|
const link = transformPageLink(data)
|
||||||
.array(z.object({ image: tempImageVaultAssetSchema }))
|
if (link) {
|
||||||
.transform((images) =>
|
return link
|
||||||
images
|
}
|
||||||
.map((image) => image.image)
|
return data
|
||||||
.filter((image): image is ImageVaultAsset => !!image)
|
}),
|
||||||
),
|
})
|
||||||
has_sidepeek: z.boolean().default(false),
|
),
|
||||||
sidepeek_button_text: z.string().default(""),
|
|
||||||
sidepeek_content: z.object({
|
|
||||||
heading: z.string(),
|
|
||||||
content: z.object({
|
|
||||||
json: z.any(),
|
|
||||||
embedded_itemsConnection: z.object({
|
|
||||||
edges: z.array(
|
|
||||||
z.object({
|
|
||||||
node: linkUnionSchema.transform((data) => {
|
|
||||||
const link = transformPageLink(data)
|
|
||||||
if (link) {
|
|
||||||
return link
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
blocks: discriminatedUnionArray(blocksSchema.options).nullable(),
|
|
||||||
system: systemSchema.merge(
|
|
||||||
z.object({
|
|
||||||
created_at: z.string(),
|
|
||||||
updated_at: z.string(),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
}),
|
}),
|
||||||
trackingProps: z.object({
|
blocks: discriminatedUnionArray(blocksSchema.options).nullable(),
|
||||||
url: z.string(),
|
system: systemSchema.merge(
|
||||||
}),
|
z.object({
|
||||||
})
|
created_at: z.string(),
|
||||||
.transform((data) => {
|
updated_at: z.string(),
|
||||||
const destinationCityPage = data.destination_city_page
|
})
|
||||||
const system = destinationCityPage.system
|
),
|
||||||
const trackingUrl = data.trackingProps.url
|
}),
|
||||||
|
trackingProps: z.object({
|
||||||
const tracking: TrackingSDKPageData = {
|
url: z.string(),
|
||||||
pageId: system.uid,
|
}),
|
||||||
domainLanguage: system.locale,
|
})
|
||||||
publishDate: system.updated_at,
|
|
||||||
createDate: system.created_at,
|
|
||||||
channel: TrackingChannelEnum["destination-page"],
|
|
||||||
pageType: "staticcontentpage",
|
|
||||||
pageName: trackingUrl,
|
|
||||||
siteSections: trackingUrl,
|
|
||||||
siteVersion: "new-web",
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
destinationCityPage,
|
|
||||||
tracking,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export const cityPageCountSchema = z
|
export const cityPageCountSchema = z
|
||||||
.object({
|
.object({
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ import {
|
|||||||
} from "./telemetry"
|
} from "./telemetry"
|
||||||
import { generatePageTags } from "./utils"
|
import { generatePageTags } from "./utils"
|
||||||
|
|
||||||
|
import {
|
||||||
|
TrackingChannelEnum,
|
||||||
|
type TrackingSDKPageData,
|
||||||
|
} from "@/types/components/tracking"
|
||||||
import type {
|
import type {
|
||||||
GetDestinationCityPageData,
|
GetDestinationCityPageData,
|
||||||
GetDestinationCityPageRefsSchema,
|
GetDestinationCityPageRefsSchema,
|
||||||
@@ -147,8 +151,8 @@ export const destinationCityPageQueryRouter = router({
|
|||||||
)
|
)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const cityIdentifier =
|
const destinationCityPage = validatedResponse.data.destination_city_page
|
||||||
validatedResponse.data.destinationCityPage.destination_settings.city
|
const cityIdentifier = destinationCityPage.destination_settings.city
|
||||||
const city = await getCityByCityIdentifier(cityIdentifier, serviceToken)
|
const city = await getCityByCityIdentifier(cityIdentifier, serviceToken)
|
||||||
|
|
||||||
if (!city) {
|
if (!city) {
|
||||||
@@ -177,10 +181,26 @@ export const destinationCityPageQueryRouter = router({
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const system = destinationCityPage.system
|
||||||
|
const pageName = `hotels|${city.country ? `${city.country}|` : ""}${city.name}`
|
||||||
|
|
||||||
|
const tracking: TrackingSDKPageData = {
|
||||||
|
pageId: system.uid,
|
||||||
|
domainLanguage: system.locale,
|
||||||
|
publishDate: system.updated_at,
|
||||||
|
createDate: system.created_at,
|
||||||
|
channel: TrackingChannelEnum.hotels,
|
||||||
|
pageType: "citypage",
|
||||||
|
pageName,
|
||||||
|
siteSections: pageName,
|
||||||
|
siteVersion: "new-web",
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...validatedResponse.data,
|
destinationCityPage,
|
||||||
cityIdentifier,
|
cityIdentifier,
|
||||||
city,
|
city,
|
||||||
|
tracking,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -19,10 +19,6 @@ import {
|
|||||||
import { systemSchema } from "../schemas/system"
|
import { systemSchema } from "../schemas/system"
|
||||||
|
|
||||||
import type { ImageVaultAsset } from "@/types/components/imageVault"
|
import type { ImageVaultAsset } from "@/types/components/imageVault"
|
||||||
import {
|
|
||||||
TrackingChannelEnum,
|
|
||||||
type TrackingSDKPageData,
|
|
||||||
} from "@/types/components/tracking"
|
|
||||||
import { Country } from "@/types/enums/country"
|
import { Country } from "@/types/enums/country"
|
||||||
import { DestinationCountryPageEnum } from "@/types/enums/destinationCountryPage"
|
import { DestinationCountryPageEnum } from "@/types/enums/destinationCountryPage"
|
||||||
|
|
||||||
@@ -47,82 +43,60 @@ export const blocksSchema = z.discriminatedUnion("__typename", [
|
|||||||
destinationCountryPageContent,
|
destinationCountryPageContent,
|
||||||
])
|
])
|
||||||
|
|
||||||
export const destinationCountryPageSchema = z
|
export const destinationCountryPageSchema = z.object({
|
||||||
.object({
|
destination_country_page: z.object({
|
||||||
destination_country_page: z.object({
|
title: z.string(),
|
||||||
title: z.string(),
|
destination_settings: z.object({
|
||||||
destination_settings: z.object({
|
country: z.nativeEnum(Country),
|
||||||
country: z.nativeEnum(Country),
|
location: mapLocationSchema,
|
||||||
location: mapLocationSchema,
|
}),
|
||||||
}),
|
heading: z.string(),
|
||||||
|
preamble: z.string(),
|
||||||
|
experiences: z
|
||||||
|
.object({
|
||||||
|
destination_experiences: z.array(z.string()),
|
||||||
|
})
|
||||||
|
.transform(({ destination_experiences }) => destination_experiences),
|
||||||
|
images: z
|
||||||
|
.array(z.object({ image: tempImageVaultAssetSchema }))
|
||||||
|
.transform((images) =>
|
||||||
|
images
|
||||||
|
.map((image) => image.image)
|
||||||
|
.filter((image): image is ImageVaultAsset => !!image)
|
||||||
|
),
|
||||||
|
has_sidepeek: z.boolean().default(false),
|
||||||
|
sidepeek_button_text: z.string().default(""),
|
||||||
|
sidepeek_content: z.object({
|
||||||
heading: z.string(),
|
heading: z.string(),
|
||||||
preamble: z.string(),
|
content: z.object({
|
||||||
experiences: z
|
json: z.any(),
|
||||||
.object({
|
embedded_itemsConnection: z.object({
|
||||||
destination_experiences: z.array(z.string()),
|
edges: z.array(
|
||||||
})
|
z.object({
|
||||||
.transform(({ destination_experiences }) => destination_experiences),
|
node: linkUnionSchema.transform((data) => {
|
||||||
images: z
|
const link = transformPageLink(data)
|
||||||
.array(z.object({ image: tempImageVaultAssetSchema }))
|
if (link) {
|
||||||
.transform((images) =>
|
return link
|
||||||
images
|
}
|
||||||
.map((image) => image.image)
|
return data
|
||||||
.filter((image): image is ImageVaultAsset => !!image)
|
}),
|
||||||
),
|
})
|
||||||
has_sidepeek: z.boolean().default(false),
|
),
|
||||||
sidepeek_button_text: z.string().default(""),
|
|
||||||
sidepeek_content: z.object({
|
|
||||||
heading: z.string(),
|
|
||||||
content: z.object({
|
|
||||||
json: z.any(),
|
|
||||||
embedded_itemsConnection: z.object({
|
|
||||||
edges: z.array(
|
|
||||||
z.object({
|
|
||||||
node: linkUnionSchema.transform((data) => {
|
|
||||||
const link = transformPageLink(data)
|
|
||||||
if (link) {
|
|
||||||
return link
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
blocks: discriminatedUnionArray(blocksSchema.options).nullable(),
|
|
||||||
system: systemSchema.merge(
|
|
||||||
z.object({
|
|
||||||
created_at: z.string(),
|
|
||||||
updated_at: z.string(),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
}),
|
}),
|
||||||
trackingProps: z.object({
|
blocks: discriminatedUnionArray(blocksSchema.options).nullable(),
|
||||||
url: z.string(),
|
system: systemSchema.merge(
|
||||||
}),
|
z.object({
|
||||||
})
|
created_at: z.string(),
|
||||||
.transform((data) => {
|
updated_at: z.string(),
|
||||||
const countryPageData = data.destination_country_page
|
})
|
||||||
const system = countryPageData.system
|
),
|
||||||
const trackingUrl = data.trackingProps.url
|
}),
|
||||||
const tracking: TrackingSDKPageData = {
|
trackingProps: z.object({
|
||||||
pageId: system.uid,
|
url: z.string(),
|
||||||
domainLanguage: system.locale,
|
}),
|
||||||
publishDate: system.updated_at,
|
})
|
||||||
createDate: system.created_at,
|
|
||||||
channel: TrackingChannelEnum["destination-page"],
|
|
||||||
pageType: "staticcontentpage",
|
|
||||||
pageName: trackingUrl,
|
|
||||||
siteSections: trackingUrl,
|
|
||||||
siteVersion: "new-web",
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
destinationCountryPage: countryPageData,
|
|
||||||
tracking,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export const countryPageUrlsSchema = z
|
export const countryPageUrlsSchema = z
|
||||||
.object({
|
.object({
|
||||||
|
|||||||
@@ -6,10 +6,9 @@ import { request } from "@/lib/graphql/request"
|
|||||||
import { notFound } from "@/server/errors/trpc"
|
import { notFound } from "@/server/errors/trpc"
|
||||||
import {
|
import {
|
||||||
contentStackBaseWithServiceProcedure,
|
contentStackBaseWithServiceProcedure,
|
||||||
contentStackUidWithServiceProcedure,
|
contentstackExtendedProcedureUID,
|
||||||
router,
|
router,
|
||||||
} from "@/server/trpc"
|
} from "@/server/trpc"
|
||||||
import { toApiLang } from "@/server/utils"
|
|
||||||
|
|
||||||
import { generateTag } from "@/utils/generateTag"
|
import { generateTag } from "@/utils/generateTag"
|
||||||
|
|
||||||
@@ -28,6 +27,10 @@ import {
|
|||||||
} from "./telemetry"
|
} from "./telemetry"
|
||||||
import { generatePageTags, getCityPages } from "./utils"
|
import { generatePageTags, getCityPages } from "./utils"
|
||||||
|
|
||||||
|
import {
|
||||||
|
TrackingChannelEnum,
|
||||||
|
type TrackingSDKPageData,
|
||||||
|
} from "@/types/components/tracking"
|
||||||
import { ApiCountry } from "@/types/enums/country"
|
import { ApiCountry } from "@/types/enums/country"
|
||||||
import type {
|
import type {
|
||||||
GetDestinationCountryPageData,
|
GetDestinationCountryPageData,
|
||||||
@@ -35,9 +38,8 @@ import type {
|
|||||||
} from "@/types/trpc/routers/contentstack/destinationCountryPage"
|
} from "@/types/trpc/routers/contentstack/destinationCountryPage"
|
||||||
|
|
||||||
export const destinationCountryPageQueryRouter = router({
|
export const destinationCountryPageQueryRouter = router({
|
||||||
get: contentStackUidWithServiceProcedure.query(async ({ ctx }) => {
|
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
|
||||||
const { lang, uid, serviceToken } = ctx
|
const { lang, uid } = ctx
|
||||||
const apiLang = toApiLang(lang)
|
|
||||||
|
|
||||||
getDestinationCountryPageRefsCounter.add(1, { lang, uid })
|
getDestinationCountryPageRefsCounter.add(1, { lang, uid })
|
||||||
console.info(
|
console.info(
|
||||||
@@ -156,8 +158,9 @@ export const destinationCountryPageQueryRouter = router({
|
|||||||
)
|
)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const country =
|
const destinationCountryPage =
|
||||||
validatedResponse.data.destinationCountryPage.destination_settings.country
|
validatedResponse.data.destination_country_page
|
||||||
|
const country = destinationCountryPage.destination_settings.country
|
||||||
|
|
||||||
getDestinationCountryPageSuccessCounter.add(1, { lang, uid: `${uid}` })
|
getDestinationCountryPageSuccessCounter.add(1, { lang, uid: `${uid}` })
|
||||||
console.info(
|
console.info(
|
||||||
@@ -167,9 +170,25 @@ export const destinationCountryPageQueryRouter = router({
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const system = destinationCountryPage.system
|
||||||
|
const pageName = `hotels|${country}`
|
||||||
|
|
||||||
|
const tracking: TrackingSDKPageData = {
|
||||||
|
pageId: system.uid,
|
||||||
|
domainLanguage: system.locale,
|
||||||
|
publishDate: system.updated_at,
|
||||||
|
createDate: system.created_at,
|
||||||
|
channel: TrackingChannelEnum.hotels,
|
||||||
|
pageType: "countrypage",
|
||||||
|
pageName,
|
||||||
|
siteSections: pageName,
|
||||||
|
siteVersion: "new-web",
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...validatedResponse.data,
|
destinationCountryPage,
|
||||||
translatedCountry: ApiCountry[lang][country],
|
translatedCountry: ApiCountry[lang][country],
|
||||||
|
tracking,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
cityPages: contentStackBaseWithServiceProcedure
|
cityPages: contentStackBaseWithServiceProcedure
|
||||||
|
|||||||
@@ -189,10 +189,10 @@ export const destinationOverviewPageQueryRouter = router({
|
|||||||
domainLanguage: lang,
|
domainLanguage: lang,
|
||||||
publishDate: system.updated_at,
|
publishDate: system.updated_at,
|
||||||
createDate: system.created_at,
|
createDate: system.created_at,
|
||||||
channel: TrackingChannelEnum["destination-overview-page"],
|
channel: TrackingChannelEnum.hotels,
|
||||||
pageType: "staticcontentpage",
|
pageType: "destinationoverviewpage",
|
||||||
pageName: destinationOverviewPage.data.trackingProps.url,
|
pageName: "destination|overview",
|
||||||
siteSections: destinationOverviewPage.data.trackingProps.url,
|
siteSections: "destination|overview",
|
||||||
siteVersion: "new-web",
|
siteVersion: "new-web",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ import { useContext } from "react"
|
|||||||
import { create, useStore } from "zustand"
|
import { create, useStore } from "zustand"
|
||||||
|
|
||||||
import { DestinationDataContext } from "@/contexts/DestinationData"
|
import { DestinationDataContext } from "@/contexts/DestinationData"
|
||||||
|
import {
|
||||||
|
trackFilterChangeEvent,
|
||||||
|
trackSortingChangeEvent,
|
||||||
|
} from "@/utils/tracking/destinationPage"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getBasePathNameWithoutFilters,
|
getBasePathNameWithoutFilters,
|
||||||
@@ -50,6 +54,30 @@ export function createDestinationDataStore({
|
|||||||
: []
|
: []
|
||||||
const sortedCities = getSortedCities(filteredCities, newSort)
|
const sortedCities = getSortedCities(filteredCities, newSort)
|
||||||
|
|
||||||
|
// Tracking
|
||||||
|
if (newSort !== state.activeSort) {
|
||||||
|
trackSortingChangeEvent(newSort)
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
JSON.stringify(filters) !== JSON.stringify(state.activeFilters)
|
||||||
|
) {
|
||||||
|
const facilityFiltersUsed = filters.filter((f) =>
|
||||||
|
state.allFilters.facilityFilters
|
||||||
|
.map((ff) => ff.slug)
|
||||||
|
.includes(f)
|
||||||
|
)
|
||||||
|
const surroundingsFiltersUsed = filters.filter((f) =>
|
||||||
|
state.allFilters.surroundingsFilters
|
||||||
|
.map((sf) => sf.slug)
|
||||||
|
.includes(f)
|
||||||
|
)
|
||||||
|
|
||||||
|
trackFilterChangeEvent(
|
||||||
|
facilityFiltersUsed,
|
||||||
|
surroundingsFiltersUsed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
state.activeSort = newSort
|
state.activeSort = newSort
|
||||||
state.activeFilters = filters
|
state.activeFilters = filters
|
||||||
state.activeHotels = sortedHotels
|
state.activeHotels = sortedHotels
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ export enum TrackingChannelEnum {
|
|||||||
"static-content-page" = "static-content-page",
|
"static-content-page" = "static-content-page",
|
||||||
"hotelreservation" = "hotelreservation",
|
"hotelreservation" = "hotelreservation",
|
||||||
"collection-page" = "collection-page",
|
"collection-page" = "collection-page",
|
||||||
"destination-overview-page" = "destination-overview-page",
|
|
||||||
"destination-page" = "destination-page",
|
|
||||||
"hotels" = "hotels",
|
"hotels" = "hotels",
|
||||||
"start-page" = "start-page",
|
"start-page" = "start-page",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ export interface GetDestinationCityPageData
|
|||||||
extends z.input<typeof destinationCityPageSchema> {}
|
extends z.input<typeof destinationCityPageSchema> {}
|
||||||
export interface DestinationCityPage
|
export interface DestinationCityPage
|
||||||
extends z.output<typeof destinationCityPageSchema> {}
|
extends z.output<typeof destinationCityPageSchema> {}
|
||||||
export type DestinationCityPageData = DestinationCityPage["destinationCityPage"]
|
export type DestinationCityPageData =
|
||||||
|
DestinationCityPage["destination_city_page"]
|
||||||
|
|
||||||
export interface GetDestinationCityListDataResponse
|
export interface GetDestinationCityListDataResponse
|
||||||
extends z.input<typeof destinationCityListDataSchema> {}
|
extends z.input<typeof destinationCityListDataSchema> {}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export interface GetDestinationCountryPageData
|
|||||||
interface DestinationCountryPage
|
interface DestinationCountryPage
|
||||||
extends z.output<typeof destinationCountryPageSchema> {}
|
extends z.output<typeof destinationCountryPageSchema> {}
|
||||||
export type DestinationCountryPageData =
|
export type DestinationCountryPageData =
|
||||||
DestinationCountryPage["destinationCountryPage"]
|
DestinationCountryPage["destination_country_page"]
|
||||||
export type Block = z.output<typeof blocksSchema>
|
export type Block = z.output<typeof blocksSchema>
|
||||||
|
|
||||||
export interface GetDestinationCountryPageRefsSchema
|
export interface GetDestinationCountryPageRefsSchema
|
||||||
|
|||||||
@@ -1,198 +0,0 @@
|
|||||||
import type {
|
|
||||||
LowestRoomPriceEvent,
|
|
||||||
PaymentEvent,
|
|
||||||
PaymentFailEvent,
|
|
||||||
TrackingPosition,
|
|
||||||
TrackingSDKData,
|
|
||||||
} from "@/types/components/tracking"
|
|
||||||
|
|
||||||
export function trackClick(
|
|
||||||
name: string,
|
|
||||||
additionalParams?: Record<string, string>
|
|
||||||
) {
|
|
||||||
trackEvent({
|
|
||||||
event: "linkClick",
|
|
||||||
cta: {
|
|
||||||
...additionalParams,
|
|
||||||
name,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function trackPageViewStart() {
|
|
||||||
trackEvent({
|
|
||||||
event: "pageViewStart",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function trackLoginClick(position: TrackingPosition) {
|
|
||||||
const event = {
|
|
||||||
event: "loginStart",
|
|
||||||
login: {
|
|
||||||
position,
|
|
||||||
action: "login start",
|
|
||||||
ctaName: "login",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
trackEvent(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function trackSocialMediaClick(socialMediaName: string) {
|
|
||||||
const event = {
|
|
||||||
event: "social media",
|
|
||||||
social: {
|
|
||||||
socialIconClicked: socialMediaName,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
trackEvent(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function trackFooterClick(group: string, name: string) {
|
|
||||||
const event = {
|
|
||||||
event: "footer link",
|
|
||||||
footer: {
|
|
||||||
footerLinkClicked: `${group}:${name}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
trackEvent(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function trackHotelMapClick() {
|
|
||||||
const event = {
|
|
||||||
event: "map click",
|
|
||||||
map: {
|
|
||||||
action: "map click - open/explore mearby",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
trackEvent(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function trackAccordionClick(option: string) {
|
|
||||||
const event = {
|
|
||||||
event: "accordionClick",
|
|
||||||
accordion: {
|
|
||||||
action: "accordion open click",
|
|
||||||
option,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
trackEvent(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function trackHotelTabClick(name: string) {
|
|
||||||
trackEvent({
|
|
||||||
event: "linkClick",
|
|
||||||
link: {
|
|
||||||
action: "hotel menu click",
|
|
||||||
option: `hotel menu:${name}`,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function trackUpdatePaymentMethod(hotelId: string, method: string) {
|
|
||||||
const paymentSelectionEvent = {
|
|
||||||
event: "paymentSelection",
|
|
||||||
hotelInfo: {
|
|
||||||
hotelId: hotelId,
|
|
||||||
},
|
|
||||||
cta: {
|
|
||||||
name: method,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
trackEvent(paymentSelectionEvent)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function trackOpenSidePeekEvent(
|
|
||||||
name: string | null,
|
|
||||||
hotelId: string,
|
|
||||||
pathName?: string,
|
|
||||||
roomTypeCode?: string | null
|
|
||||||
) {
|
|
||||||
const openSidePeekEvent = {
|
|
||||||
event: "openSidePeek",
|
|
||||||
hotelInfo: {
|
|
||||||
hotelId: hotelId,
|
|
||||||
},
|
|
||||||
cta: {
|
|
||||||
name,
|
|
||||||
...(roomTypeCode ? { roomTypeCode } : {}),
|
|
||||||
...(pathName ? { pathName } : {}),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
trackEvent(openSidePeekEvent)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function trackPaymentEvent(paymentEvent: PaymentEvent) {
|
|
||||||
const paymentAttempt = {
|
|
||||||
event: paymentEvent.event,
|
|
||||||
hotelInfo: {
|
|
||||||
hotelId: paymentEvent.hotelId,
|
|
||||||
},
|
|
||||||
paymentInfo: {
|
|
||||||
isSavedCard: paymentEvent.isSavedCreditCard,
|
|
||||||
status: paymentEvent.status,
|
|
||||||
type: paymentEvent.method,
|
|
||||||
smsEnable: paymentEvent.smsEnable,
|
|
||||||
errorMessage: isPaymentFailEvent(paymentEvent)
|
|
||||||
? paymentEvent.errorMessage
|
|
||||||
: undefined,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
trackEvent(paymentAttempt)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function trackLowestRoomPrice(event: LowestRoomPriceEvent) {
|
|
||||||
const lowestRoomPrice = {
|
|
||||||
event: "lowestRoomPrice",
|
|
||||||
hotelInfo: {
|
|
||||||
hotelId: event.hotelId,
|
|
||||||
arrivalDate: event.arrivalDate,
|
|
||||||
departureDate: event.departureDate,
|
|
||||||
},
|
|
||||||
viewItemInfo: {
|
|
||||||
lowestPrice: event.lowestPrice,
|
|
||||||
currency: event.currency,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
trackEvent(lowestRoomPrice)
|
|
||||||
}
|
|
||||||
|
|
||||||
function trackEvent(data: any) {
|
|
||||||
if (typeof window !== "undefined" && window.adobeDataLayer) {
|
|
||||||
data = { ...data, siteVersion: "new-web" }
|
|
||||||
window.adobeDataLayer.push(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function trackPageView(data: any) {
|
|
||||||
if (typeof window !== "undefined" && window.adobeDataLayer) {
|
|
||||||
window.adobeDataLayer.push(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createSDKPageObject(
|
|
||||||
trackingData: TrackingSDKData
|
|
||||||
): TrackingSDKData {
|
|
||||||
let pageName = convertSlashToPipe(trackingData.pageName)
|
|
||||||
let siteSections = convertSlashToPipe(trackingData.siteSections)
|
|
||||||
|
|
||||||
if (trackingData.pathName.indexOf("/webview/") > -1) {
|
|
||||||
pageName = "webview|" + pageName
|
|
||||||
siteSections = "webview|" + siteSections
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...trackingData,
|
|
||||||
domain: typeof window !== "undefined" ? window.location.host : "",
|
|
||||||
pageName: pageName,
|
|
||||||
siteSections: siteSections,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertSlashToPipe(url: string) {
|
|
||||||
const formattedUrl = url.startsWith("/") ? url.slice(1) : url
|
|
||||||
return formattedUrl.replaceAll("/", "|")
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPaymentFailEvent(event: PaymentEvent): event is PaymentFailEvent {
|
|
||||||
return "errorMessage" in event
|
|
||||||
}
|
|
||||||
19
apps/scandic-web/utils/tracking/base.ts
Normal file
19
apps/scandic-web/utils/tracking/base.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
export function trackEvent(data: any) {
|
||||||
|
if (typeof window !== "undefined" && window.adobeDataLayer) {
|
||||||
|
data = { ...data, siteVersion: "new-web" }
|
||||||
|
window.adobeDataLayer.push(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function trackClick(
|
||||||
|
name: string,
|
||||||
|
additionalParams?: Record<string, string>
|
||||||
|
) {
|
||||||
|
trackEvent({
|
||||||
|
event: "linkClick",
|
||||||
|
cta: {
|
||||||
|
...additionalParams,
|
||||||
|
name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
18
apps/scandic-web/utils/tracking/booking.ts
Normal file
18
apps/scandic-web/utils/tracking/booking.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { trackEvent } from "./base"
|
||||||
|
|
||||||
|
import type { LowestRoomPriceEvent } from "@/types/components/tracking"
|
||||||
|
|
||||||
|
export function trackLowestRoomPrice(event: LowestRoomPriceEvent) {
|
||||||
|
trackEvent({
|
||||||
|
event: "lowestRoomPrice",
|
||||||
|
hotelInfo: {
|
||||||
|
hotelId: event.hotelId,
|
||||||
|
arrivalDate: event.arrivalDate,
|
||||||
|
departureDate: event.departureDate,
|
||||||
|
},
|
||||||
|
viewItemInfo: {
|
||||||
|
lowestPrice: event.lowestPrice,
|
||||||
|
currency: event.currency,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
30
apps/scandic-web/utils/tracking/componentEvents.ts
Normal file
30
apps/scandic-web/utils/tracking/componentEvents.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { trackEvent } from "./base"
|
||||||
|
|
||||||
|
export function trackAccordionClick(option: string) {
|
||||||
|
trackEvent({
|
||||||
|
event: "accordionClick",
|
||||||
|
accordion: {
|
||||||
|
action: "accordion open click",
|
||||||
|
option,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function trackOpenSidePeekEvent(
|
||||||
|
name: string | null,
|
||||||
|
hotelId: string,
|
||||||
|
pathName?: string,
|
||||||
|
roomTypeCode?: string | null
|
||||||
|
) {
|
||||||
|
trackEvent({
|
||||||
|
event: "openSidePeek",
|
||||||
|
hotelInfo: {
|
||||||
|
hotelId: hotelId,
|
||||||
|
},
|
||||||
|
cta: {
|
||||||
|
name,
|
||||||
|
...(roomTypeCode ? { roomTypeCode } : {}),
|
||||||
|
...(pathName ? { pathName } : {}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
59
apps/scandic-web/utils/tracking/destinationPage.ts
Normal file
59
apps/scandic-web/utils/tracking/destinationPage.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { trackEvent } from "./base"
|
||||||
|
|
||||||
|
export function trackSortingChangeEvent(sortOption: string) {
|
||||||
|
trackEvent({
|
||||||
|
event: "sortOptionClick",
|
||||||
|
filter: {
|
||||||
|
sortOptions: sortOption,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function trackFilterChangeEvent(
|
||||||
|
facilityFilters: string[],
|
||||||
|
surroundingsFilters: string[]
|
||||||
|
) {
|
||||||
|
const filtersUsed = []
|
||||||
|
if (facilityFilters.length) {
|
||||||
|
filtersUsed.push(`hotelfacilities:${facilityFilters.join(",")}`)
|
||||||
|
}
|
||||||
|
if (surroundingsFilters.length) {
|
||||||
|
filtersUsed.push(`hotelsurroundings:${surroundingsFilters.join(",")}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
trackEvent({
|
||||||
|
event: "filterUsed",
|
||||||
|
filter: {
|
||||||
|
filtersUsed: filtersUsed.join("|"),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function trackOpenSidePeekOnDestinationPagesEvent(location: string) {
|
||||||
|
trackEvent({
|
||||||
|
event: "trackOpenSidePeekEvent",
|
||||||
|
cta: {
|
||||||
|
pageName: `explore${location}|sidepeek`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function trackMapClick(name: string) {
|
||||||
|
trackEvent({
|
||||||
|
event: "map click",
|
||||||
|
map: {
|
||||||
|
action: "map click",
|
||||||
|
clickedItemName: name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function trackOpenMapView(pageName: string, pageType: string) {
|
||||||
|
trackEvent({
|
||||||
|
event: "openMapView",
|
||||||
|
map: {
|
||||||
|
pageName,
|
||||||
|
pageType,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
20
apps/scandic-web/utils/tracking/hotelPage.ts
Normal file
20
apps/scandic-web/utils/tracking/hotelPage.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { trackEvent } from "./base"
|
||||||
|
|
||||||
|
export function trackHotelMapClick() {
|
||||||
|
trackEvent({
|
||||||
|
event: "map click",
|
||||||
|
map: {
|
||||||
|
action: "map click - open/explore mearby",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function trackHotelTabClick(name: string) {
|
||||||
|
trackEvent({
|
||||||
|
event: "linkClick",
|
||||||
|
link: {
|
||||||
|
action: "hotel menu click",
|
||||||
|
option: `hotel menu:${name}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
15
apps/scandic-web/utils/tracking/index.ts
Normal file
15
apps/scandic-web/utils/tracking/index.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
export { trackClick } from "./base"
|
||||||
|
export { trackLowestRoomPrice } from "./booking"
|
||||||
|
export { trackAccordionClick, trackOpenSidePeekEvent } from "./componentEvents"
|
||||||
|
export { trackHotelMapClick, trackHotelTabClick } from "./hotelPage"
|
||||||
|
export {
|
||||||
|
trackFooterClick,
|
||||||
|
trackLoginClick,
|
||||||
|
trackSocialMediaClick,
|
||||||
|
} from "./navigation"
|
||||||
|
export {
|
||||||
|
createSDKPageObject,
|
||||||
|
trackPageView,
|
||||||
|
trackPageViewStart,
|
||||||
|
} from "./pageview"
|
||||||
|
export { trackPaymentEvent, trackUpdatePaymentMethod } from "./payment"
|
||||||
32
apps/scandic-web/utils/tracking/navigation.ts
Normal file
32
apps/scandic-web/utils/tracking/navigation.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { trackEvent } from "./base"
|
||||||
|
|
||||||
|
import type { TrackingPosition } from "@/types/components/tracking"
|
||||||
|
|
||||||
|
export function trackFooterClick(group: string, name: string) {
|
||||||
|
trackEvent({
|
||||||
|
event: "footer link",
|
||||||
|
footer: {
|
||||||
|
footerLinkClicked: `${group}:${name}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function trackSocialMediaClick(socialMediaName: string) {
|
||||||
|
trackEvent({
|
||||||
|
event: "social media",
|
||||||
|
social: {
|
||||||
|
socialIconClicked: socialMediaName,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function trackLoginClick(position: TrackingPosition) {
|
||||||
|
trackEvent({
|
||||||
|
event: "loginStart",
|
||||||
|
login: {
|
||||||
|
position,
|
||||||
|
action: "login start",
|
||||||
|
ctaName: "login",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
39
apps/scandic-web/utils/tracking/pageview.ts
Normal file
39
apps/scandic-web/utils/tracking/pageview.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { trackEvent } from "./base"
|
||||||
|
|
||||||
|
import type { TrackingSDKData } from "@/types/components/tracking"
|
||||||
|
|
||||||
|
function convertSlashToPipe(url: string) {
|
||||||
|
const formattedUrl = url.startsWith("/") ? url.slice(1) : url
|
||||||
|
return formattedUrl.replaceAll("/", "|")
|
||||||
|
}
|
||||||
|
|
||||||
|
export function trackPageViewStart() {
|
||||||
|
trackEvent({
|
||||||
|
event: "pageViewStart",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function trackPageView(data: any) {
|
||||||
|
if (typeof window !== "undefined" && window.adobeDataLayer) {
|
||||||
|
window.adobeDataLayer.push(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createSDKPageObject(
|
||||||
|
trackingData: TrackingSDKData
|
||||||
|
): TrackingSDKData {
|
||||||
|
let pageName = convertSlashToPipe(trackingData.pageName)
|
||||||
|
let siteSections = convertSlashToPipe(trackingData.siteSections)
|
||||||
|
|
||||||
|
if (trackingData.pathName.indexOf("/webview/") > -1) {
|
||||||
|
pageName = "webview|" + pageName
|
||||||
|
siteSections = "webview|" + siteSections
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...trackingData,
|
||||||
|
domain: typeof window !== "undefined" ? window.location.host : "",
|
||||||
|
pageName: pageName,
|
||||||
|
siteSections: siteSections,
|
||||||
|
}
|
||||||
|
}
|
||||||
38
apps/scandic-web/utils/tracking/payment.ts
Normal file
38
apps/scandic-web/utils/tracking/payment.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { trackEvent } from "./base"
|
||||||
|
|
||||||
|
import type { PaymentEvent, PaymentFailEvent } from "@/types/components/tracking"
|
||||||
|
|
||||||
|
function isPaymentFailEvent(event: PaymentEvent): event is PaymentFailEvent {
|
||||||
|
return "errorMessage" in event
|
||||||
|
}
|
||||||
|
|
||||||
|
export function trackUpdatePaymentMethod(hotelId: string, method: string) {
|
||||||
|
trackEvent({
|
||||||
|
event: "paymentSelection",
|
||||||
|
hotelInfo: {
|
||||||
|
hotelId: hotelId,
|
||||||
|
},
|
||||||
|
cta: {
|
||||||
|
name: method,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function trackPaymentEvent(paymentEvent: PaymentEvent) {
|
||||||
|
const paymentAttempt = {
|
||||||
|
event: paymentEvent.event,
|
||||||
|
hotelInfo: {
|
||||||
|
hotelId: paymentEvent.hotelId,
|
||||||
|
},
|
||||||
|
paymentInfo: {
|
||||||
|
isSavedCard: paymentEvent.isSavedCreditCard,
|
||||||
|
status: paymentEvent.status,
|
||||||
|
type: paymentEvent.method,
|
||||||
|
smsEnable: paymentEvent.smsEnable,
|
||||||
|
errorMessage: isPaymentFailEvent(paymentEvent)
|
||||||
|
? paymentEvent.errorMessage
|
||||||
|
: undefined,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
trackEvent(paymentAttempt)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user