Merged in feat/sw-1975-get-profile-v2 (pull request #1651)
Use get Profile V2 endpoint Approved-by: Linus Flood
This commit is contained in:
@@ -10,7 +10,6 @@ import { protectedServerActionProcedure } from "@/server/trpc"
|
||||
|
||||
import { editProfileSchema } from "@/components/Forms/Edit/Profile/schema"
|
||||
import { getIntl } from "@/i18n"
|
||||
import { getFriendsMembership } from "@/utils/user"
|
||||
import { phoneValidator } from "@/utils/zod/phoneValidator"
|
||||
|
||||
import { Status } from "@/types/components/myPages/myProfile/edit"
|
||||
@@ -142,9 +141,8 @@ export const editProfile = protectedServerActionProcedure
|
||||
status: Status.success,
|
||||
}
|
||||
} else {
|
||||
const membership = getFriendsMembership(profile.memberships)
|
||||
console.log(
|
||||
`[edit profile: ${membership?.membershipNumber}] body keys: ${JSON.stringify(Object.keys(body))}`
|
||||
`[edit profile: ${profile.membershipNumber}] body keys: ${JSON.stringify(Object.keys(body))}`
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ export default async function SASxScandicLinkPage({
|
||||
|
||||
if (!profile) return null
|
||||
|
||||
const eurobonusMembership = getEurobonusMembership(profile.memberships)
|
||||
const eurobonusMembership = getEurobonusMembership(profile.loyalty)
|
||||
|
||||
if (eurobonusMembership) {
|
||||
redirect(`/${params.lang}/sas-x-scandic/error?errorCode=alreadyLinked`)
|
||||
|
||||
@@ -10,7 +10,7 @@ import type { UserProps } from "@/types/components/myPages/user"
|
||||
|
||||
export default async function ExpiringPoints({ user }: UserProps) {
|
||||
const intl = await getIntl()
|
||||
const membership = getFriendsMembership(user.memberships)
|
||||
const membership = getFriendsMembership(user.loyalty)
|
||||
|
||||
if (!membership || !membership.pointsToExpire) {
|
||||
// TODO: handle this case?
|
||||
|
||||
@@ -12,7 +12,7 @@ import type { UserProps } from "@/types/components/myPages/user"
|
||||
export default async function Points({ user }: UserProps) {
|
||||
const intl = await getIntl()
|
||||
|
||||
const membership = getFriendsMembership(user.memberships)
|
||||
const membership = getFriendsMembership(user.loyalty)
|
||||
|
||||
const nextLevel =
|
||||
membership?.nextLevel && MembershipLevelEnum[membership.nextLevel]
|
||||
|
||||
@@ -5,7 +5,10 @@ import DiamondAddIcon from "@scandic-hotels/design-system/Icons/DiamondAddIcon"
|
||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import { TIER_TO_FRIEND_MAP } from "@/constants/membershipLevels"
|
||||
import {
|
||||
SAS_EUROBONUS_TIER_TO_NAME_MAP,
|
||||
TIER_TO_FRIEND_MAP,
|
||||
} from "@/constants/membershipLevels"
|
||||
import { env } from "@/env/server"
|
||||
import { getProfile } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
@@ -14,17 +17,13 @@ import SectionHeader from "@/components/Section/Header"
|
||||
import SectionLink from "@/components/Section/Link"
|
||||
import SkeletonShimmer from "@/components/SkeletonShimmer"
|
||||
import { getIntl } from "@/i18n"
|
||||
import {
|
||||
getEurobonusMembership,
|
||||
getFriendsMembership,
|
||||
scandicMemberships,
|
||||
} from "@/utils/user"
|
||||
import { getEurobonusMembership } from "@/utils/user"
|
||||
|
||||
import { UnlinkSAS } from "./UnlinkSAS"
|
||||
|
||||
import styles from "./linkedAccounts.module.css"
|
||||
|
||||
import type { Membership } from "@/types/user"
|
||||
import type { UserLoyalty } from "@/types/user"
|
||||
|
||||
type Props = {
|
||||
title?: string
|
||||
@@ -78,20 +77,20 @@ async function MatchedAccountInfo() {
|
||||
|
||||
const intl = await getIntl()
|
||||
|
||||
const eurobonusMembership = getEurobonusMembership(user.memberships)
|
||||
const eurobonusMembership = getEurobonusMembership(user.loyalty)
|
||||
const friendsMembership = user.membership
|
||||
if (!eurobonusMembership || !friendsMembership) {
|
||||
return null
|
||||
}
|
||||
|
||||
const sasLevelName = eurobonusMembership.membershipLevel || "-"
|
||||
const sasLevelName = SAS_EUROBONUS_TIER_TO_NAME_MAP[eurobonusMembership.tier]
|
||||
const sasMembershipNumber = eurobonusMembership.membershipNumber
|
||||
const sasTierExpirationDate = eurobonusMembership.tierExpirationDate
|
||||
const sasTierExpirationDate = eurobonusMembership.tierExpires
|
||||
|
||||
const scandicLevelName = TIER_TO_FRIEND_MAP[friendsMembership.membershipLevel]
|
||||
const scandicExpirationDate = friendsMembership.tierExpirationDate
|
||||
|
||||
const matchState = calculateMatchState(user.memberships)
|
||||
const matchState = calculateMatchState(user.loyalty)
|
||||
|
||||
return (
|
||||
<section className={styles.matchedAccountSection}>
|
||||
@@ -278,30 +277,12 @@ function Label({ children }: { children: ReactNode }) {
|
||||
}
|
||||
|
||||
type MatchState = "boostedBySAS" | "boostedByScandic" | "noBoost"
|
||||
function calculateMatchState(memberships: Membership[]): MatchState {
|
||||
const eurobonusMembership = getEurobonusMembership(memberships)
|
||||
const friendsMembership = getFriendsMembership(memberships)
|
||||
const nativeMembership = memberships.find(
|
||||
(x) => x.membershipType === scandicMemberships.scandic_native_tiers
|
||||
)
|
||||
function calculateMatchState(loyalty: UserLoyalty): MatchState {
|
||||
if (!loyalty.tierBoostedBy) return "noBoost"
|
||||
if (loyalty.tierBoostedBy === "SAS_EB") return "boostedBySAS"
|
||||
|
||||
if (!eurobonusMembership || !friendsMembership || !nativeMembership) {
|
||||
return "noBoost"
|
||||
}
|
||||
|
||||
const nativeLevel = nativeMembership.membershipLevel
|
||||
const friendsLevel = friendsMembership.membershipLevel
|
||||
|
||||
if (nativeLevel !== friendsLevel) {
|
||||
return "boostedBySAS"
|
||||
}
|
||||
|
||||
// TODO check if SAS have been boosted by Scandic when API is available
|
||||
const isBoostedByScandic = false
|
||||
|
||||
if (isBoostedByScandic) {
|
||||
return "boostedByScandic"
|
||||
}
|
||||
// const eurobonusMembership = getEurobonusMembership(loyalty)
|
||||
// if (eurobonusMembership.boostedByScandic) return "boostedByScandic"
|
||||
|
||||
return "noBoost"
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import SkeletonShimmer from "@/components/SkeletonShimmer"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import useClickOutside from "@/hooks/useClickOutside"
|
||||
import { useHandleKeyUp } from "@/hooks/useHandleKeyUp"
|
||||
import { type FriendsMembership, getInitials } from "@/utils/user"
|
||||
import { getInitials } from "@/utils/user"
|
||||
|
||||
import Avatar from "../Avatar"
|
||||
import MainMenuButton from "../MainMenuButton"
|
||||
@@ -20,7 +20,7 @@ import MyPagesMenuContent, { useMyPagesNavigation } from "../MyPagesMenuContent"
|
||||
import styles from "./myPagesMenu.module.css"
|
||||
|
||||
import { DropdownTypeEnum } from "@/types/components/dropdown/dropdown"
|
||||
import type { User } from "@/types/user"
|
||||
import type { FriendsMembership,User } from "@/types/user"
|
||||
import type { LoyaltyLevel } from "@/server/routers/contentstack/loyaltyLevel/output"
|
||||
|
||||
export type MyPagesMenuProps = {
|
||||
|
||||
@@ -127,8 +127,19 @@ const user: SafeUser = {
|
||||
firstName: "",
|
||||
language: undefined,
|
||||
lastName: "",
|
||||
membershipNumber: "",
|
||||
membership: undefined,
|
||||
memberships: [],
|
||||
loyalty: {
|
||||
memberships: [],
|
||||
pointExpirations: [],
|
||||
points: {
|
||||
earned: 0,
|
||||
spent: 0,
|
||||
spendable: 0,
|
||||
},
|
||||
tier: "L1",
|
||||
tierExpires: "",
|
||||
},
|
||||
name: "",
|
||||
phoneNumber: undefined,
|
||||
profileId: "",
|
||||
|
||||
@@ -16,7 +16,7 @@ export default async function SidebarMyPages() {
|
||||
const intl = await getIntl()
|
||||
const profile = await getProfileSafely()
|
||||
const eurobonusMembership = profile
|
||||
? getEurobonusMembership(profile.memberships)
|
||||
? getEurobonusMembership(profile.loyalty)
|
||||
: null
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { EurobonusTier } from "@/types/user"
|
||||
|
||||
export enum membershipLevels {
|
||||
L1 = 1,
|
||||
L2 = 2,
|
||||
@@ -32,3 +34,11 @@ export const TIER_TO_FRIEND_MAP: Record<MembershipLevelEnum, string> = {
|
||||
}
|
||||
|
||||
export type MembershipLevel = keyof typeof MembershipLevelEnum
|
||||
|
||||
export const SAS_EUROBONUS_TIER_TO_NAME_MAP: Record<EurobonusTier, string> = {
|
||||
EBB: "Basic",
|
||||
EBS: "Silver",
|
||||
EBG: "Gold",
|
||||
EBD: "Diamond",
|
||||
EBP: "Pandion",
|
||||
}
|
||||
|
||||
@@ -207,6 +207,18 @@ export namespace endpoints {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export namespace v2 {
|
||||
const version = "v2"
|
||||
|
||||
/**
|
||||
* profile (Swagger)
|
||||
* https://tstapi.scandichotels.com/profile/swagger/v2/index.html
|
||||
*/
|
||||
export namespace Profile {
|
||||
export const profile = `${base.path.profile}/${version}/${base.enitity.Profile}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type Endpoint = string
|
||||
|
||||
@@ -6,7 +6,6 @@ import { getVerifiedUser } from "@/server/routers/user/query"
|
||||
import { router, safeProtectedServiceProcedure } from "@/server/trpc"
|
||||
|
||||
import { isValidSession } from "@/utils/session"
|
||||
import { getFriendsMembership } from "@/utils/user"
|
||||
|
||||
import {
|
||||
addPackageInput,
|
||||
@@ -85,8 +84,7 @@ async function getMembershipNumber(
|
||||
return undefined
|
||||
}
|
||||
|
||||
const membership = getFriendsMembership(verifiedUser.data.memberships)
|
||||
return membership?.membershipNumber
|
||||
return verifiedUser.data.membershipNumber
|
||||
}
|
||||
|
||||
export const bookingMutationRouter = router({
|
||||
|
||||
@@ -7,20 +7,20 @@ import { getIntl } from "@/i18n"
|
||||
import { safeTry } from "@/utils/safeTry"
|
||||
import { getEurobonusMembership } from "@/utils/user"
|
||||
|
||||
import type { Membership } from "@/types/user"
|
||||
import type { UserLoyalty } from "@/types/user"
|
||||
import type { Lang } from "@/constants/languages"
|
||||
import type { MyPagesLink } from "./MyPagesLink"
|
||||
|
||||
export const getPrimaryLinks = cache(
|
||||
async ({
|
||||
lang,
|
||||
memberships,
|
||||
userLoyalty,
|
||||
}: {
|
||||
lang: Lang
|
||||
memberships: Membership[]
|
||||
userLoyalty: UserLoyalty
|
||||
}): Promise<MyPagesLink[]> => {
|
||||
const intl = await getIntl()
|
||||
const showSASLink = isScandicXSASActive(memberships)
|
||||
const showSASLink = isScandicXSASActive(userLoyalty)
|
||||
const [showTeamMemberLink] = await safeTry(showTeamMemberCard())
|
||||
|
||||
const menuItems: MyPagesLink[] = [
|
||||
@@ -66,8 +66,8 @@ export const getPrimaryLinks = cache(
|
||||
}
|
||||
)
|
||||
|
||||
const isScandicXSASActive = (memberships: Membership[]) => {
|
||||
const eurobonusMembership = getEurobonusMembership(memberships)
|
||||
const isScandicXSASActive = (loyalty: UserLoyalty) => {
|
||||
const eurobonusMembership = getEurobonusMembership(loyalty)
|
||||
const isLinked = Boolean(eurobonusMembership)
|
||||
|
||||
return env.SAS_ENABLED && isLinked
|
||||
|
||||
@@ -45,7 +45,7 @@ export const myPagesNavigation = safeProtectedProcedure
|
||||
}
|
||||
|
||||
const [primaryLinks, secondaryLinks] = await Promise.all([
|
||||
getPrimaryLinks({ lang, memberships: user.data.memberships }),
|
||||
getPrimaryLinks({ lang, userLoyalty: user.data.loyalty }),
|
||||
getSecondaryLinks({ lang }),
|
||||
])
|
||||
|
||||
|
||||
@@ -2,53 +2,85 @@ import { z } from "zod"
|
||||
|
||||
import { countriesMap } from "@/constants/countries"
|
||||
|
||||
import { getFriendsMembership, scandicMemberships } from "@/utils/user"
|
||||
import { getFriendsMembership } from "@/utils/user"
|
||||
|
||||
import { imageSchema } from "../hotels/schemas/image"
|
||||
|
||||
const scandicFriendsTier = z.enum(["L1", "L2", "L3", "L4", "L5", "L6", "L7"])
|
||||
const sasEurobonusTier = z.enum(["EBB", "EBS", "EBG", "EBD", "EBP"])
|
||||
|
||||
const commonMembershipSchema = z.object({
|
||||
currentPoints: z.number().optional(),
|
||||
expirationDate: z.string().optional(),
|
||||
membershipLevel: z.string().optional(),
|
||||
nextLevel: z.string().optional(),
|
||||
nightsToTopTier: z.number().optional(),
|
||||
pointsExpiryDate: z.string().optional(),
|
||||
pointsRequiredToNextlevel: z.number().optional(),
|
||||
pointsToExpire: z.number().optional(),
|
||||
tierExpirationDate: z.string().optional(),
|
||||
membershipNumber: z.string(),
|
||||
tierExpires: z.string(),
|
||||
memberSince: z.string().nullish(),
|
||||
})
|
||||
|
||||
const toLowerCaseString = z.string().transform((s) => s.toLowerCase())
|
||||
const membershipType = (membershipType: scandicMemberships) =>
|
||||
toLowerCaseString
|
||||
// The memberships enum is in lower case so this makes sure it will match regardless of casing in the API response.
|
||||
.pipe(z.literal(membershipType))
|
||||
|
||||
const friendsMembershipSchema = z
|
||||
// This prevents validation errors if the API returns an unhandled membership type
|
||||
const otherMembershipSchema = z
|
||||
.object({
|
||||
membershipType: membershipType(scandicMemberships.guestpr),
|
||||
membershipNumber: z.string(),
|
||||
memberSince: z.string(),
|
||||
// This ensures that `type` won't widen into "string", losing the literal types, when used in a union
|
||||
type: z.string().refine((val): val is string & {} => true),
|
||||
})
|
||||
.merge(commonMembershipSchema)
|
||||
|
||||
const otherMembershipSchema = z
|
||||
export const sasMembershipSchema = z
|
||||
.object({
|
||||
membershipType: toLowerCaseString,
|
||||
membershipNumber: z.string().optional(),
|
||||
memberSince: z.string().optional(),
|
||||
type: z.literal("SAS_EB"),
|
||||
tier: sasEurobonusTier,
|
||||
nextTier: sasEurobonusTier.nullish(),
|
||||
})
|
||||
.merge(commonMembershipSchema)
|
||||
|
||||
export const friendsMembershipSchema = z
|
||||
.object({
|
||||
type: z.literal("SCANDIC_NATIVE"),
|
||||
tier: scandicFriendsTier,
|
||||
nextTier: scandicFriendsTier.nullish(),
|
||||
pointsToNextTier: z.number().nullish(),
|
||||
nightsToTopTier: z.number().nullish(),
|
||||
})
|
||||
.merge(commonMembershipSchema)
|
||||
|
||||
export const membershipSchema = z.union([
|
||||
friendsMembershipSchema,
|
||||
sasMembershipSchema,
|
||||
otherMembershipSchema,
|
||||
])
|
||||
|
||||
const pointExpirationSchema = z.object({
|
||||
points: z.number().int(),
|
||||
expires: z.string(),
|
||||
})
|
||||
|
||||
export const userLoyaltySchema = z.object({
|
||||
memberships: z.array(membershipSchema),
|
||||
points: z.object({
|
||||
spendable: z.number().int(),
|
||||
earned: z.number().int(),
|
||||
spent: z.number().int(),
|
||||
}),
|
||||
tier: scandicFriendsTier,
|
||||
tierExpires: z.string(),
|
||||
tierBoostedBy: z.string().nullish(),
|
||||
pointExpirations: z.array(pointExpirationSchema),
|
||||
})
|
||||
|
||||
export const getUserSchema = z
|
||||
.object({
|
||||
data: z.object({
|
||||
attributes: z.object({
|
||||
dateOfBirth: z.string().optional().default("1900-01-01"),
|
||||
email: z.string().email(),
|
||||
firstName: z.string(),
|
||||
language: z
|
||||
.string()
|
||||
// Preserve Profile v1 formatting for now so it matches ApiLang enum
|
||||
.transform((s) => s.charAt(0).toUpperCase() + s.slice(1))
|
||||
.optional(),
|
||||
lastName: z.string(),
|
||||
phoneNumber: z.string().optional(),
|
||||
profileId: z.string(),
|
||||
membershipNumber: z.string(),
|
||||
address: z
|
||||
.object({
|
||||
city: z.string().optional(),
|
||||
@@ -59,14 +91,7 @@ export const getUserSchema = z
|
||||
})
|
||||
.optional()
|
||||
.nullable(),
|
||||
dateOfBirth: z.string().optional().default("1900-01-01"),
|
||||
email: z.string().email(),
|
||||
firstName: z.string(),
|
||||
language: z.string().optional(),
|
||||
lastName: z.string(),
|
||||
memberships: z.array(membershipSchema),
|
||||
phoneNumber: z.string().optional(),
|
||||
profileId: z.string(),
|
||||
loyalty: userLoyaltySchema,
|
||||
}),
|
||||
type: z.string(),
|
||||
}),
|
||||
@@ -74,7 +99,7 @@ export const getUserSchema = z
|
||||
.transform((apiResponse) => {
|
||||
return {
|
||||
...apiResponse.data.attributes,
|
||||
membership: getFriendsMembership(apiResponse.data.attributes.memberships),
|
||||
membership: getFriendsMembership(apiResponse.data.attributes.loyalty),
|
||||
name: `${apiResponse.data.attributes.firstName} ${apiResponse.data.attributes.lastName}`,
|
||||
}
|
||||
})
|
||||
@@ -224,16 +249,6 @@ export const creditCardsSchema = z.object({
|
||||
data: z.array(creditCardSchema),
|
||||
})
|
||||
|
||||
export const getMembershipCardsSchema = z.array(
|
||||
z.object({
|
||||
currentPoints: z.number(),
|
||||
expirationDate: z.string(),
|
||||
membershipNumber: z.string(),
|
||||
memberSince: z.string(),
|
||||
membershipType: z.string(),
|
||||
})
|
||||
)
|
||||
|
||||
export const initiateSaveCardSchema = z.object({
|
||||
data: z.object({
|
||||
attribute: z.object({
|
||||
|
||||
@@ -23,7 +23,6 @@ import {
|
||||
import {
|
||||
creditCardsSchema,
|
||||
getFriendTransactionsSchema,
|
||||
getMembershipCardsSchema,
|
||||
getStaysSchema,
|
||||
getUserSchema,
|
||||
} from "./output"
|
||||
@@ -37,7 +36,6 @@ import type {
|
||||
} from "@/types/components/tracking"
|
||||
import { Transactions } from "@/types/enums/transactions"
|
||||
import type { User } from "@/types/user"
|
||||
import type { MembershipLevel } from "@/constants/membershipLevels"
|
||||
|
||||
// OpenTelemetry metrics: User
|
||||
const meter = metrics.getMeter("trpc.user")
|
||||
@@ -46,10 +44,6 @@ const getVerifiedUserSuccessCounter = meter.createCounter(
|
||||
"trpc.user.get-success"
|
||||
)
|
||||
const getVerifiedUserFailCounter = meter.createCounter("trpc.user.get-fail")
|
||||
const getProfileSuccessCounter = meter.createCounter(
|
||||
"trpc.user.profile-success"
|
||||
)
|
||||
const getProfileFailCounter = meter.createCounter("trpc.user.profile-fail")
|
||||
|
||||
// OpenTelemetry metrics: Stays
|
||||
const getPreviousStaysCounter = meter.createCounter("trpc.user.stays.previous")
|
||||
@@ -95,7 +89,7 @@ export const getVerifiedUser = cache(
|
||||
}
|
||||
getVerifiedUserCounter.add(1)
|
||||
console.info("api.user.profile getVerifiedUser start", JSON.stringify({}))
|
||||
const apiResponse = await api.get(api.endpoints.v1.Profile.profile, {
|
||||
const apiResponse = await api.get(api.endpoints.v2.Profile.profile, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${session.token.access_token}`,
|
||||
},
|
||||
@@ -186,8 +180,9 @@ export function parsedUser(data: User, isMFA: boolean) {
|
||||
firstName: data.firstName,
|
||||
language: data.language,
|
||||
lastName: data.lastName,
|
||||
membership: getFriendsMembership(data.memberships),
|
||||
memberships: data.memberships,
|
||||
membershipNumber: data.membershipNumber,
|
||||
membership: getFriendsMembership(data.loyalty),
|
||||
loyalty: data.loyalty,
|
||||
name: `${data.firstName} ${data.lastName}`,
|
||||
phoneNumber: data.phoneNumber,
|
||||
profileId: data.profileId,
|
||||
@@ -343,7 +338,7 @@ export const userQueryRouter = router({
|
||||
return null
|
||||
}
|
||||
|
||||
const membershipLevel = getFriendsMembership(verifiedData.data.memberships)
|
||||
const membershipLevel = getFriendsMembership(verifiedData.data.loyalty)
|
||||
return membershipLevel
|
||||
}),
|
||||
safeMembershipLevel: safeProtectedProcedure.query(async function ({ ctx }) {
|
||||
@@ -356,7 +351,7 @@ export const userQueryRouter = router({
|
||||
return null
|
||||
}
|
||||
|
||||
const membershipLevel = getFriendsMembership(verifiedData.data.memberships)
|
||||
const membershipLevel = getFriendsMembership(verifiedData.data.loyalty)
|
||||
return membershipLevel
|
||||
}),
|
||||
userTrackingInfo: safeProtectedProcedure.query(async function ({ ctx }) {
|
||||
@@ -429,14 +424,14 @@ export const userQueryRouter = router({
|
||||
getPreviousStaysSuccessCounter.add(1)
|
||||
console.info("api.booking.stays.past success", JSON.stringify({}))
|
||||
|
||||
const membership = getFriendsMembership(verifiedUserData.data.memberships)
|
||||
const membership = getFriendsMembership(verifiedUserData.data.loyalty)
|
||||
|
||||
const loggedInUserTrackingData: TrackingSDKUserData = {
|
||||
loginStatus: "logged in",
|
||||
loginType: ctx.session.token.loginType as LoginType,
|
||||
memberId: verifiedUserData.data.profileId,
|
||||
membershipNumber: membership?.membershipNumber,
|
||||
memberLevel: membership?.membershipLevel as MembershipLevel,
|
||||
memberLevel: membership?.membershipLevel,
|
||||
noOfNightsStayed: verifiedPreviousStaysData.data.links?.totalCount ?? 0,
|
||||
totalPointsAvailableToSpend: membership?.currentPoints,
|
||||
loginAction: "login success",
|
||||
@@ -795,23 +790,6 @@ export const userQueryRouter = router({
|
||||
return null
|
||||
}
|
||||
|
||||
const verifiedData = getMembershipCardsSchema.safeParse(
|
||||
userData.data.memberships
|
||||
)
|
||||
|
||||
if (!verifiedData.success) {
|
||||
getProfileFailCounter.add(1, {
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(verifiedData),
|
||||
})
|
||||
console.error(
|
||||
"api.profile validation error",
|
||||
JSON.stringify({ error: verifiedData })
|
||||
)
|
||||
return null
|
||||
}
|
||||
getProfileSuccessCounter.add(1)
|
||||
|
||||
return getMembershipCards(verifiedData.data)
|
||||
return getMembershipCards(userData.data.loyalty)
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export type PointsColumnProps = {
|
||||
title: string
|
||||
subtitle?: string
|
||||
value?: number
|
||||
value?: number | null
|
||||
}
|
||||
|
||||
@@ -3,9 +3,12 @@ import type { z } from "zod"
|
||||
import type { RouterOutput } from "@/lib/trpc/client"
|
||||
import type {
|
||||
creditCardSchema,
|
||||
friendsMembershipSchema,
|
||||
getUserSchema,
|
||||
membershipSchema,
|
||||
sasMembershipSchema,
|
||||
userLoyaltySchema,
|
||||
} from "@/server/routers/user/output"
|
||||
import type { getFriendsMembership } from "@/utils/user"
|
||||
|
||||
/**
|
||||
* All extended field needs to be added by API team to response or
|
||||
@@ -17,6 +20,12 @@ export type SafeUser = RouterOutput["user"]["getSafely"]
|
||||
|
||||
export type CreditCard = z.output<typeof creditCardSchema>
|
||||
|
||||
export type Membership = z.output<typeof membershipSchema>
|
||||
export type UserLoyalty = z.output<typeof userLoyaltySchema>
|
||||
|
||||
export type Memberships = Membership[]
|
||||
export type Membership = UserLoyalty["memberships"][number]
|
||||
export type NativeFriendsMembership = z.output<typeof friendsMembershipSchema>
|
||||
export type EurobonusMembership = z.output<typeof sasMembershipSchema>
|
||||
|
||||
export type FriendsMembership = ReturnType<typeof getFriendsMembership>
|
||||
|
||||
export type EurobonusTier = EurobonusMembership["tier"]
|
||||
|
||||
@@ -3,50 +3,78 @@ import {
|
||||
MembershipLevelEnum,
|
||||
} from "@/constants/membershipLevels"
|
||||
|
||||
import type { z } from "zod"
|
||||
import type {
|
||||
EurobonusMembership,
|
||||
Membership,
|
||||
NativeFriendsMembership,
|
||||
User,
|
||||
UserLoyalty,
|
||||
} from "@/types/user"
|
||||
|
||||
import type { Membership, Memberships, User } from "@/types/user"
|
||||
import type { getMembershipCardsSchema } from "@/server/routers/user/output"
|
||||
|
||||
export enum scandicMemberships {
|
||||
guestpr = "guestpr",
|
||||
scandicfriends = "scandicfriend's",
|
||||
sas_eb = "sas_eb",
|
||||
scandic_native_tiers = "scandic_native_tiers",
|
||||
export enum scandicMembershipTypes {
|
||||
SCANDIC_NATIVE = "SCANDIC_NATIVE",
|
||||
SAS_EB = "SAS_EB",
|
||||
}
|
||||
|
||||
export function getFriendsMembership(memberships: Memberships) {
|
||||
return memberships?.find(
|
||||
(membership) =>
|
||||
membership.membershipType.toLowerCase() === scandicMemberships.guestpr
|
||||
) as FriendsMembership | undefined
|
||||
export function isScandicNativeMembership(
|
||||
membership: Membership
|
||||
): membership is NativeFriendsMembership {
|
||||
return membership.type === scandicMembershipTypes.SCANDIC_NATIVE
|
||||
}
|
||||
|
||||
export type FriendsMembership = Omit<
|
||||
NonNullable<Membership>,
|
||||
"membershipLevel" | "nextLevel"
|
||||
> & {
|
||||
membershipLevel: MembershipLevel
|
||||
nextLevel: MembershipLevel
|
||||
}
|
||||
export function getFriendsMembership(userLoyalty: UserLoyalty) {
|
||||
const { memberships, ...loyalty } = userLoyalty
|
||||
|
||||
export function getEurobonusMembership(memberships: Memberships) {
|
||||
return memberships?.find(
|
||||
(membership) =>
|
||||
membership.membershipType.toLowerCase() === scandicMemberships.sas_eb
|
||||
)
|
||||
}
|
||||
const friendsMembership = memberships.find(isScandicNativeMembership)
|
||||
|
||||
export function getMembershipCards(
|
||||
memberships: z.infer<typeof getMembershipCardsSchema>
|
||||
) {
|
||||
return memberships.filter(function (membership) {
|
||||
return (
|
||||
membership.membershipType.toLowerCase() !== scandicMemberships.guestpr &&
|
||||
membership.membershipType.toLowerCase() !==
|
||||
scandicMemberships.scandicfriends
|
||||
if (!friendsMembership) return undefined
|
||||
|
||||
const pointExpiration = loyalty.pointExpirations
|
||||
.sort(
|
||||
(a, b) => new Date(a.expires).getTime() - new Date(b.expires).getTime()
|
||||
)
|
||||
})
|
||||
.at(0)
|
||||
|
||||
// Map to the same format that was used with Profile V1 to avoid larger changes for now.
|
||||
const result = {
|
||||
membershipType: friendsMembership.type,
|
||||
membershipNumber: friendsMembership.membershipNumber,
|
||||
membershipLevel: loyalty.tier,
|
||||
nextLevel: friendsMembership.nextTier,
|
||||
currentPoints: loyalty.points.spendable,
|
||||
expirationDate: loyalty.tierExpires,
|
||||
nightsToTopTier: friendsMembership.nightsToTopTier,
|
||||
pointsRequiredToNextlevel: friendsMembership.pointsToNextTier,
|
||||
tierExpirationDate: loyalty.tierExpires,
|
||||
pointsExpiryDate: pointExpiration?.expires,
|
||||
pointsToExpire: pointExpiration?.points,
|
||||
memberSince: friendsMembership.memberSince,
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function isEurobonusMembership(
|
||||
membership: Membership
|
||||
): membership is EurobonusMembership {
|
||||
return membership.type === scandicMembershipTypes.SAS_EB
|
||||
}
|
||||
|
||||
export function getEurobonusMembership(loyalty: UserLoyalty) {
|
||||
return loyalty.memberships?.find(isEurobonusMembership)
|
||||
}
|
||||
|
||||
export function getMembershipCards(userLoyalty: UserLoyalty) {
|
||||
return userLoyalty.memberships
|
||||
.filter(
|
||||
(membership) => membership.type !== scandicMembershipTypes.SCANDIC_NATIVE
|
||||
)
|
||||
.map((membership) => ({
|
||||
currentPoints: 0, // We only have points for Friends so we can't set this for now
|
||||
expirationDate: membership.tierExpires,
|
||||
membershipNumber: membership.membershipNumber,
|
||||
membershipType: membership.type,
|
||||
memberSince: membership.memberSince,
|
||||
}))
|
||||
}
|
||||
|
||||
export function isHighestMembership(
|
||||
|
||||
Reference in New Issue
Block a user