From 914871607da14f0cfde5c8bfd032ace0b1723808 Mon Sep 17 00:00:00 2001 From: "Chuma Mcphoy (We Ahead)" Date: Mon, 1 Sep 2025 13:42:50 +0000 Subject: [PATCH] Merged in feat/LOY-315-Membership-Status-Card (pull request #2712) Feat(LOY-315): Membership Overview Card * fix(LOY-315): new divider variants * feat(LOY-315): Add MembershipOverviewCard * refactor(LOY-315): abstract sasbooststatus * fix(LOY-315): correct space vars * fix(LOY-315): date formatting fixes Approved-by: Erik Tiekstra --- .../MembershipOverviewCard/SasBoostStatus.tsx | 44 ++++++++++ .../Overview/MembershipOverviewCard/index.tsx | 85 +++++++++++++++++++ .../membershipOverviewCard.module.css | 41 +++++++++ .../Blocks/DynamicContent/Overview/index.tsx | 18 +++- .../Overview/overview.module.css | 22 +++-- .../lib/components/Divider/divider.module.css | 8 ++ .../lib/components/Divider/variants.ts | 3 + 7 files changed, 205 insertions(+), 16 deletions(-) create mode 100644 apps/scandic-web/components/Blocks/DynamicContent/Overview/MembershipOverviewCard/SasBoostStatus.tsx create mode 100644 apps/scandic-web/components/Blocks/DynamicContent/Overview/MembershipOverviewCard/index.tsx create mode 100644 apps/scandic-web/components/Blocks/DynamicContent/Overview/MembershipOverviewCard/membershipOverviewCard.module.css diff --git a/apps/scandic-web/components/Blocks/DynamicContent/Overview/MembershipOverviewCard/SasBoostStatus.tsx b/apps/scandic-web/components/Blocks/DynamicContent/Overview/MembershipOverviewCard/SasBoostStatus.tsx new file mode 100644 index 000000000..4d3b6c017 --- /dev/null +++ b/apps/scandic-web/components/Blocks/DynamicContent/Overview/MembershipOverviewCard/SasBoostStatus.tsx @@ -0,0 +1,44 @@ +import { dt } from "@scandic-hotels/common/dt" +import { Divider } from "@scandic-hotels/design-system/Divider" +import { Typography } from "@scandic-hotels/design-system/Typography" + +import { getLang } from "@/i18n/serverContext" + +import styles from "./membershipOverviewCard.module.css" + +import type { EurobonusMembership } from "@scandic-hotels/trpc/types/user" +import type { IntlShape } from "react-intl" + +interface SasBoostStatusProps { + sasMembership: EurobonusMembership + intl: IntlShape +} + +export default async function SasBoostStatus({ + sasMembership, + intl, +}: SasBoostStatusProps) { + const lang = await getLang() + + if (!sasMembership.boostedTierExpires) return null + + const sasBoostExpiryText = intl.formatMessage( + { + defaultMessage: "Boosted by SAS until {date}", + }, + { + date: dt(sasMembership.boostedTierExpires) + .locale(lang) + .format("D MMM YYYY"), + } + ) + + return ( + <> + + + {sasBoostExpiryText} + + + ) +} diff --git a/apps/scandic-web/components/Blocks/DynamicContent/Overview/MembershipOverviewCard/index.tsx b/apps/scandic-web/components/Blocks/DynamicContent/Overview/MembershipOverviewCard/index.tsx new file mode 100644 index 000000000..c473d261d --- /dev/null +++ b/apps/scandic-web/components/Blocks/DynamicContent/Overview/MembershipOverviewCard/index.tsx @@ -0,0 +1,85 @@ +import { MembershipLevelEnum } from "@scandic-hotels/common/constants/membershipLevels" +import { Divider } from "@scandic-hotels/design-system/Divider" +import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" +import { Typography } from "@scandic-hotels/design-system/Typography" +import { getEurobonusMembership } from "@scandic-hotels/trpc/routers/user/helpers" + +import { membershipLevels } from "@/constants/membershipLevels" + +import MembershipLevelIcon from "@/components/Levels/Icon" +import { getIntl } from "@/i18n" + +import SasBoostStatus from "./SasBoostStatus" + +import styles from "./membershipOverviewCard.module.css" + +import type { User } from "@scandic-hotels/trpc/types/user" + +interface MembershipOverviewCardProps { + user: User +} + +export default async function MembershipOverviewCard({ + user, +}: MembershipOverviewCardProps) { + const intl = await getIntl() + + if (!user.membership?.membershipLevel) { + return null + } + + const pointsToSpendText = + typeof user.membership.currentPoints === "number" + ? intl.formatNumber(user.membership.currentPoints) + : intl.formatMessage({ defaultMessage: "N/A" }) + + const sasMembership = user.loyalty + ? getEurobonusMembership(user.loyalty) + : null + + return ( +
+
+ {sasMembership && ( + + )} + +

+ {intl.formatMessage( + { + defaultMessage: "LEVEL {level}", + }, + { level: membershipLevels[user.membership.membershipLevel] } + )} +

+
+ {sasMembership && ( + + )} +
+ + + + + + +

+ {intl.formatMessage({ + defaultMessage: "POINTS TO SPEND", + })} +

+
+ +

{pointsToSpendText}

+
+
+ ) +} diff --git a/apps/scandic-web/components/Blocks/DynamicContent/Overview/MembershipOverviewCard/membershipOverviewCard.module.css b/apps/scandic-web/components/Blocks/DynamicContent/Overview/MembershipOverviewCard/membershipOverviewCard.module.css new file mode 100644 index 000000000..6b093862d --- /dev/null +++ b/apps/scandic-web/components/Blocks/DynamicContent/Overview/MembershipOverviewCard/membershipOverviewCard.module.css @@ -0,0 +1,41 @@ +.card { + background-color: var(--Surface-Brand-Primary-3-Default); + border-radius: var(--Corner-radius-lg); + padding: var(--Space-x3) var(--Space-x2); + position: relative; + min-height: 200px; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.membershipHeader { + display: flex; + align-items: center; + gap: var(--Space-x1); + margin-bottom: var(--Space-x05); +} + +.levelText, +.sasBoostText, +.pointsLabel { + color: var(--Text-Brand-OnPrimary-3-Heading); +} + +.levelText { + text-transform: uppercase; +} + +.divider { + margin: var(--Space-x4) 0; +} + +.pointsValue { + color: var(--Text-Brand-OnPrimary-3-Accent); +} + +@media (min-width: 1367px) { + .card { + padding: var(--Space-x3) var(--Space-x4); + } +} diff --git a/apps/scandic-web/components/Blocks/DynamicContent/Overview/index.tsx b/apps/scandic-web/components/Blocks/DynamicContent/Overview/index.tsx index 18682829b..9fff16ae3 100644 --- a/apps/scandic-web/components/Blocks/DynamicContent/Overview/index.tsx +++ b/apps/scandic-web/components/Blocks/DynamicContent/Overview/index.tsx @@ -2,7 +2,7 @@ import { Divider } from "@scandic-hotels/design-system/Divider" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import { env } from "@/env/server" -import { getProfile } from "@/lib/trpc/memoizedRequests" +import { getProfileWithExtendedPartnerData } from "@/lib/trpc/memoizedRequests" import { TeamMemberCardTrigger } from "@/components/DigitalTeamMemberCard/Trigger" import DigitalTeamMemberCard from "@/components/MyPages/DigitalTeamMemberCard" @@ -15,6 +15,7 @@ import { getIntl } from "@/i18n" import Hero from "./Friend/Hero" import MembershipNumber from "./Friend/MembershipNumber" import Friend from "./Friend" +import MembershipOverviewCard from "./MembershipOverviewCard" import Stats from "./Stats" import UserBaseInfo from "./UserBaseInfo" @@ -28,7 +29,7 @@ export default async function Overview({ title, }: AccountPageComponentProps) { const intl = await getIntl() - const user = await getProfile() + const user = await getProfileWithExtendedPartnerData() if (!user || "error" in user) { return null } @@ -58,8 +59,17 @@ export default async function Overview({ - {env.ENABLE_NEW_OVERVIEW_SECTION ? : null} - {/*TODO: Replace Hero Section Cards with New ones. */} + {env.ENABLE_NEW_OVERVIEW_SECTION ? ( + <> + +
+ + {/* LevelProgressCard will be added here in the next iteration */} +
+ + ) : null} + + {/*TODO: Replace hero section with new section above. */} diff --git a/apps/scandic-web/components/Blocks/DynamicContent/Overview/overview.module.css b/apps/scandic-web/components/Blocks/DynamicContent/Overview/overview.module.css index f64bf5954..cbb022963 100644 --- a/apps/scandic-web/components/Blocks/DynamicContent/Overview/overview.module.css +++ b/apps/scandic-web/components/Blocks/DynamicContent/Overview/overview.module.css @@ -1,5 +1,5 @@ .divider { - margin-top: var(--Spacing-x2); + margin-top: var(--Space-x2); } .teamMemberCardButton { @@ -12,17 +12,9 @@ } } -@media screen and (max-width: 767px) { - .container { - /* Full-width override styling */ - left: 50%; - margin-left: -50vw; - margin-right: -50vw; - padding: 0 var(--Spacing-x2); - position: relative; - right: 50%; - width: 100dvw; - } +.membershipCardsContainer { + display: grid; + gap: var(--Space-x2); } @media screen and (min-width: 768px) { @@ -30,3 +22,9 @@ display: none; } } + +@media screen and (min-width: 1367px) { + .membershipCardsContainer { + grid-template-columns: 1fr 1fr; + } +} diff --git a/packages/design-system/lib/components/Divider/divider.module.css b/packages/design-system/lib/components/Divider/divider.module.css index 435390806..e7808070a 100644 --- a/packages/design-system/lib/components/Divider/divider.module.css +++ b/packages/design-system/lib/components/Divider/divider.module.css @@ -29,6 +29,10 @@ background-color: var(--UI-Opacity-White-100); } +.Border-Divider-Accent { + background-color: var(--Border-Divider-Accent); +} + .Border-Divider-Subtle { background-color: var(--Border-Divider-Subtle); } @@ -37,6 +41,10 @@ background-color: var(--Border-Divider-Default); } +.Border-Divider-Brand-OnPrimary-3-Default { + background: var(--Border-Divider-Brand-OnPrimary-3-Default); +} + .Surface-Brand-Primary-1-OnSurface-Accent-Secondary { background-color: var(--Surface-Brand-Primary-1-OnSurface-Accent-Secondary); } diff --git a/packages/design-system/lib/components/Divider/variants.ts b/packages/design-system/lib/components/Divider/variants.ts index d39dba0f1..c41f4c30e 100644 --- a/packages/design-system/lib/components/Divider/variants.ts +++ b/packages/design-system/lib/components/Divider/variants.ts @@ -9,7 +9,10 @@ export const dividerVariants = cva(styles.divider, { pale: styles.pale, peach: styles.peach, white: styles.white, + 'Border/Divider/Accent': styles['Border-Divider-Accent'], 'Border/Divider/Default': styles['Border-Divider-Default'], + 'Border/Divider/Brand/OnPrimary 3/Default': + styles['Border-Divider-Brand-OnPrimary-3-Default'], 'Border/Divider/Subtle': styles['Border-Divider-Subtle'], 'Surface/Brand/Primary 1/OnSurface/Accent Secondary': styles['Surface-Brand-Primary-1-OnSurface-Accent-Secondary'],