Merged in feat/trackingsdk-client (pull request #1420)

feat: trackingsdk as client component

* feat: trackingsdk as client component

* Cleanup

* Merge changes from feat/trackingsdk-client

* revert yarn.lock

* Added lcpTime and wait with tracking until we have the values


Approved-by: Joakim Jäderberg
This commit is contained in:
Linus Flood
2025-02-27 07:22:58 +00:00
parent d995dcf0aa
commit 0c498d82ca
23 changed files with 90 additions and 103 deletions

View File

@@ -42,9 +42,7 @@ export default async function MyPages({}: PageArgs<
<p>{intl.formatMessage({ id: "No content published" })}</p> <p>{intl.formatMessage({ id: "No content published" })}</p>
)} )}
</main> </main>
<Suspense fallback={null}> <TrackingSDK pageData={tracking} />
<TrackingSDK pageData={tracking} />
</Suspense>
</> </>
) )
} }

View File

@@ -19,9 +19,7 @@ export default async function ProfilePage({}: PageArgs<LangParams>) {
return ( return (
<> <>
<Profile /> <Profile />
<Suspense fallback={null}> <TrackingSDK pageData={accountPage.tracking} />
<TrackingSDK pageData={accountPage.tracking} />
</Suspense>
</> </>
) )
} }

View File

@@ -30,9 +30,7 @@ export default function HotelReservationPage({ params }: PageArgs<LangParams>) {
return ( return (
<div className={styles.page}> <div className={styles.page}>
New booking flow! Please report errors/issues in Slack. New booking flow! Please report errors/issues in Slack.
<Suspense fallback={null}> <TrackingSDK pageData={pageTrackingData} />
<TrackingSDK pageData={pageTrackingData} />
</Suspense>
</div> </div>
) )
} }

View File

@@ -14,7 +14,6 @@ import Header from "@/components/Header"
import StorageCleaner from "@/components/HotelReservation/EnterDetails/StorageCleaner" import StorageCleaner from "@/components/HotelReservation/EnterDetails/StorageCleaner"
import SitewideAlert from "@/components/SitewideAlert" import SitewideAlert from "@/components/SitewideAlert"
import { ToastHandler } from "@/components/TempDesignSystem/Toasts" import { ToastHandler } from "@/components/TempDesignSystem/Toasts"
import { preloadUserTracking } from "@/components/TrackingSDK"
import AdobeSDKScript from "@/components/TrackingSDK/AdobeSDKScript" import AdobeSDKScript from "@/components/TrackingSDK/AdobeSDKScript"
import GTMScript from "@/components/TrackingSDK/GTMScript" import GTMScript from "@/components/TrackingSDK/GTMScript"
import RouterTracking from "@/components/TrackingSDK/RouterTracking" import RouterTracking from "@/components/TrackingSDK/RouterTracking"
@@ -31,7 +30,6 @@ export default async function RootLayout({
bookingwidget: React.ReactNode bookingwidget: React.ReactNode
} }
>) { >) {
preloadUserTracking()
const { defaultLocale, locale, messages } = await getIntl() const { defaultLocale, locale, messages } = await getIntl()
return ( return (

View File

@@ -11,7 +11,6 @@ import TokenRefresher from "@/components/Auth/TokenRefresher"
import CookieBotConsent from "@/components/CookieBot" import CookieBotConsent from "@/components/CookieBot"
import StorageCleaner from "@/components/HotelReservation/EnterDetails/StorageCleaner" import StorageCleaner from "@/components/HotelReservation/EnterDetails/StorageCleaner"
import { ToastHandler } from "@/components/TempDesignSystem/Toasts" import { ToastHandler } from "@/components/TempDesignSystem/Toasts"
import { preloadUserTracking } from "@/components/TrackingSDK"
import AdobeSDKScript from "@/components/TrackingSDK/AdobeSDKScript" import AdobeSDKScript from "@/components/TrackingSDK/AdobeSDKScript"
import GTMScript from "@/components/TrackingSDK/GTMScript" import GTMScript from "@/components/TrackingSDK/GTMScript"
import RouterTracking from "@/components/TrackingSDK/RouterTracking" import RouterTracking from "@/components/TrackingSDK/RouterTracking"
@@ -28,7 +27,6 @@ export default async function RootLayout({
return null return null
} }
preloadUserTracking()
const { defaultLocale, locale, messages } = await getIntl() const { defaultLocale, locale, messages } = await getIntl()
return ( return (

View File

@@ -90,9 +90,7 @@ export default async function DestinationCityPage({
/> />
</HotelDataContainer> </HotelDataContainer>
</Suspense> </Suspense>
<Suspense fallback={null}> <TrackingSDK pageData={tracking} />
<TrackingSDK pageData={tracking} />
</Suspense>
</> </>
) )
} }

View File

@@ -76,9 +76,7 @@ export default async function DestinationCountryPage() {
</aside> </aside>
</div> </div>
<CountryMap country={destination_settings.country} /> <CountryMap country={destination_settings.country} />
<Suspense fallback={null}> <TrackingSDK pageData={tracking} />
<TrackingSDK pageData={tracking} />
</Suspense>
</> </>
) )
} }

View File

@@ -40,9 +40,7 @@ export default async function DestinationOverviewPage() {
<HotelsSection destinations={destinationsData} /> <HotelsSection destinations={destinationsData} />
</aside> </aside>
)} )}
<Suspense fallback={null}> <TrackingSDK pageData={tracking} />
<TrackingSDK pageData={tracking} />
</Suspense>
</> </>
) )
} }

View File

@@ -50,9 +50,7 @@ export default async function LoyaltyPage() {
) : null} ) : null}
</MaxWidth> </MaxWidth>
</section> </section>
<Suspense fallback={null}> <TrackingSDK pageData={tracking} />
<TrackingSDK pageData={tracking} />
</Suspense>
</> </>
) )
} }

View File

@@ -59,9 +59,7 @@ export default async function StartPage() {
) )
})} })}
</main> </main>
<Suspense fallback={null}> <TrackingSDK pageData={content.tracking} />
<TrackingSDK pageData={content.tracking} />
</Suspense>
</div> </div>
) )
} }

View File

@@ -78,9 +78,7 @@ export default function StaticPage({
) : null} ) : null}
</div> </div>
</section> </section>
<Suspense fallback={null}> <TrackingSDK pageData={tracking} />
<TrackingSDK pageData={tracking} />
</Suspense>
</> </>
) )
} }

View File

@@ -112,7 +112,7 @@ export default function ReceiptRoom({
</div> </div>
<div className={styles.entry}> <div className={styles.entry}>
<Body>{intl.formatMessage({ id: "Breakfast buffet" })}</Body> <Body>{intl.formatMessage({ id: "Breakfast buffet" })}</Body>
{booking.rateDefinition.breakfastIncluded ?? breakfastPkgIncluded ? ( {(booking.rateDefinition.breakfastIncluded ?? breakfastPkgIncluded) ? (
<Body color="red">{intl.formatMessage({ id: "Included" })}</Body> <Body color="red">{intl.formatMessage({ id: "Included" })}</Body>
) : null} ) : null}
{breakfastPkgSelected ? ( {breakfastPkgSelected ? (

View File

@@ -53,9 +53,9 @@
var(--Spacing-x2); var(--Spacing-x2);
} }
.img { .img {
min-width: 306px; min-width: 306px;
} }
.roomDetails { .roomDetails {
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
} }

View File

@@ -159,13 +159,11 @@ export default async function BookingConfirmation({
</SidePanel> </SidePanel>
</aside> </aside>
</Confirmation> </Confirmation>
<Suspense fallback={null}> <TrackingSDK
<TrackingSDK pageData={initialPageTrackingData}
pageData={initialPageTrackingData} hotelInfo={initialHotelsTrackingData}
hotelInfo={initialHotelsTrackingData} paymentInfo={paymentInfo}
paymentInfo={paymentInfo} />
/>
</Suspense>
</> </>
) )
} }

View File

@@ -155,12 +155,7 @@ export async function SelectHotelMapContainer({
cityCoordinates={cityCoordinates} cityCoordinates={cityCoordinates}
bookingCode={bookingCode ?? ""} bookingCode={bookingCode ?? ""}
/> />
<Suspense fallback={null}> <TrackingSDK pageData={pageTrackingData} hotelInfo={hotelsTrackingData} />
<TrackingSDK
pageData={pageTrackingData}
hotelInfo={hotelsTrackingData}
/>
</Suspense>
</> </>
) )
} }

View File

@@ -49,44 +49,65 @@ export default function RouterTransition({
useEffect(() => { useEffect(() => {
if (!hasRun && !hasRunInitial.current) { if (!hasRun && !hasRunInitial.current) {
const handleLoad = () => {
const perfObserver = new PerformanceObserver((observedEntries) => {
const entry = observedEntries.getEntriesByType("navigation")[0]
if (entry) {
const trackingData = {
...pageData,
sessionId,
pathName,
pageLoadTime: entry.duration / 1000,
}
const pageObject = createSDKPageObject(trackingData)
trackPageView({
event: "pageView",
pageInfo: pageObject,
userInfo: userData,
hotelInfo: hotelInfo,
paymentInfo,
})
perfObserver.disconnect()
}
})
perfObserver.observe({
type: "navigation",
buffered: true,
})
}
// If the page is already loaded, execute immediately
if (document.readyState === "complete") {
handleLoad()
} else {
// Otherwise wait for the load event
window.addEventListener("load", handleLoad)
return () => window.removeEventListener("load", handleLoad)
}
hasRunInitial.current = true hasRunInitial.current = true
setHasRun() setHasRun()
const getPageLoadTime = () => {
return new Promise<number>((resolve) => {
const observer = new PerformanceObserver((entries) => {
const navEntry = entries.getEntriesByType("navigation")[0]
if (navEntry) {
observer.disconnect()
resolve(navEntry.duration / 1000)
}
})
observer.observe({ type: "navigation", buffered: true })
})
}
const getLCPTime = () => {
return new Promise<number>((resolve) => {
const observer = new PerformanceObserver((entries) => {
const lastEntry = entries.getEntries().pop()
if (lastEntry) {
observer.disconnect()
resolve(lastEntry.startTime / 1000)
}
})
observer.observe({ type: "largest-contentful-paint", buffered: true })
})
}
const trackPerformance = async () => {
const [pageLoadTime, lcpTime] = await Promise.all([
getPageLoadTime(),
getLCPTime(),
])
const trackingData = {
...pageData,
sessionId,
pathName,
pageLoadTime,
lcpTime,
}
const pageObject = createSDKPageObject(trackingData)
trackPageView({
event: "pageView",
pageInfo: pageObject,
userInfo: userData,
hotelInfo,
paymentInfo,
})
}
if (document.readyState === "complete") {
trackPerformance()
} else {
window.addEventListener("load", trackPerformance)
return () => window.removeEventListener("load", trackPerformance)
}
} }
}, [ }, [
pathName, pathName,

View File

@@ -1,6 +1,7 @@
import { getUserTracking } from "@/lib/trpc/memoizedRequests" "use client"
import RouterTransition from "@/components/TrackingSDK/RouterTransition" import RouterTransition from "@/components/TrackingSDK/RouterTransition"
import { trpc } from "@/lib/trpc/client"
import type { import type {
TrackingSDKHotelInfo, TrackingSDKHotelInfo,
@@ -8,11 +9,7 @@ import type {
TrackingSDKPaymentInfo, TrackingSDKPaymentInfo,
} from "@/types/components/tracking" } from "@/types/components/tracking"
export const preloadUserTracking = () => { export default function TrackingSDK({
void getUserTracking()
}
export default async function TrackingSDK({
pageData, pageData,
hotelInfo, hotelInfo,
paymentInfo, paymentInfo,
@@ -21,7 +18,12 @@ export default async function TrackingSDK({
hotelInfo?: TrackingSDKHotelInfo hotelInfo?: TrackingSDKHotelInfo
paymentInfo?: TrackingSDKPaymentInfo paymentInfo?: TrackingSDKPaymentInfo
}) { }) {
const userTrackingData = await getUserTracking() const { data: userTrackingData, isPending } =
trpc.user.userTrackingInfo.useQuery()
if (isPending || !userTrackingData) {
return null
}
return ( return (
<RouterTransition <RouterTransition

View File

@@ -33,9 +33,7 @@ export default async function AccountPage() {
<Blocks content={accountPage.content} /> <Blocks content={accountPage.content} />
</MaxWidth> </MaxWidth>
<Suspense fallback={null}> <TrackingSDK pageData={tracking} />
<TrackingSDK pageData={tracking} />
</Suspense>
</> </>
) )
} }

View File

@@ -29,9 +29,7 @@ export default async function AboutScandicFriends() {
</MaxWidth> </MaxWidth>
</section> </section>
<Suspense fallback={null}> <TrackingSDK pageData={tracking} />
<TrackingSDK pageData={tracking} />
</Suspense>
</> </>
) )
} }

View File

@@ -64,10 +64,6 @@ export const getMembershipCards = cache(
} }
) )
export const getUserTracking = cache(async function getMemoizedUserTracking() {
return serverClient().user.tracking()
})
export const getHotelsByCSFilter = cache(async function getMemoizedHotels( export const getHotelsByCSFilter = cache(async function getMemoizedHotels(
input: GetHotelsByCSFilterInput input: GetHotelsByCSFilterInput
) { ) {

View File

@@ -357,7 +357,7 @@ 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 }) { userTrackingInfo: safeProtectedProcedure.query(async function ({ ctx }) {
const notLoggedInUserTrackingData: TrackingSDKUserData = { const notLoggedInUserTrackingData: TrackingSDKUserData = {
loginStatus: "Non-logged in", loginStatus: "Non-logged in",
} }

View File

@@ -16,4 +16,4 @@ export interface ConfirmationProps extends BookingConfirmation {
export interface BookingConfirmationAlertsProps { export interface BookingConfirmationAlertsProps {
booking: BookingConfirmationSchema booking: BookingConfirmationSchema
} }

View File

@@ -26,6 +26,7 @@ export type TrackingSDKPageData = {
domain?: string domain?: string
siteSections: string siteSections: string
pageLoadTime?: number // Page load time in seconds pageLoadTime?: number // Page load time in seconds
lcpTime?: number // Largest contentful paint time in seconds
sessionId?: string | null sessionId?: string | null
} }