fix: track user on page load
This commit is contained in:
@@ -22,6 +22,8 @@ export default async function MyPages({
|
|||||||
const accountPageTracking =
|
const accountPageTracking =
|
||||||
await serverClient().contentstack.accountPage.tracking()
|
await serverClient().contentstack.accountPage.tracking()
|
||||||
|
|
||||||
|
const userTrackingData = await serverClient().user.tracking()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className={styles.blocks}>
|
<main className={styles.blocks}>
|
||||||
<Title>{accountPage.heading}</Title>
|
<Title>{accountPage.heading}</Title>
|
||||||
@@ -30,7 +32,7 @@ export default async function MyPages({
|
|||||||
) : (
|
) : (
|
||||||
<p>{formatMessage({ id: "No content published" })}</p>
|
<p>{formatMessage({ id: "No content published" })}</p>
|
||||||
)}
|
)}
|
||||||
<TrackingSDK pageData={accountPageTracking} />
|
<TrackingSDK pageData={accountPageTracking} userData={userTrackingData} />
|
||||||
</main>
|
</main>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,3 @@
|
|||||||
import "../profileLayout.css"
|
import ProfilePage from "../page"
|
||||||
|
|
||||||
import { serverClient } from "@/lib/trpc/server"
|
export default ProfilePage
|
||||||
|
|
||||||
import TrackingSDK from "@/components/Current/TrackingSDK"
|
|
||||||
|
|
||||||
export default async function EditProfilePage() {
|
|
||||||
const accountPageTracking =
|
|
||||||
await serverClient().contentstack.accountPage.tracking()
|
|
||||||
|
|
||||||
return <TrackingSDK pageData={accountPageTracking} />
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import TrackingSDK from "@/components/Current/TrackingSDK"
|
|||||||
export default async function ProfilePage() {
|
export default async function ProfilePage() {
|
||||||
const accountPageTracking =
|
const accountPageTracking =
|
||||||
await serverClient().contentstack.accountPage.tracking()
|
await serverClient().contentstack.accountPage.tracking()
|
||||||
|
const userTrackingData = await serverClient().user.tracking()
|
||||||
|
|
||||||
return <TrackingSDK pageData={accountPageTracking} />
|
return (
|
||||||
|
<TrackingSDK pageData={accountPageTracking} userData={userTrackingData} />
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import "@/app/globals.css"
|
import "@/app/globals.css"
|
||||||
import "@scandic-hotels/design-system/style.css"
|
import "@scandic-hotels/design-system/style.css"
|
||||||
|
|
||||||
|
import Script from "next/script"
|
||||||
|
|
||||||
import TrpcProvider from "@/lib/trpc/Provider"
|
import TrpcProvider from "@/lib/trpc/Provider"
|
||||||
|
|
||||||
|
import AdobeSDKScript from "@/components/Current/AdobeSDKScript"
|
||||||
import { getIntl } from "@/i18n"
|
import { getIntl } from "@/i18n"
|
||||||
import ServerIntlProvider from "@/i18n/Provider"
|
import ServerIntlProvider from "@/i18n/Provider"
|
||||||
|
|
||||||
@@ -23,6 +26,12 @@ export default async function RootLayout({
|
|||||||
const { defaultLocale, locale, messages } = await getIntl()
|
const { defaultLocale, locale, messages } = await getIntl()
|
||||||
return (
|
return (
|
||||||
<html lang={params.lang}>
|
<html lang={params.lang}>
|
||||||
|
<head>
|
||||||
|
<AdobeSDKScript />
|
||||||
|
<Script id="ensure-adobeDataLayer">{`
|
||||||
|
window.adobeDataLayer = window.adobeDataLayer || []
|
||||||
|
`}</Script>
|
||||||
|
</head>
|
||||||
<body className={styles.layout}>
|
<body className={styles.layout}>
|
||||||
<ServerIntlProvider intl={{ defaultLocale, locale, messages }}>
|
<ServerIntlProvider intl={{ defaultLocale, locale, messages }}>
|
||||||
<TrpcProvider lang={params.lang}>{children}</TrpcProvider>
|
<TrpcProvider lang={params.lang}>{children}</TrpcProvider>
|
||||||
|
|||||||
21
auth.ts
21
auth.ts
@@ -2,9 +2,23 @@ import NextAuth from "next-auth"
|
|||||||
|
|
||||||
import { env } from "@/env/server"
|
import { env } from "@/env/server"
|
||||||
|
|
||||||
|
import { LoginTypeEnum } from "./types/components/tracking"
|
||||||
|
|
||||||
import type { NextAuthConfig, User } from "next-auth"
|
import type { NextAuthConfig, User } from "next-auth"
|
||||||
import type { OIDCConfig } from "next-auth/providers"
|
import type { OIDCConfig } from "next-auth/providers"
|
||||||
|
|
||||||
|
function getLoginType(user: User) {
|
||||||
|
if (user?.nonce) {
|
||||||
|
return LoginTypeEnum.MagicLink
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user?.login_with.includes("@")) {
|
||||||
|
return LoginTypeEnum.Email
|
||||||
|
} else {
|
||||||
|
return LoginTypeEnum.MembershipNumber
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const customProvider = {
|
const customProvider = {
|
||||||
clientId: env.CURITY_CLIENT_ID_USER,
|
clientId: env.CURITY_CLIENT_ID_USER,
|
||||||
clientSecret: env.CURITY_CLIENT_SECRET_USER,
|
clientSecret: env.CURITY_CLIENT_SECRET_USER,
|
||||||
@@ -39,6 +53,8 @@ const customProvider = {
|
|||||||
id: profile.id,
|
id: profile.id,
|
||||||
sub: profile.sub,
|
sub: profile.sub,
|
||||||
given_name: profile.given_name,
|
given_name: profile.given_name,
|
||||||
|
login_with: profile.login_with,
|
||||||
|
nonce: profile.nonce,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
} satisfies OIDCConfig<User>
|
} satisfies OIDCConfig<User>
|
||||||
@@ -96,7 +112,8 @@ export const config = {
|
|||||||
async authorized({ auth, request }) {
|
async authorized({ auth, request }) {
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
async jwt({ account, session, token, trigger }) {
|
async jwt({ account, session, token, trigger, user }) {
|
||||||
|
const loginType = getLoginType(user)
|
||||||
if (account) {
|
if (account) {
|
||||||
return {
|
return {
|
||||||
access_token: account.access_token,
|
access_token: account.access_token,
|
||||||
@@ -104,6 +121,7 @@ export const config = {
|
|||||||
? account.expires_at * 1000
|
? account.expires_at * 1000
|
||||||
: undefined,
|
: undefined,
|
||||||
refresh_token: account.refresh_token,
|
refresh_token: account.refresh_token,
|
||||||
|
loginType,
|
||||||
}
|
}
|
||||||
} else if (Date.now() < token.expires_at) {
|
} else if (Date.now() < token.expires_at) {
|
||||||
return token
|
return token
|
||||||
@@ -158,6 +176,7 @@ export const config = {
|
|||||||
access_token: new_tokens.access_token,
|
access_token: new_tokens.access_token,
|
||||||
expires_at: new_tokens.expires_at,
|
expires_at: new_tokens.expires_at,
|
||||||
refresh_token: new_tokens.refresh_token ?? token.refresh_token,
|
refresh_token: new_tokens.refresh_token ?? token.refresh_token,
|
||||||
|
loginType,
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("token-debug Error thrown when trying to refresh", {
|
console.log("token-debug Error thrown when trying to refresh", {
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ export default async function LoyaltyPage({ lang }: LangParams) {
|
|||||||
const loyaltyPageTracking =
|
const loyaltyPageTracking =
|
||||||
await serverClient().contentstack.loyaltyPage.tracking()
|
await serverClient().contentstack.loyaltyPage.tracking()
|
||||||
|
|
||||||
|
const userTracking = await serverClient().user.tracking()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.content}>
|
<section className={styles.content}>
|
||||||
{loyaltyPage.sidebar.length ? (
|
{loyaltyPage.sidebar.length ? (
|
||||||
@@ -31,7 +33,7 @@ export default async function LoyaltyPage({ lang }: LangParams) {
|
|||||||
<Blocks blocks={loyaltyPage.blocks} lang={lang} />
|
<Blocks blocks={loyaltyPage.blocks} lang={lang} />
|
||||||
) : null}
|
) : null}
|
||||||
</MaxWidth>
|
</MaxWidth>
|
||||||
<TrackingSDK pageData={loyaltyPageTracking} />
|
<TrackingSDK pageData={loyaltyPageTracking} userData={userTracking} />
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
|
|
||||||
.content:has(> aside) .blocks {
|
.content:has(> aside) .blocks {
|
||||||
grid-column: 2 / -1;
|
grid-column: 2 / -1;
|
||||||
|
height: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blocks {
|
.blocks {
|
||||||
|
|||||||
@@ -1,30 +1,53 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { usePathname } from "next/navigation"
|
import { usePathname } from "next/navigation"
|
||||||
import { useIntl } from "react-intl"
|
import { PropsWithChildren, useEffect } from "react"
|
||||||
|
|
||||||
import { login } from "@/constants/routes/handleAuth"
|
import { login } from "@/constants/routes/handleAuth"
|
||||||
|
|
||||||
import Link from "@/components/TempDesignSystem/Link"
|
import Link from "@/components/TempDesignSystem/Link"
|
||||||
|
import { LinkProps } from "@/components/TempDesignSystem/Link/link"
|
||||||
|
import { trackLoginClick } from "@/utils/tracking"
|
||||||
|
|
||||||
import type { TrackableLoginId } from "@/types/components/tracking"
|
import { TrackingPosition } from "@/types/components/tracking"
|
||||||
import { LangParams } from "@/types/params"
|
import { LangParams } from "@/types/params"
|
||||||
|
|
||||||
export default function LoginButton({
|
export default function LoginButton({
|
||||||
className,
|
className,
|
||||||
|
position,
|
||||||
trackingId,
|
trackingId,
|
||||||
lang,
|
lang,
|
||||||
}: LangParams & { className: string; trackingId: TrackableLoginId }) {
|
children,
|
||||||
const { formatMessage } = useIntl()
|
color = "black",
|
||||||
|
}: PropsWithChildren<
|
||||||
|
LangParams & {
|
||||||
|
className: string
|
||||||
|
trackingId: string
|
||||||
|
position: TrackingPosition
|
||||||
|
color?: LinkProps["color"]
|
||||||
|
}
|
||||||
|
>) {
|
||||||
const pathName = usePathname()
|
const pathName = usePathname()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document
|
||||||
|
.getElementById(trackingId)
|
||||||
|
?.addEventListener("click", () => trackLoginClick(position))
|
||||||
|
return () => {
|
||||||
|
document
|
||||||
|
.getElementById(trackingId)
|
||||||
|
?.removeEventListener("click", () => trackLoginClick(position))
|
||||||
|
}
|
||||||
|
}, [position, trackingId])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
href={`${login[lang]}?redirectTo=${encodeURIComponent(`/${lang}${pathName}`)}`}
|
|
||||||
className={className}
|
className={className}
|
||||||
id={trackingId}
|
id={trackingId}
|
||||||
|
color={color}
|
||||||
|
href={`${login[lang]}?redirectTo=${encodeURIComponent(`/${lang}${pathName}`)}`}
|
||||||
>
|
>
|
||||||
{formatMessage({ id: "Log in" })}
|
{children}
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import useDropdownStore from "@/stores/main-menu"
|
|||||||
import Image from "@/components/Image"
|
import Image from "@/components/Image"
|
||||||
import Avatar from "@/components/MyPages/Avatar"
|
import Avatar from "@/components/MyPages/Avatar"
|
||||||
import Link from "@/components/TempDesignSystem/Link"
|
import Link from "@/components/TempDesignSystem/Link"
|
||||||
|
import { trackClick } from "@/utils/tracking"
|
||||||
|
|
||||||
import BookingButton from "../BookingButton"
|
import BookingButton from "../BookingButton"
|
||||||
import LoginButton from "../LoginButton"
|
import LoginButton from "../LoginButton"
|
||||||
@@ -37,6 +38,11 @@ export function MainMenu({
|
|||||||
toggleMyPagesMobileMenu,
|
toggleMyPagesMobileMenu,
|
||||||
} = useDropdownStore()
|
} = useDropdownStore()
|
||||||
|
|
||||||
|
function handleMyPagesMobileMenuClick() {
|
||||||
|
trackClick("profile picture icon")
|
||||||
|
toggleMyPagesMobileMenu()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.mainMenu}>
|
<div className={styles.mainMenu}>
|
||||||
<div
|
<div
|
||||||
@@ -98,10 +104,13 @@ export function MainMenu({
|
|||||||
</li>
|
</li>
|
||||||
<li className={styles.mobileLinkRow}>
|
<li className={styles.mobileLinkRow}>
|
||||||
<LoginButton
|
<LoginButton
|
||||||
trackingId="LoginStartHamburgerMenu"
|
position="hamburger menu"
|
||||||
|
trackingId="loginStartHamburgerMenu"
|
||||||
className={styles.mobileLinkButton}
|
className={styles.mobileLinkButton}
|
||||||
lang={lang}
|
lang={lang}
|
||||||
/>
|
>
|
||||||
|
{intl.formatMessage({ id: "Log in" })}
|
||||||
|
</LoginButton>
|
||||||
</li>
|
</li>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -118,9 +127,17 @@ export function MainMenu({
|
|||||||
<ul className={styles.mainLinks}>
|
<ul className={styles.mainLinks}>
|
||||||
{links.map((link, i) => (
|
{links.map((link, i) => (
|
||||||
<li className={styles.li} key={link.href + i}>
|
<li className={styles.li} key={link.href + i}>
|
||||||
<a className={styles.link} href={link.href}>
|
<Link
|
||||||
|
className={styles.link}
|
||||||
|
href={link.href}
|
||||||
|
trackingId={
|
||||||
|
isHamburgerMenuOpen
|
||||||
|
? `hamburger - ${link.title}`
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
{link.title}
|
{link.title}
|
||||||
</a>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -128,9 +145,17 @@ export function MainMenu({
|
|||||||
<ul className={styles.mobileList}>
|
<ul className={styles.mobileList}>
|
||||||
{topMenuMobileLinks.map(({ link }, i) => (
|
{topMenuMobileLinks.map(({ link }, i) => (
|
||||||
<li className={styles.mobileLi} key={link.href + i}>
|
<li className={styles.mobileLi} key={link.href + i}>
|
||||||
<a className={styles.mobileLink} href={link.href}>
|
<Link
|
||||||
|
className={styles.mobileLink}
|
||||||
|
href={link.href}
|
||||||
|
trackingId={
|
||||||
|
isHamburgerMenuOpen
|
||||||
|
? `hamburger - ${link.title}`
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
{link.title}
|
{link.title}
|
||||||
</a>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -159,7 +184,7 @@ export function MainMenu({
|
|||||||
{myPagesMobileDropdown && user ? (
|
{myPagesMobileDropdown && user ? (
|
||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
onClick={() => toggleMyPagesMobileMenu()}
|
onClick={handleMyPagesMobileMenuClick}
|
||||||
className={styles.avatarButton}
|
className={styles.avatarButton}
|
||||||
>
|
>
|
||||||
<Avatar firstName={user.firstName} lastName={user.lastName} />
|
<Avatar firstName={user.firstName} lastName={user.lastName} />
|
||||||
|
|||||||
@@ -65,10 +65,13 @@ export default async function TopMenu({
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<LoginButton
|
<LoginButton
|
||||||
trackingId="LoginStartTopMenu"
|
position="hamburger menu"
|
||||||
lang={lang}
|
trackingId="loginStartTopMeny"
|
||||||
className={`${styles.sessionLink} ${styles.loginLink}`}
|
className={`${styles.sessionLink} ${styles.loginLink}`}
|
||||||
/>
|
lang={lang}
|
||||||
|
>
|
||||||
|
{formatMessage({ id: "Log in" })}
|
||||||
|
</LoginButton>
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import { useEffect } from "react"
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
SiteSectionObject,
|
SiteSectionObject,
|
||||||
TrackableClickIdEnum,
|
|
||||||
TrackingPosition,
|
|
||||||
TrackingSDKData,
|
TrackingSDKData,
|
||||||
TrackingSDKProps,
|
TrackingSDKProps,
|
||||||
} from "@/types/components/tracking"
|
} from "@/types/components/tracking"
|
||||||
@@ -63,7 +61,7 @@ function createSDKPageObject(trackingData: TrackingSDKData) {
|
|||||||
return page_obj
|
return page_obj
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TrackingSDK({ pageData }: TrackingSDKProps) {
|
export default function TrackingSDK({ pageData, userData }: TrackingSDKProps) {
|
||||||
const pathName = usePathname()
|
const pathName = usePathname()
|
||||||
|
|
||||||
function CookiebotCallbackOnAccept() {
|
function CookiebotCallbackOnAccept() {
|
||||||
@@ -91,9 +89,9 @@ export default function TrackingSDK({ pageData }: TrackingSDKProps) {
|
|||||||
if (window.adobeDataLayer) {
|
if (window.adobeDataLayer) {
|
||||||
const trackingData = { ...pageData, pathName }
|
const trackingData = { ...pageData, pathName }
|
||||||
const pageObject = createSDKPageObject(trackingData)
|
const pageObject = createSDKPageObject(trackingData)
|
||||||
window.adobeDataLayer.push(pageObject)
|
window.adobeDataLayer.push({ ...pageObject, userInfo: userData })
|
||||||
}
|
}
|
||||||
}, [pathName, pageData])
|
}, [pathName, pageData, userData])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// handle consent
|
// handle consent
|
||||||
@@ -109,66 +107,5 @@ export default function TrackingSDK({ pageData }: TrackingSDKProps) {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
function loginClick(position: TrackingPosition) {
|
|
||||||
if (window.adobeDataLayer) {
|
|
||||||
const loginEvent = {
|
|
||||||
event: "loginStart",
|
|
||||||
login: {
|
|
||||||
position,
|
|
||||||
action: "login start",
|
|
||||||
ctaName: "login",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
window.adobeDataLayer.push(loginEvent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function linkClick(name: string) {
|
|
||||||
if (window.adobeDataLayer) {
|
|
||||||
window.adobeDataLayer.push({
|
|
||||||
event: "linkClick",
|
|
||||||
cta: {
|
|
||||||
name: name,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Handle clickable events
|
|
||||||
document
|
|
||||||
.getElementById(TrackableClickIdEnum.LoginStartTopMenu)
|
|
||||||
?.addEventListener("click", () => loginClick("top menu"))
|
|
||||||
document
|
|
||||||
.getElementById(TrackableClickIdEnum.LoginStartJoinScandicFriends)
|
|
||||||
?.addEventListener("click", () =>
|
|
||||||
loginClick("join scandic friends sidebar")
|
|
||||||
)
|
|
||||||
document
|
|
||||||
.getElementById(TrackableClickIdEnum.LoginStartHamburgerMenu)
|
|
||||||
?.addEventListener("click", () => loginClick("hamburger menu"))
|
|
||||||
|
|
||||||
document
|
|
||||||
.getElementById(TrackableClickIdEnum.ProfilePictureLink)
|
|
||||||
?.addEventListener("click", () => linkClick("profile picture link"))
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document
|
|
||||||
.getElementById(TrackableClickIdEnum.LoginStartTopMenu)
|
|
||||||
?.removeEventListener("click", () => loginClick("top menu"))
|
|
||||||
document
|
|
||||||
.getElementById(TrackableClickIdEnum.LoginStartJoinScandicFriends)
|
|
||||||
?.removeEventListener("click", () =>
|
|
||||||
loginClick("join scandic friends sidebar")
|
|
||||||
)
|
|
||||||
document
|
|
||||||
.getElementById(TrackableClickIdEnum.LoginStartHamburgerMenu)
|
|
||||||
?.removeEventListener("click", () => loginClick("hamburger menu"))
|
|
||||||
document
|
|
||||||
.getElementById(TrackableClickIdEnum.ProfilePictureLink)
|
|
||||||
?.removeEventListener("click", () => linkClick("profile picture link"))
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { serverClient } from "@/lib/trpc/server"
|
import { serverClient } from "@/lib/trpc/server"
|
||||||
|
|
||||||
|
import LoginButton from "@/components/Current/Header/LoginButton"
|
||||||
import ArrowRight from "@/components/Icons/ArrowRight"
|
import ArrowRight from "@/components/Icons/ArrowRight"
|
||||||
import { ScandicFriends } from "@/components/Levels"
|
import { ScandicFriends } from "@/components/Levels"
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
@@ -13,7 +14,6 @@ import Contact from "./Contact"
|
|||||||
import styles from "./joinLoyalty.module.css"
|
import styles from "./joinLoyalty.module.css"
|
||||||
|
|
||||||
import type { JoinLoyaltyContactProps } from "@/types/components/loyalty/sidebar"
|
import type { JoinLoyaltyContactProps } from "@/types/components/loyalty/sidebar"
|
||||||
import { TrackableClickIdEnum } from "@/types/components/tracking"
|
|
||||||
import { LangParams } from "@/types/params"
|
import { LangParams } from "@/types/params"
|
||||||
|
|
||||||
export default async function JoinLoyaltyContact({
|
export default async function JoinLoyaltyContact({
|
||||||
@@ -53,13 +53,12 @@ export default async function JoinLoyaltyContact({
|
|||||||
) : null}
|
) : null}
|
||||||
<section className={styles.loginContainer}>
|
<section className={styles.loginContainer}>
|
||||||
<Body>{formatMessage({ id: "Already a friend?" })}</Body>
|
<Body>{formatMessage({ id: "Already a friend?" })}</Body>
|
||||||
<Link
|
<LoginButton
|
||||||
className={styles.link}
|
className={styles.link}
|
||||||
|
lang={lang}
|
||||||
|
trackingId="loginJoinLoyalty"
|
||||||
|
position="join scandic friends sidebar"
|
||||||
color="burgundy"
|
color="burgundy"
|
||||||
href={`/${lang}/login`}
|
|
||||||
variant="icon"
|
|
||||||
size="small"
|
|
||||||
id={TrackableClickIdEnum.LoginStartJoinScandicFriends}
|
|
||||||
>
|
>
|
||||||
<ArrowRight
|
<ArrowRight
|
||||||
color="burgundy"
|
color="burgundy"
|
||||||
@@ -68,7 +67,7 @@ export default async function JoinLoyaltyContact({
|
|||||||
width="20"
|
width="20"
|
||||||
/>
|
/>
|
||||||
{formatMessage({ id: "Log in here" })}
|
{formatMessage({ id: "Log in here" })}
|
||||||
</Link>
|
</LoginButton>
|
||||||
</section>
|
</section>
|
||||||
</article>
|
</article>
|
||||||
{block.contact ? <Contact contactBlock={block.contact} /> : null}
|
{block.contact ? <Contact contactBlock={block.contact} /> : null}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import NextLink from "next/link"
|
|||||||
import { usePathname } from "next/navigation"
|
import { usePathname } from "next/navigation"
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
|
|
||||||
|
import { trackClick } from "@/utils/tracking"
|
||||||
|
|
||||||
import { linkVariants } from "./variants"
|
import { linkVariants } from "./variants"
|
||||||
|
|
||||||
import type { LinkProps } from "./link"
|
import type { LinkProps } from "./link"
|
||||||
@@ -36,11 +38,13 @@ export default function Link({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (trackingId) {
|
if (trackingId) {
|
||||||
document.getElementById(trackingId)?.addEventListener("click", () => {})
|
document
|
||||||
|
.getElementById(trackingId)
|
||||||
|
?.addEventListener("click", () => trackClick(trackingId))
|
||||||
return () => {
|
return () => {
|
||||||
document
|
document
|
||||||
.getElementById(trackingId)
|
.getElementById(trackingId)
|
||||||
?.removeEventListener("click", () => {})
|
?.removeEventListener("click", () => trackClick(trackingId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [trackingId])
|
}, [trackingId])
|
||||||
@@ -51,6 +55,7 @@ export default function Link({
|
|||||||
prefetch={prefetch}
|
prefetch={prefetch}
|
||||||
className={classNames}
|
className={classNames}
|
||||||
href={href}
|
href={href}
|
||||||
|
id={trackingId}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -17,3 +17,5 @@ export enum MembershipLevelEnum {
|
|||||||
L6 = "L6",
|
L6 = "L6",
|
||||||
L7 = "L7",
|
L7 = "L7",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MembershipLevel = keyof typeof MembershipLevelEnum
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ import { benefits, extendedUser, nextLevelPerks } from "./temp"
|
|||||||
|
|
||||||
import type { Session } from "next-auth"
|
import type { Session } from "next-auth"
|
||||||
|
|
||||||
|
import type {
|
||||||
|
LoginType,
|
||||||
|
TrackingSDKUserData,
|
||||||
|
} from "@/types/components/tracking"
|
||||||
|
|
||||||
async function getVerifiedUser({ session }: { session: Session }) {
|
async function getVerifiedUser({ session }: { session: Session }) {
|
||||||
const apiResponse = await api.get(api.endpoints.v1.profile, {
|
const apiResponse = await api.get(api.endpoints.v1.profile, {
|
||||||
cache: "no-store",
|
cache: "no-store",
|
||||||
@@ -215,6 +220,73 @@ export const userQueryRouter = router({
|
|||||||
const membershipLevel = getMembership(verifiedData.data.memberships)
|
const membershipLevel = getMembership(verifiedData.data.memberships)
|
||||||
return membershipLevel
|
return membershipLevel
|
||||||
}),
|
}),
|
||||||
|
tracking: safeProtectedProcedure.query(async function ({ ctx }) {
|
||||||
|
const notLoggedInUserTrackingData: TrackingSDKUserData = {
|
||||||
|
loginStatus: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctx.session) {
|
||||||
|
return notLoggedInUserTrackingData
|
||||||
|
}
|
||||||
|
const verifiedUserData = await getVerifiedUser({ session: ctx.session })
|
||||||
|
|
||||||
|
if (!verifiedUserData) {
|
||||||
|
return notLoggedInUserTrackingData
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
params.set("limit", "1")
|
||||||
|
|
||||||
|
const previousStaysResponse = await api.get(
|
||||||
|
api.endpoints.v1.previousStays,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${ctx.session.token.access_token}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
params
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!previousStaysResponse.ok) {
|
||||||
|
// switch (apiResponse.status) {
|
||||||
|
// case 400:
|
||||||
|
// throw badRequestError(apiResponse)
|
||||||
|
// case 401:
|
||||||
|
// throw unauthorizedError(apiResponse)
|
||||||
|
// case 403:
|
||||||
|
// throw forbiddenError(apiResponse)
|
||||||
|
// default:
|
||||||
|
// throw internalServerError(apiResponse)
|
||||||
|
// }
|
||||||
|
console.info(`API Response Failed - Getting Previous Stays`)
|
||||||
|
console.info(`User: (${JSON.stringify(ctx.session.user)})`)
|
||||||
|
console.error(previousStaysResponse)
|
||||||
|
return notLoggedInUserTrackingData
|
||||||
|
}
|
||||||
|
|
||||||
|
const previousStaysApiJson = await previousStaysResponse.json()
|
||||||
|
const verifiedPreviousStaysData =
|
||||||
|
getStaysSchema.safeParse(previousStaysApiJson)
|
||||||
|
if (!verifiedPreviousStaysData.success) {
|
||||||
|
console.info(`Failed to validate Previous Stays Data`)
|
||||||
|
console.info(`User: (${JSON.stringify(ctx.session.user)})`)
|
||||||
|
console.error(verifiedPreviousStaysData.error)
|
||||||
|
return notLoggedInUserTrackingData
|
||||||
|
}
|
||||||
|
|
||||||
|
const membership = getMembership(verifiedUserData.data.memberships)
|
||||||
|
|
||||||
|
const loggedInUserTrackingData: TrackingSDKUserData = {
|
||||||
|
loginStatus: true,
|
||||||
|
loginType: ctx.session.token.loginType as LoginType,
|
||||||
|
memberId: membership?.membershipNumber,
|
||||||
|
memberLevel: membership?.membershipLevel,
|
||||||
|
noOfNightsStayed: verifiedPreviousStaysData.data.links?.totalCount ?? 0,
|
||||||
|
totalPointsAvailableToSpend: membership?.currentPoints,
|
||||||
|
}
|
||||||
|
|
||||||
|
return loggedInUserTrackingData
|
||||||
|
}),
|
||||||
benefits: router({
|
benefits: router({
|
||||||
current: protectedProcedure.query(async function (opts) {
|
current: protectedProcedure.query(async function (opts) {
|
||||||
// TODO: Make request to get user data from Scandic API
|
// TODO: Make request to get user data from Scandic API
|
||||||
|
|||||||
3
types/auth.d.ts
vendored
3
types/auth.d.ts
vendored
@@ -25,5 +25,8 @@ declare module "next-auth" {
|
|||||||
interface User {
|
interface User {
|
||||||
given_name: string
|
given_name: string
|
||||||
sub: string
|
sub: string
|
||||||
|
email?: string
|
||||||
|
login_with: string
|
||||||
|
nonce?: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { MembershipLevel } from "@/constants/membershipLevels"
|
||||||
|
|
||||||
import type { Lang } from "@/constants/languages"
|
import type { Lang } from "@/constants/languages"
|
||||||
|
|
||||||
export enum TrackingChannelEnum {
|
export enum TrackingChannelEnum {
|
||||||
@@ -14,8 +16,25 @@ export type TrackingSDKPageData = {
|
|||||||
channel: TrackingChannel
|
channel: TrackingChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum LoginTypeEnum {
|
||||||
|
Email = "email",
|
||||||
|
MembershipNumber = "membership number",
|
||||||
|
MagicLink = "magic link",
|
||||||
|
}
|
||||||
|
export type LoginType = keyof typeof LoginTypeEnum
|
||||||
|
|
||||||
|
export type TrackingSDKUserData = {
|
||||||
|
loginStatus: boolean
|
||||||
|
loginType?: LoginType
|
||||||
|
memberId?: string
|
||||||
|
memberLevel?: MembershipLevel
|
||||||
|
noOfNightsStayed?: number
|
||||||
|
totalPointsAvailableToSpend?: number
|
||||||
|
}
|
||||||
|
|
||||||
export type TrackingSDKProps = {
|
export type TrackingSDKProps = {
|
||||||
pageData: TrackingSDKPageData
|
pageData: TrackingSDKPageData
|
||||||
|
userData: TrackingSDKUserData
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TrackingSDKData = {
|
export type TrackingSDKData = {
|
||||||
@@ -56,22 +75,6 @@ export type SiteSectionObject = {
|
|||||||
sitesection6: string
|
sitesection6: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TrackableClickIdEnum {
|
|
||||||
LoginStartTopMenu = "LoginStartTopMenu",
|
|
||||||
LoginStartHamburgerMenu = "LoginStartHamburgerMenu",
|
|
||||||
LoginStartJoinScandicFriends = "LoginStartJoinScandicFriends",
|
|
||||||
LoginFail = "LoginFail",
|
|
||||||
HamburgerLink = "HamburgerLink",
|
|
||||||
ProfilePictureLink = "ProfilePictureLink",
|
|
||||||
}
|
|
||||||
|
|
||||||
type TrackableClickId = keyof typeof TrackableClickIdEnum
|
|
||||||
|
|
||||||
export type TrackableLoginId = Exclude<
|
|
||||||
TrackableClickId,
|
|
||||||
"HamburgerLink" | "ProfilePictureLink" | "LoginFail"
|
|
||||||
>
|
|
||||||
|
|
||||||
export type TrackingPosition =
|
export type TrackingPosition =
|
||||||
| "top menu"
|
| "top menu"
|
||||||
| "hamburger menu"
|
| "hamburger menu"
|
||||||
|
|||||||
26
utils/tracking.ts
Normal file
26
utils/tracking.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { TrackingPosition } from "@/types/components/tracking"
|
||||||
|
|
||||||
|
export function trackClick(name: string) {
|
||||||
|
if (window.adobeDataLayer) {
|
||||||
|
window.adobeDataLayer.push({
|
||||||
|
event: "linkClick",
|
||||||
|
cta: {
|
||||||
|
name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function trackLoginClick(position: TrackingPosition) {
|
||||||
|
if (window.adobeDataLayer) {
|
||||||
|
const loginEvent = {
|
||||||
|
event: "loginStart",
|
||||||
|
login: {
|
||||||
|
position,
|
||||||
|
action: "login start",
|
||||||
|
ctaName: "login",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
window.adobeDataLayer.push(loginEvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user