Merged in feat/sw-1291-show-sas-membership-data (pull request #1503)
Show SAS membership data in Linked Accounts * Rip out old styling * Desktop version of new linked accounts design * Use new design system tokens * Refactor SASLinkedAccount to handle all states * Improve small screen styling * Add intl etc * Skeletons * Tiny fixes * Add i18n keys to all languages Approved-by: Linus Flood
This commit is contained in:
@@ -1,20 +1,30 @@
|
||||
import { Suspense } from "react"
|
||||
import { cx } from "class-variance-authority"
|
||||
import { type ReactNode, Suspense } from "react"
|
||||
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import { TIER_TO_FRIEND_MAP } from "@/constants/membershipLevels"
|
||||
import { env } from "@/env/server"
|
||||
import { getProfile } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import { DiamondIcon, InfoCircleIcon, LinkIcon } from "@/components/Icons"
|
||||
import SectionContainer from "@/components/Section/Container"
|
||||
import SectionHeader from "@/components/Section/Header"
|
||||
import SectionLink from "@/components/Section/Link"
|
||||
import { timeout } from "@/utils/timeout"
|
||||
import SkeletonShimmer from "@/components/SkeletonShimmer"
|
||||
import { getIntl } from "@/i18n"
|
||||
import {
|
||||
getEurobonusMembership,
|
||||
getFriendsMembership,
|
||||
scandicMemberships,
|
||||
} from "@/utils/user"
|
||||
|
||||
import { TierLevelCard, TierLevelCardSkeleton } from "./Card/TierLevelCard"
|
||||
import { LevelUpgradeButton } from "./LevelUpgradeButton"
|
||||
import { UnlinkSAS } from "./UnlinkSAS"
|
||||
|
||||
import styles from "./linkedAccounts.module.css"
|
||||
|
||||
import type { Membership } from "@/types/user"
|
||||
|
||||
type Props = {
|
||||
title?: string
|
||||
link?: { href: string; text: string }
|
||||
@@ -30,65 +40,271 @@ export default async function SASLinkedAccount({
|
||||
return null
|
||||
}
|
||||
|
||||
const intl = await getIntl()
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.container}>
|
||||
<SectionContainer>
|
||||
<SectionHeader link={link} preamble={subtitle} title={title} />
|
||||
<SectionLink link={link} variant="mobile" />
|
||||
<section className={styles.cardsContainer}>
|
||||
<Suspense fallback={<TierLevelCardsSkeleton />}>
|
||||
<TierLevelCards />
|
||||
<Suspense fallback={<MatchedAccountInfoSkeleton />}>
|
||||
<MatchedAccountInfo />
|
||||
</Suspense>
|
||||
</section>
|
||||
</SectionContainer>
|
||||
<div className={styles.mutationSection}>
|
||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||
<p className={styles.caption}>
|
||||
<InfoCircleIcon height={20} width={20} />
|
||||
{intl.formatMessage({
|
||||
id: "Changes in tier match can take up to 24 hours to be displayed.",
|
||||
})}
|
||||
</p>
|
||||
</Typography>
|
||||
<UnlinkSAS />
|
||||
<LevelUpgradeButton />
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function TierLevelCardsSkeleton() {
|
||||
return (
|
||||
<>
|
||||
<TierLevelCardSkeleton bonusSystem={"scandic"} />
|
||||
<TierLevelCardSkeleton bonusSystem={"sas"} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
async function TierLevelCards() {
|
||||
console.log("[SAS] Fetching tier level cards")
|
||||
await timeout(2_000)
|
||||
console.log("[SAS] AFTER Fetching tier level cards")
|
||||
|
||||
async function MatchedAccountInfo() {
|
||||
const user = await getProfile()
|
||||
|
||||
if (!user || "error" in user) {
|
||||
return null
|
||||
}
|
||||
|
||||
const sasPoints = 250_000
|
||||
const sfPoints = user.membership?.currentPoints || 0
|
||||
const sfLevelName =
|
||||
TIER_TO_FRIEND_MAP[user.membership?.membershipLevel ?? "L1"]
|
||||
const intl = await getIntl()
|
||||
|
||||
const eurobonusMembership = getEurobonusMembership(user.memberships)
|
||||
const friendsMembership = user.membership
|
||||
if (!eurobonusMembership || !friendsMembership) {
|
||||
return null
|
||||
}
|
||||
|
||||
const sasLevelName = eurobonusMembership.membershipLevel || "-"
|
||||
const sasMembershipNumber = eurobonusMembership.membershipNumber
|
||||
const sasTierExpirationDate = eurobonusMembership.tierExpirationDate
|
||||
|
||||
const scandicLevelName = TIER_TO_FRIEND_MAP[friendsMembership.membershipLevel]
|
||||
const scandicExpirationDate = friendsMembership.tierExpirationDate
|
||||
|
||||
const matchState = calculateMatchState(user.memberships)
|
||||
|
||||
return (
|
||||
<>
|
||||
<TierLevelCard
|
||||
points={sfPoints}
|
||||
tier={sfLevelName}
|
||||
boostState="boostedInThisSystem"
|
||||
bonusSystem={"scandic"}
|
||||
/>
|
||||
<TierLevelCard
|
||||
points={sasPoints}
|
||||
tier="Silver"
|
||||
boostState="boostedInOtherSystem"
|
||||
bonusSystem={"sas"}
|
||||
boostExpiration={new Date("2022-12-31")}
|
||||
boostedTier="Gold"
|
||||
/>
|
||||
</>
|
||||
<section className={styles.matchedAccountSection}>
|
||||
<div className={styles.accountDetails}>
|
||||
<div className={styles.stack}>
|
||||
<Label>{intl.formatMessage({ id: "Linked account" })}</Label>
|
||||
<Typography variant="Body/Paragraph/mdBold">
|
||||
<p>SAS EuroBonus</p>
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={styles.stack}>
|
||||
<Label>{intl.formatMessage({ id: "Tier status" })}</Label>
|
||||
<Typography variant="Body/Paragraph/mdBold">
|
||||
<p>{sasLevelName}</p>
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={cx(styles.stack, styles.accountMemberNumber)}>
|
||||
<Label>{intl.formatMessage({ id: "Member number" })}</Label>
|
||||
<Typography variant="Body/Paragraph/mdBold">
|
||||
<p className={styles.textRight}>EB{sasMembershipNumber}</p>
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.tierMatchStatus}>
|
||||
<TierMatchMessage
|
||||
matchState={matchState}
|
||||
scandicLevelName={scandicLevelName}
|
||||
sasLevelName={sasLevelName}
|
||||
/>
|
||||
<TierMatchExpiration
|
||||
matchState={matchState}
|
||||
sasExpirationDate={sasTierExpirationDate}
|
||||
scandicExpirationDate={scandicExpirationDate}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
async function MatchedAccountInfoSkeleton() {
|
||||
const intl = await getIntl()
|
||||
|
||||
return (
|
||||
<section className={styles.matchedAccountSection}>
|
||||
<div className={styles.accountDetails}>
|
||||
<div className={styles.stack}>
|
||||
<Label>{intl.formatMessage({ id: "Linked account" })}</Label>
|
||||
<Typography variant="Body/Paragraph/mdBold">
|
||||
<p>SAS EuroBonus</p>
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={styles.stack}>
|
||||
<Label>{intl.formatMessage({ id: "Tier status" })}</Label>
|
||||
<SkeletonShimmer width="6ch" height="24px" />
|
||||
</div>
|
||||
<div className={cx(styles.stack, styles.accountMemberNumber)}>
|
||||
<Label>{intl.formatMessage({ id: "Member number" })}</Label>
|
||||
<SkeletonShimmer width="10ch" height="24px" />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.tierMatchStatus}>
|
||||
<TierMatchMessageSkeleton />
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
type TierMatchMessageProps = {
|
||||
matchState: MatchState
|
||||
scandicLevelName: string
|
||||
sasLevelName: string
|
||||
}
|
||||
async function TierMatchMessage({
|
||||
matchState,
|
||||
sasLevelName,
|
||||
scandicLevelName,
|
||||
}: TierMatchMessageProps) {
|
||||
const intl = await getIntl()
|
||||
|
||||
const messageValues = {
|
||||
sasLevelName: sasLevelName,
|
||||
scandicLevelName: scandicLevelName,
|
||||
sasMark: (text: ReactNode) => (
|
||||
<Typography variant="Body/Paragraph/mdBold">
|
||||
<span className={styles.sasMark}>{text}</span>
|
||||
</Typography>
|
||||
),
|
||||
scandicMark: (text: ReactNode) => (
|
||||
<Typography variant="Body/Paragraph/mdBold">
|
||||
<span className={styles.scandicMark}>{text}</span>
|
||||
</Typography>
|
||||
),
|
||||
}
|
||||
|
||||
const messageMap: Record<MatchState, ReactNode> = {
|
||||
boostedBySAS: intl.formatMessage(
|
||||
{
|
||||
id: "<sasMark>SAS {sasLevelName}</sasMark> has upgraded your Scandic Friends level to <scandicMark>{scandicLevelName}</scandicMark>.",
|
||||
},
|
||||
messageValues
|
||||
),
|
||||
boostedByScandic: intl.formatMessage(
|
||||
{
|
||||
id: "<scandicMark>Scandic {scandicLevelName}</scandicMark> has upgraded you to <sasMark>{sasLevelName}</sasMark>.",
|
||||
},
|
||||
messageValues
|
||||
),
|
||||
noBoost: intl.formatMessage(
|
||||
{
|
||||
id: "<sasMark>SAS {sasLevelName}</sasMark> and <scandicMark>{scandicLevelName}</scandicMark> are equally matched tiers. Level up one of your memberships for a chance of an upgrade!",
|
||||
},
|
||||
messageValues
|
||||
),
|
||||
}
|
||||
|
||||
const iconMap: Record<MatchState, ReactNode> = {
|
||||
boostedBySAS: (
|
||||
<DiamondIcon height={20} width={20} color="uiTextMediumContrast" />
|
||||
),
|
||||
boostedByScandic: (
|
||||
<DiamondIcon height={20} width={20} color="uiTextMediumContrast" />
|
||||
),
|
||||
noBoost: <LinkIcon height={20} width={20} color="uiTextMediumContrast" />,
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.stack}>
|
||||
<Label>{intl.formatMessage({ id: "Tier match status" })}</Label>
|
||||
<div className={styles.tierMatchText}>
|
||||
<div className={styles.iconWrapper}>{iconMap[matchState]}</div>
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p>{messageMap[matchState]}</p>
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
async function TierMatchMessageSkeleton() {
|
||||
const intl = await getIntl()
|
||||
|
||||
return (
|
||||
<div className={styles.stack}>
|
||||
<Label>{intl.formatMessage({ id: "Tier match status" })}</Label>
|
||||
<div className={styles.tierMatchText}>
|
||||
<SkeletonShimmer width="250px" height="24px" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type TierMatchExpirationProps = {
|
||||
matchState: MatchState
|
||||
sasExpirationDate: string | undefined
|
||||
scandicExpirationDate: string | undefined
|
||||
}
|
||||
async function TierMatchExpiration({
|
||||
matchState,
|
||||
sasExpirationDate,
|
||||
scandicExpirationDate,
|
||||
}: TierMatchExpirationProps) {
|
||||
if (matchState === "noBoost") {
|
||||
return null
|
||||
}
|
||||
|
||||
const intl = await getIntl()
|
||||
|
||||
return (
|
||||
<div className={styles.stack}>
|
||||
<Label>{intl.formatMessage({ id: "Upgrade valid until" })}</Label>
|
||||
<Typography variant="Body/Paragraph/mdBold">
|
||||
<p>
|
||||
{matchState === "boostedBySAS"
|
||||
? scandicExpirationDate
|
||||
: sasExpirationDate}
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Label({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<Typography variant="Tag/sm">
|
||||
<p className={styles.label}>{children}</p>
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
return "noBoost"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user