From ac53f128af80d9ea7338c52f085280fec246d3b5 Mon Sep 17 00:00:00 2001 From: "Chuma Mcphoy (We Ahead)" Date: Wed, 17 Dec 2025 10:45:30 +0000 Subject: [PATCH] Merged in feat/LOY-423-Nights-Stayed-Progress-for-L6-Members (pull request #3360) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat(LOY-423): Add progress bar for L6 members showing nights stayed * feat(LOY-423): Add progress bar for L6 members showing nights stayed * chore(LOY-423): shorten css selector Approved-by: Matilda Landström --- .../Previous/L6Progress/ProgressSection.tsx | 72 ++++++++++++++ .../Stays/Previous/L6Progress/index.tsx | 82 ++++++++++++++++ .../Previous/L6Progress/l6Progress.module.css | 95 +++++++++++++++++++ .../DynamicContent/Stays/Previous/index.tsx | 2 + .../LevelProgressModal/index.tsx | 10 +- apps/scandic-web/utils/getTierStartDate.ts | 14 +++ 6 files changed, 267 insertions(+), 8 deletions(-) create mode 100644 apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/L6Progress/ProgressSection.tsx create mode 100644 apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/L6Progress/index.tsx create mode 100644 apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/L6Progress/l6Progress.module.css create mode 100644 apps/scandic-web/utils/getTierStartDate.ts diff --git a/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/L6Progress/ProgressSection.tsx b/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/L6Progress/ProgressSection.tsx new file mode 100644 index 000000000..e6ac31066 --- /dev/null +++ b/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/L6Progress/ProgressSection.tsx @@ -0,0 +1,72 @@ +"use client" + +import { useIntl } from "react-intl" + +import { Divider } from "@scandic-hotels/design-system/Divider" +import { Progress } from "@scandic-hotels/design-system/Progress" +import { Typography } from "@scandic-hotels/design-system/Typography" + +import styles from "./l6Progress.module.css" + +interface ProgressSectionProps { + nightsStayed: number + progressValue: number + maxNights: number + bestFriendLabel: string +} + +export function ProgressSection({ + nightsStayed, + progressValue, + maxNights, + bestFriendLabel, +}: ProgressSectionProps) { + const intl = useIntl() + + return ( +
+ + + {intl.formatMessage({ + id: "previousStays.nightsStayed", + defaultMessage: "Nights stayed", + })} + + + + + + + {nightsStayed} + + + + + + {maxNights} + + + + + + {bestFriendLabel} + +
+ ) +} diff --git a/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/L6Progress/index.tsx b/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/L6Progress/index.tsx new file mode 100644 index 000000000..8272d5bee --- /dev/null +++ b/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/L6Progress/index.tsx @@ -0,0 +1,82 @@ +import { MembershipLevelEnum } from "@scandic-hotels/common/constants/membershipLevels" +import { dt } from "@scandic-hotels/common/dt" +import { Typography } from "@scandic-hotels/design-system/Typography" + +import { TIER_TO_FRIEND_MAP } from "@/constants/membershipLevels" +import { getProfile } from "@/lib/trpc/memoizedRequests" + +import { getIntl } from "@/i18n" +import { getLang } from "@/i18n/serverContext" +import { getTierStartDate } from "@/utils/getTierStartDate" + +import { ProgressSection } from "./ProgressSection" + +import styles from "./l6Progress.module.css" + +const MAX_NIGHTS = 100 + +export async function L6Progress() { + const user = await getProfile() + const intl = await getIntl() + const lang = await getLang() + + if ( + !user || + "error" in user || + user?.membership?.membershipLevel !== MembershipLevelEnum.L6 + ) { + return null + } + + const nightsToTopTier = user?.membership?.nightsToTopTier + + if (nightsToTopTier == null) { + return null + } + + const nightsStayed = MAX_NIGHTS - nightsToTopTier + // Cap progress at 100 to prevent overflow. + const progressValue = Math.min(nightsStayed, MAX_NIGHTS) + + const tierStartDate = getTierStartDate(user.membership.tierExpirationDate) + const formattedStartDate = tierStartDate + ? dt(tierStartDate).locale(lang).format("D MMMM YYYY") + : null + + const bestFriendLabel = TIER_TO_FRIEND_MAP[MembershipLevelEnum.L7] + + return ( +
+
+ +

+ {intl.formatMessage({ + id: "previousStays.totalStays", + defaultMessage: "Total stays", + })} +

+
+ {formattedStartDate && ( + +

+ {intl.formatMessage( + { + id: "previousStays.sinceDate", + defaultMessage: "Since {date}", + }, + { date: formattedStartDate } + )} +

+
+ )} +
+ + +
+ ) +} diff --git a/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/L6Progress/l6Progress.module.css b/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/L6Progress/l6Progress.module.css new file mode 100644 index 000000000..87c328410 --- /dev/null +++ b/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/L6Progress/l6Progress.module.css @@ -0,0 +1,95 @@ +.l6Progress { + border-radius: var(--Corner-radius-md); + border: 1px solid var(--Border-Divider-Default); + background: var(--Surface-Primary-Default); + padding: var(--Space-x3); + display: flex; + flex-direction: column; + gap: var(--Space-x2); +} + +.header { + display: flex; + flex-direction: column; + gap: var(--Space-x05); +} + +.title, +.since { + color: var(--Text-Secondary); +} + +.progressSection { + display: grid; + grid-template-columns: min-content 1fr auto; + gap: var(--Space-x05); + align-items: center; + grid-template-areas: + "startLabel . endLabel" + "startValue bar endValue"; +} + +.startLabelText { + grid-area: startLabel; + color: var(--Text-Secondary); +} + +.endLabelText { + grid-area: endLabel; + text-align: right; + color: var(--Text-Secondary); +} + +.startValue { + grid-area: startValue; + color: var(--Surface-Brand-Primary-1-OnSurface-Accent); + padding-right: var(--Space-x2); +} + +.progressBar { + grid-area: bar; +} + +.endValue { + grid-area: endValue; + text-align: right; + color: var(--Surface-Brand-Primary-1-OnSurface-Accent); + padding-left: var(--Space-x2); +} + +.startDivider, +.endDivider { + display: none; +} + +@media screen and (min-width: 768px) { + .l6Progress { + padding: var(--Space-x3) var(--Space-x4); + } + + .header { + flex-direction: row; + align-items: baseline; + gap: var(--Space-x1); + } + + .progressSection { + grid-template-columns: auto auto auto 1fr auto auto auto; + gap: var(--Space-x2); + grid-template-areas: "startLabel startDiv startValue bar endValue endDiv endLabel"; + } + + .startDivider { + grid-area: startDiv; + display: block; + } + + .endDivider { + grid-area: endDiv; + display: block; + } + + .endValue .endLabelText { + text-align: left; + } +} diff --git a/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/index.tsx b/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/index.tsx index b37d843dc..4c64fd77e 100644 --- a/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/index.tsx +++ b/apps/scandic-web/components/Blocks/DynamicContent/Stays/Previous/index.tsx @@ -8,6 +8,7 @@ import SectionLink from "@/components/Section/Link" import { Cards } from "./Cards" import { INITIAL_STAYS_FETCH_LIMIT } from "./data" +import { L6Progress } from "./L6Progress" import { ClientPreviousStays } from "./OldClient" import styles from "./previous.module.css" @@ -35,6 +36,7 @@ export default async function PreviousStays({ + diff --git a/apps/scandic-web/components/MyPages/LevelProgressCard/LevelProgressModal/index.tsx b/apps/scandic-web/components/MyPages/LevelProgressCard/LevelProgressModal/index.tsx index 3550251ed..767377f11 100644 --- a/apps/scandic-web/components/MyPages/LevelProgressCard/LevelProgressModal/index.tsx +++ b/apps/scandic-web/components/MyPages/LevelProgressCard/LevelProgressModal/index.tsx @@ -13,6 +13,7 @@ import { Typography } from "@scandic-hotels/design-system/Typography" import { compareAllLevels, faq } from "@/constants/webHrefs" import useLang from "@/hooks/useLang" +import { getTierStartDate } from "@/utils/getTierStartDate" import styles from "./LevelProgressModal.module.css" @@ -28,14 +29,7 @@ export default function LevelProgressModal({ const intl = useIntl() const lang = useLang() - // calculate tierStarts by tierExpires - let tierStarts: string | null = null - - if (tierExpires) { - const date = new Date(tierExpires) - date.setFullYear(date.getFullYear() - 1) - tierStarts = date.toISOString().split("T")[0] - } + const tierStarts = getTierStartDate(tierExpires) return (