Merged in feat/3614-basicInfo (pull request #3427)

feat(SW-3614): use new loyalty prop in basicProfile

* feat(SW-3614): use new loyalty prop in basicProfile

* PR fixes


Approved-by: Matilda Landström
This commit is contained in:
Linus Flood
2026-01-15 14:02:28 +00:00
parent b9d134bca6
commit 7639daf792
12 changed files with 83 additions and 46 deletions

View File

@@ -1,8 +1,12 @@
import { TrackingSDK } from "@scandic-hotels/tracking/TrackingSDK" import { TrackingSDK } from "@scandic-hotels/tracking/TrackingSDK"
import { getEurobonusMembership } from "@scandic-hotels/trpc/routers/user/helpers" import {
getEurobonusMembership,
scandicMembershipTypes,
} from "@scandic-hotels/trpc/routers/user/helpers"
import { env } from "@/env/server" import { env } from "@/env/server"
import { import {
getBasicProfileSafely,
getProfileSafely, getProfileSafely,
getProfilingConsent, getProfilingConsent,
} from "@/lib/trpc/memoizedRequests" } from "@/lib/trpc/memoizedRequests"
@@ -85,10 +89,10 @@ async function MyPagesLayoutBase({
breadcrumbs, breadcrumbs,
children, children,
}: MyPagesLayoutProps) { }: MyPagesLayoutProps) {
const profile = await getProfileSafely() const profile = await getBasicProfileSafely()
const eurobonusMembership = profile?.loyalty const eurobonusMembership = profile?.loyalty?.memberships?.find(
? getEurobonusMembership(profile.loyalty) (m) => m.membershipType === scandicMembershipTypes.SAS_EB
: null )
return ( return (
<div className={styles.container}> <div className={styles.container}>

View File

@@ -1,9 +1,9 @@
import { redirect } from "next/navigation" import { redirect } from "next/navigation"
import React from "react" import React from "react"
import { getEurobonusMembership } from "@scandic-hotels/trpc/routers/user/helpers" import { scandicMembershipTypes } from "@scandic-hotels/trpc/routers/user/helpers"
import { getProfileSafely } from "@/lib/trpc/memoizedRequests" import { getBasicProfileSafely } from "@/lib/trpc/memoizedRequests"
import { SASModal } from "../components/SASModal" import { SASModal } from "../components/SASModal"
import { LinkAccountForm } from "./LinkAccountForm" import { LinkAccountForm } from "./LinkAccountForm"
@@ -12,11 +12,13 @@ export default async function SASxScandicLinkPage(
props: PageProps<"/[lang]/sas-x-scandic/link"> props: PageProps<"/[lang]/sas-x-scandic/link">
) { ) {
const params = await props.params const params = await props.params
const profile = await getProfileSafely() const profile = await getBasicProfileSafely()
if (!profile || !profile.loyalty) return null if (!profile || !profile.loyalty) return null
const eurobonusMembership = getEurobonusMembership(profile.loyalty) const eurobonusMembership = profile.loyalty.memberships?.some(
(m) => m.membershipType === scandicMembershipTypes.SAS_EB
)
if (eurobonusMembership) { if (eurobonusMembership) {
redirect(`/${params.lang}/sas-x-scandic/error?errorCode=alreadyLinked`) redirect(`/${params.lang}/sas-x-scandic/error?errorCode=alreadyLinked`)

View File

@@ -1,9 +1,9 @@
import ButtonLink from "@scandic-hotels/design-system/ButtonLink" import ButtonLink from "@scandic-hotels/design-system/ButtonLink"
import Image from "@scandic-hotels/design-system/Image" import Image from "@scandic-hotels/design-system/Image"
import { Typography } from "@scandic-hotels/design-system/Typography" import { Typography } from "@scandic-hotels/design-system/Typography"
import { getEurobonusMembership } from "@scandic-hotels/trpc/routers/user/helpers" import { scandicMembershipTypes } from "@scandic-hotels/trpc/routers/user/helpers"
import { getProfile } from "@/lib/trpc/memoizedRequests" import { getBasicProfileSafely } from "@/lib/trpc/memoizedRequests"
import { getIntl } from "@/i18n" import { getIntl } from "@/i18n"
import desktopBackground from "@/public/_static/img/sas/sas-scandic-link-account-banner-desktop.png" import desktopBackground from "@/public/_static/img/sas/sas-scandic-link-account-banner-desktop.png"
@@ -17,12 +17,14 @@ export default async function SASLinkAccountBanner(
props: DynamicContentProps["dynamic_content"] props: DynamicContentProps["dynamic_content"]
) { ) {
const intl = await getIntl() const intl = await getIntl()
const user = await getProfile() const user = await getBasicProfileSafely()
if (!user || "error" in user || !user.loyalty) { if (!user || "error" in user || !user.loyalty) {
return null return null
} }
const sasMembership = getEurobonusMembership(user.loyalty) const sasMembership = user.loyalty.memberships?.some(
(m) => m.membershipType === scandicMembershipTypes.SAS_EB
)
if (sasMembership) { if (sasMembership) {
return null return null
} }

View File

@@ -22,6 +22,17 @@ export const getBasicProfile = cache(async function getMemoizedBasicProfile() {
return caller.user.getBasic() return caller.user.getBasic()
}) })
export const getBasicProfileSafely = cache(
async function getMemoizedBasicProfile() {
try {
const caller = await serverClient()
return caller.user.getBasic()
} catch {
return null
}
}
)
export const getProfileSafely = cache( export const getProfileSafely = cache(
async function getMemoizedProfileSafely() { async function getMemoizedProfileSafely() {
const caller = await serverClient() const caller = await serverClient()
@@ -59,13 +70,6 @@ export const getMembershipLevelSafely = cache(
} }
) )
export const getMembershipCards = cache(
async function getMemoizedMembershipCards() {
const caller = await serverClient()
return caller.user.membershipCards()
}
)
export const getHotelsByCSFilter = cache(async function getMemoizedHotels( export const getHotelsByCSFilter = cache(async function getMemoizedHotels(
input: GetHotelsByCSFilterInput input: GetHotelsByCSFilterInput
) { ) {

View File

@@ -3,22 +3,25 @@ import { cache } from "react"
import * as routes from "@scandic-hotels/common/constants/routes/myPages" import * as routes from "@scandic-hotels/common/constants/routes/myPages"
import { safeTry } from "@scandic-hotels/common/utils/safeTry" import { safeTry } from "@scandic-hotels/common/utils/safeTry"
import { getEurobonusMembership } from "../../user/helpers" import { scandicMembershipTypes } from "../../user/helpers"
import type { Lang } from "@scandic-hotels/common/constants/language" import type { Lang } from "@scandic-hotels/common/constants/language"
import type { UserLoyalty } from "../../../types/user" import type { BasicUserProfile } from "../../../types/user"
import type { MyPagesLink } from "./MyPagesLink" import type { MyPagesLink } from "./MyPagesLink"
export const getPrimaryLinks = cache( export const getPrimaryLinks = cache(
async ({ async ({
lang, lang,
userLoyalty, basicUserLoyalty,
}: { }: {
lang: Lang lang: Lang
userLoyalty?: UserLoyalty basicUserLoyalty?: BasicUserProfile["loyalty"]
}): Promise<MyPagesLink[]> => { }): Promise<MyPagesLink[]> => {
const showSASLink = userLoyalty ? isScandicXSASActive(userLoyalty) : false const showSASLink = basicUserLoyalty?.memberships?.some(
(m) => m.membershipType === scandicMembershipTypes.SAS_EB
)
const [showTeamMemberLink] = await safeTry(showTeamMemberCard()) const [showTeamMemberLink] = await safeTry(showTeamMemberCard())
const menuItems: MyPagesLink[] = [ const menuItems: MyPagesLink[] = [
@@ -64,11 +67,6 @@ export const getPrimaryLinks = cache(
} }
) )
const isScandicXSASActive = (loyalty: UserLoyalty) => {
const eurobonusMembership = getEurobonusMembership(loyalty)
return Boolean(eurobonusMembership)
}
const showTeamMemberCard = cache(async () => { const showTeamMemberCard = cache(async () => {
async function getIsTeamMember() { async function getIsTeamMember() {
// TODO: Implement this check // TODO: Implement this check

View File

@@ -6,7 +6,7 @@ import { safeTry } from "@scandic-hotels/common/utils/safeTry"
import { safeProtectedProcedure } from "../../../procedures" import { safeProtectedProcedure } from "../../../procedures"
import { isValidSession } from "../../../utils/session" import { isValidSession } from "../../../utils/session"
import { getVerifiedUser } from "../../user/utils/getVerifiedUser" import { getBasicUser } from "../../user/utils/getBasicUser"
import { getPrimaryLinks } from "./getPrimaryLinks" import { getPrimaryLinks } from "./getPrimaryLinks"
import { getSecondaryLinks } from "./getSecondaryLinks" import { getSecondaryLinks } from "./getSecondaryLinks"
@@ -40,14 +40,14 @@ export const myPagesNavigation = safeProtectedProcedure
} }
const [user, error] = await safeTry( const [user, error] = await safeTry(
getVerifiedUser({ token: ctx.session.token }) getBasicUser({ token: ctx.session.token })
) )
if (!user || error) { if (!user || error) {
return null return null
} }
const [primaryLinks, secondaryLinks] = await Promise.all([ const [primaryLinks, secondaryLinks] = await Promise.all([
getPrimaryLinks({ lang, userLoyalty: user.loyalty }), getPrimaryLinks({ lang, basicUserLoyalty: user.loyalty }),
getSecondaryLinks({ lang }), getSecondaryLinks({ lang }),
]) ])

View File

@@ -35,10 +35,10 @@ export const performLevelUpgrade = protectedProcedure
const [profile, error] = await safeTry( const [profile, error] = await safeTry(
getBasicUser({ token: ctx.session.token }) getBasicUser({ token: ctx.session.token })
) )
if (!profile || error) { if (!profile || error || !profile.loyalty?.tier) {
return { tierMatchState: "error" } return { tierMatchState: "error" }
} }
const currentLevel = profile.tier const currentLevel = profile.loyalty.tier
sasLogger.debug("tier match started") sasLogger.debug("tier match started")

View File

@@ -8,6 +8,7 @@ import type {
export enum scandicMembershipTypes { export enum scandicMembershipTypes {
SCANDIC_NATIVE = "SCANDIC_NATIVE", SCANDIC_NATIVE = "SCANDIC_NATIVE",
SAS_EB = "SAS_EB", SAS_EB = "SAS_EB",
"OTHER" = "OTHER",
} }
export function isScandicNativeMembership( export function isScandicNativeMembership(

View File

@@ -2,7 +2,7 @@ import { z } from "zod"
import { countriesMap } from "../../constants/countries" import { countriesMap } from "../../constants/countries"
import { imageSchema } from "../../routers/hotels/schemas/image" import { imageSchema } from "../../routers/hotels/schemas/image"
import { getFriendsMembership } from "./helpers" import { getFriendsMembership, scandicMembershipTypes } from "./helpers"
const scandicFriendsTier = z.enum(["L1", "L2", "L3", "L4", "L5", "L6", "L7"]) const scandicFriendsTier = z.enum(["L1", "L2", "L3", "L4", "L5", "L6", "L7"])
const sasEurobonusTier = z.enum(["EBB", "EBS", "EBG", "EBD", "EBP"]) const sasEurobonusTier = z.enum(["EBB", "EBS", "EBG", "EBD", "EBP"])
@@ -23,7 +23,7 @@ const otherMembershipSchema = z
export const sasMembershipSchema = z export const sasMembershipSchema = z
.object({ .object({
type: z.literal("SAS_EB"), type: z.literal(scandicMembershipTypes.SAS_EB),
tier: sasEurobonusTier, tier: sasEurobonusTier,
nextTier: sasEurobonusTier.nullish(), nextTier: sasEurobonusTier.nullish(),
spendablePoints: z.number().nullish(), spendablePoints: z.number().nullish(),
@@ -43,7 +43,7 @@ export const sasMembershipSchema = z
export const friendsMembershipSchema = z export const friendsMembershipSchema = z
.object({ .object({
type: z.literal("SCANDIC_NATIVE"), type: z.literal(scandicMembershipTypes.SCANDIC_NATIVE),
tier: scandicFriendsTier, tier: scandicFriendsTier,
nextTier: scandicFriendsTier.nullish(), nextTier: scandicFriendsTier.nullish(),
pointsToNextTier: z.number().nullish(), pointsToNextTier: z.number().nullish(),
@@ -140,8 +140,6 @@ export const getBasicUserSchema = z.object({
lastName: z.string(), lastName: z.string(),
phoneNumber: z.string().optional(), phoneNumber: z.string().optional(),
profileId: z.string().optional(), profileId: z.string().optional(),
membershipNumber: z.string(),
tier: scandicFriendsTier,
address: z address: z
.object({ .object({
city: z.string().optional(), city: z.string().optional(),
@@ -152,6 +150,21 @@ export const getBasicUserSchema = z.object({
}) })
.optional() .optional()
.nullable(), .nullable(),
loyalty: z
.object({
tier: scandicFriendsTier,
tierExpires: z.string(),
memberships: z.array(
z.object({
membershipType: z
.nativeEnum(scandicMembershipTypes)
.catch(scandicMembershipTypes.OTHER),
membershipNumber: z.string(),
})
),
})
.optional()
.nullable(),
}) })
export const creditCardSchema = z export const creditCardSchema = z

View File

@@ -3,6 +3,7 @@ import { createCounter } from "@scandic-hotels/common/telemetry"
import { safeProtectedProcedure } from "../../../procedures" import { safeProtectedProcedure } from "../../../procedures"
import { isValidSession } from "../../../utils/session" import { isValidSession } from "../../../utils/session"
import { getEuroBonusProfileData } from "../../partners/sas/getEuroBonusProfile" import { getEuroBonusProfileData } from "../../partners/sas/getEuroBonusProfile"
import { scandicMembershipTypes } from "../helpers"
import { getBasicUser } from "../utils/getBasicUser" import { getBasicUser } from "../utils/getBasicUser"
import type { LoginType } from "@scandic-hotels/common/constants/loginType" import type { LoginType } from "@scandic-hotels/common/constants/loginType"
@@ -62,8 +63,10 @@ async function getScandicFriendsUserTrackingData(session: Session | null) {
loginStatus: "logged in", loginStatus: "logged in",
loginType: session.token.loginType as LoginType, loginType: session.token.loginType as LoginType,
memberId: verifiedUserData.profileId, memberId: verifiedUserData.profileId,
membershipNumber: verifiedUserData.membershipNumber, membershipNumber: verifiedUserData.loyalty?.memberships?.find(
memberLevel: verifiedUserData?.tier, (m) => m.membershipType === scandicMembershipTypes.SCANDIC_NATIVE
)?.membershipNumber,
memberLevel: verifiedUserData.loyalty?.tier,
loginAction: "login success", loginAction: "login success",
memberType, memberType,
} }
@@ -134,7 +137,9 @@ async function getScandicFriendsDataHelper(scandicUserToken: string | null) {
return { return {
memberId: verifiedUserData.profileId, memberId: verifiedUserData.profileId,
membershipNumber: verifiedUserData.membershipNumber, membershipNumber: verifiedUserData.loyalty?.memberships?.find(
memberLevel: verifiedUserData.tier, (m) => m.membershipType === scandicMembershipTypes.SCANDIC_NATIVE
)?.membershipNumber,
memberLevel: verifiedUserData.loyalty?.tier,
} }
} }

View File

@@ -9,6 +9,7 @@ import {
serverErrorByStatus, serverErrorByStatus,
sessionExpiredError, sessionExpiredError,
} from "../../../errors" } from "../../../errors"
import { scandicMembershipTypes } from "../helpers"
import { getBasicUserSchema } from "../output" import { getBasicUserSchema } from "../output"
import type z from "zod" import type z from "zod"
@@ -63,15 +64,19 @@ export const getBasicUser = cache(
function addUserToSentry(apiJson: unknown) { function addUserToSentry(apiJson: unknown) {
const typedData = apiJson as DeepPartial<z.input<typeof getBasicUserSchema>> const typedData = apiJson as DeepPartial<z.input<typeof getBasicUserSchema>>
const memberShipNumber = typedData?.loyalty?.memberships?.find(
(m) => m?.membershipType === scandicMembershipTypes.SCANDIC_NATIVE
)?.membershipNumber
if ( if (
typeof typedData?.profileId === "undefined" || typeof typedData?.profileId === "undefined" ||
typeof typedData?.membershipNumber === "undefined" typeof memberShipNumber === "undefined"
) { ) {
return return
} }
Sentry.setUser({ Sentry.setUser({
id: typedData?.profileId, id: typedData?.profileId,
username: typedData?.membershipNumber, username: memberShipNumber,
}) })
} }

View File

@@ -4,6 +4,7 @@ import type { getFriendsMembership } from "../routers/user/helpers"
import type { import type {
creditCardSchema, creditCardSchema,
friendsMembershipSchema, friendsMembershipSchema,
getBasicUserSchema,
getUserSchema, getUserSchema,
sasMembershipSchema, sasMembershipSchema,
userLoyaltySchema, userLoyaltySchema,
@@ -23,6 +24,8 @@ export type Membership = UserLoyalty["memberships"][number]
export type NativeFriendsMembership = z.output<typeof friendsMembershipSchema> export type NativeFriendsMembership = z.output<typeof friendsMembershipSchema>
export type EurobonusMembership = z.output<typeof sasMembershipSchema> export type EurobonusMembership = z.output<typeof sasMembershipSchema>
export type BasicUserProfile = z.output<typeof getBasicUserSchema>
export type FriendsMembership = ReturnType<typeof getFriendsMembership> export type FriendsMembership = ReturnType<typeof getFriendsMembership>
export type EurobonusTier = EurobonusMembership["tier"] export type EurobonusTier = EurobonusMembership["tier"]