diff --git a/apps/scandic-web/components/Blocks/DynamicContent/Points/Overview/index.tsx b/apps/scandic-web/components/Blocks/DynamicContent/Points/Overview/index.tsx
index 3b6d127d6..1e0ac081a 100644
--- a/apps/scandic-web/components/Blocks/DynamicContent/Points/Overview/index.tsx
+++ b/apps/scandic-web/components/Blocks/DynamicContent/Points/Overview/index.tsx
@@ -12,6 +12,7 @@ import Friend from "../../Overview/Friend"
import Hero from "../../Overview/Friend/Hero"
import MembershipNumber from "../../Overview/Friend/MembershipNumber"
import Stats from "../../Overview/Stats"
+import PointsToSpendCard from "../PointsToSpendCard"
import styles from "./overview.module.css"
@@ -36,23 +37,23 @@ export default async function PointsOverview({
headingAs={"h3"}
headingLevel={"h1"}
/>
- {env.ENABLE_NEW_OVERVIEW_SECTION && (
+ {env.ENABLE_NEW_OVERVIEW_SECTION ? (
- {/*TODO: Add PointsToSpendCard */}
+
+ ) : (
+
+
+
+
+
+
+
)}
- {/*TODO: Once the PointsToSpendCard is added hide the Hero Section under the flag above. */}
-
-
-
-
-
-
-
)
diff --git a/apps/scandic-web/components/Blocks/DynamicContent/Points/PointsToSpendCard/ExpiringPointsSeeAllButton.tsx b/apps/scandic-web/components/Blocks/DynamicContent/Points/PointsToSpendCard/ExpiringPointsSeeAllButton.tsx
new file mode 100644
index 000000000..c51eed574
--- /dev/null
+++ b/apps/scandic-web/components/Blocks/DynamicContent/Points/PointsToSpendCard/ExpiringPointsSeeAllButton.tsx
@@ -0,0 +1,53 @@
+"use client"
+
+import { DialogTrigger } from "react-aria-components"
+import { useIntl } from "react-intl"
+
+import { Button } from "@scandic-hotels/design-system/Button"
+import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
+import SidePeekSelfControlled from "@scandic-hotels/design-system/SidePeekSelfControlled"
+import { Typography } from "@scandic-hotels/design-system/Typography"
+
+import ExpiringPointsTable from "../ExpiringPoints/ExpiringPointsTable"
+
+import styles from "./PointsToSpendCard.module.css"
+
+interface ExpiringPointsSeeAllButtonProps {
+ expiringPoints: number
+ expiryDate: string
+}
+
+export default function ExpiringPointsSeeAllButton({
+ expiringPoints,
+ expiryDate,
+}: ExpiringPointsSeeAllButtonProps) {
+ const intl = useIntl()
+
+ return (
+
+
+ {intl.formatMessage({ defaultMessage: "See all" })}
+
+
+
+
+
+
+ {intl.formatMessage({
+ defaultMessage:
+ "Points expire three years after they are earned, on the last day of that month. Expiring points do not affect your level.",
+ })}
+
+
+ {/* TODO: The table will be rebuilt as part of the My Pages Optimisations 3 Epic. */}
+
+
+
+
+ )
+}
diff --git a/apps/scandic-web/components/Blocks/DynamicContent/Points/PointsToSpendCard/PointsToSpendCard.module.css b/apps/scandic-web/components/Blocks/DynamicContent/Points/PointsToSpendCard/PointsToSpendCard.module.css
new file mode 100644
index 000000000..52adf7422
--- /dev/null
+++ b/apps/scandic-web/components/Blocks/DynamicContent/Points/PointsToSpendCard/PointsToSpendCard.module.css
@@ -0,0 +1,113 @@
+.card {
+ background: var(--Surface-Brand-Primary-1-Default);
+ border-radius: var(--Corner-radius-lg);
+ padding: var(--Space-x3) var(--Space-x2);
+ position: relative;
+ min-height: 200px;
+ display: flex;
+ flex-direction: column;
+ gap: var(--Space-x3);
+}
+
+.content {
+ display: grid;
+ place-items: center;
+ gap: var(--Space-x3);
+}
+
+.textContent {
+ display: grid;
+ gap: var(--Space-x1);
+ align-self: center;
+ justify-items: center;
+}
+
+.title {
+ color: var(--Text-Brand-OnPrimary-1-Heading);
+}
+
+.pointsContainer {
+ display: flex;
+ align-items: center;
+ gap: var(--Space-x1);
+}
+
+.pointsValue {
+ color: var(--Text-Accent-Primary);
+}
+
+.pointsLabel {
+ color: var(--Text-Brand-OnPrimary-1-Heading);
+}
+
+.description {
+ margin-top: auto;
+ padding: var(--Space-x2);
+ border-radius: var(--Corner-radius-md);
+ background: var(--Surface-Primary-Default);
+}
+
+.descriptionText {
+ color: var(--Text-Brand-OnPrimary-1-Body);
+ line-height: 1.5;
+ margin: 0;
+}
+
+.expiringPointsCard {
+ display: flex;
+ padding: var(--Space-x2);
+ flex-direction: column;
+ justify-content: center;
+ align-items: flex-start;
+ gap: var(--Space-x1);
+ border-radius: var(--Corner-radius-md);
+ background: var(--Surface-Primary-Default);
+}
+
+.expiryDate {
+ color: var(--Text-Heading);
+}
+
+.pointsRow {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ width: 100%;
+}
+
+.expiringPoints {
+ margin: 0;
+}
+
+.sidePeekContent {
+ display: flex;
+ flex-direction: column;
+ gap: var(--Space-x2);
+}
+
+@media screen and (min-width: 768px) {
+ .content {
+ grid-template-columns: auto 1fr;
+ place-items: start;
+ }
+
+ .textContent {
+ justify-items: start;
+ }
+
+ .expiringPointsCard {
+ padding: var(--Space-x2) var(--Space-x4);
+ }
+
+ .description {
+ padding: 0;
+ border-radius: 0;
+ background: transparent;
+ }
+}
+
+@media screen and (min-width: 1367px) {
+ .card {
+ padding: var(--Space-x3) var(--Space-x4);
+ }
+}
diff --git a/apps/scandic-web/components/Blocks/DynamicContent/Points/PointsToSpendCard/index.tsx b/apps/scandic-web/components/Blocks/DynamicContent/Points/PointsToSpendCard/index.tsx
new file mode 100644
index 000000000..f917f6939
--- /dev/null
+++ b/apps/scandic-web/components/Blocks/DynamicContent/Points/PointsToSpendCard/index.tsx
@@ -0,0 +1,135 @@
+import ButtonLink from "@scandic-hotels/design-system/ButtonLink"
+import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
+import MoneyHandEllipsisIcon from "@scandic-hotels/design-system/Icons/MoneyHandEllipsisIcon"
+import { Typography } from "@scandic-hotels/design-system/Typography"
+
+import { spendPoints } from "@/constants/webHrefs"
+
+import { getIntl } from "@/i18n"
+import { getLang } from "@/i18n/serverContext"
+
+import ExpiringPointsSeeAllButton from "./ExpiringPointsSeeAllButton"
+import { getExpiryLabel } from "./utils"
+
+import styles from "./PointsToSpendCard.module.css"
+
+import type { User } from "@scandic-hotels/trpc/types/user"
+
+interface PointsToSpendCardProps {
+ user: User
+}
+
+export default async function PointsToSpendCard({
+ user,
+}: PointsToSpendCardProps) {
+ const intl = await getIntl()
+ const lang = await getLang()
+
+ if (!user.membership) {
+ return null
+ }
+
+ const spendablePoints = user.membership.currentPoints
+ const hasPointsToSpend = spendablePoints > 0
+
+ const expiringPoints = user.membership.pointsToExpire
+ const expiryDate = user.membership.pointsExpiryDate
+ const expiryDateText = getExpiryLabel(expiryDate, intl, lang)
+
+ return (
+
+
+
+
+
+
+ {intl.formatMessage({
+ defaultMessage: "Points to spend",
+ })}
+
+
+
+
+
+ {intl.formatNumber(spendablePoints)}
+
+
+ {!hasPointsToSpend && (
+
+
+ {intl.formatMessage({
+ defaultMessage: "POINTS",
+ })}
+
+
+ )}
+
+ {hasPointsToSpend && (
+
+ {intl.formatMessage({
+ defaultMessage: "How to spend points",
+ })}
+
+
+ )}
+
+
+ {!hasPointsToSpend && (
+
+
+
+ {intl.formatMessage({
+ defaultMessage:
+ "Earn points by staying at Scandic. Turn your points into free nights and memorable experiences.",
+ })}
+
+
+
+ )}
+ {expiringPoints && expiryDate && (
+
+
+ {expiryDateText}
+
+
+
+
+ {intl.formatMessage(
+ {
+ defaultMessage: "{expiringPoints} points expiring",
+ },
+ {
+ expiringPoints: (
+
+ {intl.formatNumber(expiringPoints)}
+
+ ),
+ }
+ )}
+
+
+
+
+
+ )}
+
+ )
+}
diff --git a/apps/scandic-web/components/Blocks/DynamicContent/Points/PointsToSpendCard/utils.ts b/apps/scandic-web/components/Blocks/DynamicContent/Points/PointsToSpendCard/utils.ts
new file mode 100644
index 000000000..146b8a94c
--- /dev/null
+++ b/apps/scandic-web/components/Blocks/DynamicContent/Points/PointsToSpendCard/utils.ts
@@ -0,0 +1,43 @@
+import { dt } from "@scandic-hotels/common/dt"
+
+import type { IntlShape } from "react-intl"
+
+export const DAYS_UNTIL_EXPIRY_WARNING = 30
+
+/**
+ * Get expiry label for points based on the expiry date.
+ * @returns The formatted expiry date text or empty string if no expiry date
+ */
+export function getExpiryLabel(
+ pointsExpiryDate: string | undefined,
+ intl: IntlShape,
+ lang: string
+): string {
+ if (!pointsExpiryDate) {
+ return ""
+ }
+
+ const now = dt()
+ const expiryDate = dt(pointsExpiryDate).locale(lang)
+ const daysUntilExpiry = expiryDate.diff(now, "days")
+
+ if (daysUntilExpiry <= DAYS_UNTIL_EXPIRY_WARNING) {
+ return intl.formatMessage(
+ {
+ defaultMessage: "In {days} days",
+ },
+ {
+ days: daysUntilExpiry,
+ }
+ )
+ } else {
+ return intl.formatMessage(
+ {
+ defaultMessage: "on {expiryDate}",
+ },
+ {
+ expiryDate: expiryDate.format("DD MMM YYYY"),
+ }
+ )
+ }
+}
diff --git a/apps/scandic-web/constants/webHrefs.ts b/apps/scandic-web/constants/webHrefs.ts
index 1185f0563..06610db53 100644
--- a/apps/scandic-web/constants/webHrefs.ts
+++ b/apps/scandic-web/constants/webHrefs.ts
@@ -28,3 +28,12 @@ export const partners: LangRoute = {
no: `/${Lang.no}/scandic-friends/partnere`,
sv: `/${Lang.sv}/scandic-friends/partners`,
}
+
+export const spendPoints: LangRoute = {
+ da: `/${Lang.da}/scandic-friends/brug-point`,
+ de: `/${Lang.de}/scandic-friends/ausgeben`,
+ en: `/${Lang.en}/scandic-friends/spend-points`,
+ fi: `/${Lang.fi}/scandic-friends/hyodynna`,
+ no: `/${Lang.no}/scandic-friends/bruk-poeng`,
+ sv: `/${Lang.sv}/scandic-friends/anvand-poang`,
+}
diff --git a/packages/design-system/lib/components/Icons/IllustrationByIconName.ts b/packages/design-system/lib/components/Icons/IllustrationByIconName.ts
index aa48e03ba..fbe7ccd0c 100644
--- a/packages/design-system/lib/components/Icons/IllustrationByIconName.ts
+++ b/packages/design-system/lib/components/Icons/IllustrationByIconName.ts
@@ -10,6 +10,7 @@ import KidsIcon from './Illustrations/Kids'
import KidsMocktailIcon from './Illustrations/KidsMocktail'
import MagicWandIcon from './Illustrations/MagicWand'
import MoneyHandIcon from './Illustrations/MoneyHand'
+import MoneyHandEllipsisIcon from './Illustrations/MoneyHandEllipsis'
import TrophyIcon from './Illustrations/Trophy'
import VoucherIcon from './Illustrations/Voucher'
@@ -25,6 +26,8 @@ export function IllustrationByIconName(iconName: IconName | null) {
return MagicWandIcon
case IconName.MoneyHand:
return MoneyHandIcon
+ case IconName.MoneyHandEllipsis:
+ return MoneyHandEllipsisIcon
case IconName.HandKey:
return HandKeyIcon
case IconName.HotelNight:
diff --git a/packages/design-system/lib/components/Icons/Illustrations/MoneyHandEllipsis.tsx b/packages/design-system/lib/components/Icons/Illustrations/MoneyHandEllipsis.tsx
new file mode 100644
index 000000000..956081420
--- /dev/null
+++ b/packages/design-system/lib/components/Icons/Illustrations/MoneyHandEllipsis.tsx
@@ -0,0 +1,22 @@
+import type { IllustrationProps } from '../icon'
+
+export default function MoneyHandEllipsisIcon(props: IllustrationProps) {
+ return (
+
+
+
+
+
+ )
+}
diff --git a/packages/design-system/lib/components/Icons/iconName.ts b/packages/design-system/lib/components/Icons/iconName.ts
index 5227efa2d..0dc4e11a7 100644
--- a/packages/design-system/lib/components/Icons/iconName.ts
+++ b/packages/design-system/lib/components/Icons/iconName.ts
@@ -125,6 +125,7 @@ export enum IconName {
Minibar = 'Minibar',
Minus = 'Minus',
MoneyHand = 'MoneyHand',
+ MoneyHandEllipsis = 'MoneyHandEllipsis',
Museum = 'Museum',
Nature = 'Nature',
Nightlife = 'Nightlife',
diff --git a/packages/design-system/package.json b/packages/design-system/package.json
index c5a589941..46c1e86fc 100644
--- a/packages/design-system/package.json
+++ b/packages/design-system/package.json
@@ -106,6 +106,7 @@
"./Icons/MinimizeIcon": "./lib/components/Icons/Customised/UI/Minimize.tsx",
"./Icons/MirrorIcon": "./lib/components/Icons/Customised/Amenities_Facilities/Mirror.tsx",
"./Icons/MoneyHandIcon": "./lib/components/Icons/Illustrations/MoneyHand.tsx",
+ "./Icons/MoneyHandEllipsisIcon": "./lib/components/Icons/Illustrations/MoneyHandEllipsis.tsx",
"./Icons/MovingBedsIcon": "./lib/components/Icons/Customised/Amenities_Facilities/MovingBeds.tsx",
"./Icons/NoBreakfastBuffetIcon": "./lib/components/Icons/Illustrations/NoBreakfastBuffet.tsx",
"./Icons/PalmTreeIcon": "./lib/components/Icons/Nucleo/Experiences/palm-tree-2.tsx",