fix: track user on page load

This commit is contained in:
Christel Westerberg
2024-07-15 09:13:20 +02:00
parent edb6005a72
commit c96008fb78
18 changed files with 247 additions and 122 deletions

View File

@@ -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>
) )
} }

View File

@@ -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} />
}

View File

@@ -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} />
)
} }

View File

@@ -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
View File

@@ -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", {

View File

@@ -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>
) )
} }

View File

@@ -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 {

View File

@@ -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>
) )
} }

View File

@@ -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} />

View File

@@ -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>

View File

@@ -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
} }

View File

@@ -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}

View File

@@ -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}
/> />
) )

View File

@@ -17,3 +17,5 @@ export enum MembershipLevelEnum {
L6 = "L6", L6 = "L6",
L7 = "L7", L7 = "L7",
} }
export type MembershipLevel = keyof typeof MembershipLevelEnum

View File

@@ -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
View File

@@ -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
} }
} }

View File

@@ -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
View 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)
}
}