Merged in feat/LOY-400-create-spend-points-modal (pull request #3131)
Feat/LOY-400 create spend points modal * feat(LOY-400): Added custom button to my pages overview and skeleton file to custom modal for my points. * feat(LOY-400): Added custom button to my pages overview and components for custom modal for my points. * feat(LOY-400): Changed some style and infogridcardover * feat(LOY-400):Removed custom card components and changed in infoCard: Added imagePosition top, added optional height prop. In Card: Changed Text-wrap styling, added min-width styling to buttons, added optional Icon prop, added optional height prop * feat(LOY-400):Added linkList, LinkListItem component and messageBanner component. Added granola illustration. * feat(LOY-400): Removed background in several illustrations. Added component for illustration. Fixed LinkedList and styling for UsePointsButton. * feat(LOY-400): Added modal to PointsToSpendCard and fixed UsePointsButton. * fix(LOY-400):added some styling * feat(LOY-400): Linked Modal to contentstack and fetch the data in cards with UsePointsModal for now * feat(LOY-400): changed link to aria-component, cleaned up a bit * feat(LOY-400): Changed height for larger modals in mobile, fixed zod schema for no illustration input, cleaned up * fix(LOY-400): fixed graphql after rebase * fix(LOY-400): mini fix * fix(LOY-400): fixed pr-comments * fix(LOY-400): fixed some PR-comments * fix(LOY-400): fixed a PR-comment * feat(LOY-400): added size prop to ilustration in LinkListItem to be able to use illustrations in IllustrationByIconName * fix(LOY-400): fixed pr-comments * Merged in feat/LOY-402-pre-ticked-book-reward-night-in-booking-flow (pull request #3210) Feat/LOY-402 pre ticked book reward night in booking flow * feat(LOY-402): Changed UsePointsModal structure to handle button actions in card. * feat(LOY-402): added functionality for book now button * feat(LOY-400): pr comment fix * feat(LOY-402): transformed the contentstack data * fix(LOY-402): fixed pr comments Approved-by: Chuma Mcphoy (We Ahead) Approved-by: Anton Gunnarsson Approved-by: Matilda Landström * Merged in feat/LOY-404-add-tracking-for-spend-points-modal (pull request #3229) Feat/LOY-404 add tracking for spend points modal * feat(LOY-402): Changed UsePointsModal structure to handle button actions in card. * feat(LOY-402): added functionality for book now button * feat(LOY-400): pr comment fix * feat(LOY-402): transformed the contentstack data * feat(LOY-404): added tracking * fix(LOY-404): fix for session storage removal of bookNowFromPointsModal * feat(LOY-404): added consts * fix(LOY-404): moved foxusWidget const * fix(LOY-404): moved BOOKING_WIDGET_STATE const * fix(LOY-404):fix Approved-by: Matilda Landström * fix(LOY-400): some fixes * feat(LOY-400): created linkList storybook Approved-by: Chuma Mcphoy (We Ahead) Approved-by: Matilda Landström
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
.modalContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Space-x2);
|
||||
width: 100%;
|
||||
padding: var(--Space-x3) var(--Space-x2) var(--Space-x4) var(--Space-x2);
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Space-x1);
|
||||
padding: 0 var(--Space-x2);
|
||||
}
|
||||
|
||||
.points {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--Space-x15);
|
||||
}
|
||||
|
||||
.pointsText {
|
||||
color: var(--Text-Brand-OnAccent-Heading);
|
||||
}
|
||||
|
||||
.pointsNumber {
|
||||
color: var(--Text-Brand-OnPrimary-1-Accent);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.modalContent {
|
||||
padding: var(--Space-x3) var(--Space-x3) var(--Space-x3);
|
||||
width: 560px; /* From figma design */
|
||||
}
|
||||
|
||||
.button {
|
||||
padding: var(--Space-x15) var(--Space-x2) var(--Space-x15) var(--Space-x3);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
"use client"
|
||||
import { cx } from "class-variance-authority"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { useIsMobile } from "@scandic-hotels/booking-flow/hooks/useBreakpoint"
|
||||
import { Button, type ButtonProps } from "@scandic-hotels/design-system/Button"
|
||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||
|
||||
import styles from "./UsePoints.module.css"
|
||||
|
||||
export function UsePointsButton({ variant, className, onPress }: ButtonProps) {
|
||||
const intl = useIntl()
|
||||
const isSmallScreen = useIsMobile()
|
||||
|
||||
const buttonVariant =
|
||||
variant === "Text" && isSmallScreen ? "Secondary" : variant
|
||||
|
||||
return (
|
||||
<Button
|
||||
size="Medium"
|
||||
className={cx(styles.button, className)}
|
||||
variant={buttonVariant}
|
||||
typography="Body/Paragraph/mdBold"
|
||||
onPress={onPress}
|
||||
>
|
||||
{intl.formatMessage({
|
||||
id: "myPages.membershipPointsOverview.usePointsButton",
|
||||
defaultMessage: "Use points",
|
||||
})}
|
||||
<MaterialIcon icon="chevron_right" size={24} color="CurrentColor" />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
"use client"
|
||||
import { usePathname, useSearchParams } from "next/navigation"
|
||||
import { useState } from "react"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { serializeBookingSearchParams } from "@scandic-hotels/booking-flow/utils/url"
|
||||
import { BOOK_NOW_SESSION_KEY } from "@scandic-hotels/common/constants/sessionKeys"
|
||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||
import { LinkList } from "@scandic-hotels/design-system/LinkList"
|
||||
import { MessageBanner } from "@scandic-hotels/design-system/MessageBanner"
|
||||
import Modal from "@scandic-hotels/design-system/Modal"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
import { trackClick, trackEvent } from "@scandic-hotels/tracking/base"
|
||||
import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking"
|
||||
|
||||
import { rewardNightsURL } from "@/constants/rewards"
|
||||
|
||||
import InfoCard from "@/components/ContentType/StartPage/InfoCard"
|
||||
|
||||
import { UsePointsButton } from "./UsePointsButton"
|
||||
|
||||
import styles from "./UsePoints.module.css"
|
||||
|
||||
import type { ButtonProps } from "@scandic-hotels/design-system/Button"
|
||||
import type { UsePointsModalData } from "@scandic-hotels/trpc/routers/contentstack/UsePointsModal/output"
|
||||
|
||||
function trackButtonClick(label: string) {
|
||||
trackClick(label)
|
||||
if (label === "book now") {
|
||||
sessionStorage.setItem(BOOK_NOW_SESSION_KEY, "true")
|
||||
}
|
||||
}
|
||||
|
||||
function trackLinkListClick(linkText: string) {
|
||||
if (!linkText) return
|
||||
trackEvent({
|
||||
event: "linkClick",
|
||||
link: { name: linkText },
|
||||
})
|
||||
}
|
||||
|
||||
type UsePointsModalProps = {
|
||||
buttonVariant: ButtonProps["variant"]
|
||||
contentData: UsePointsModalData
|
||||
points: number
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function UsePointsModal({
|
||||
buttonVariant,
|
||||
contentData,
|
||||
points,
|
||||
className,
|
||||
}: UsePointsModalProps) {
|
||||
const intl = useIntl()
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const pathname = usePathname()
|
||||
const initialSearchParams = useSearchParams()
|
||||
|
||||
const searchParams = serializeBookingSearchParams(
|
||||
{
|
||||
searchType: SEARCH_TYPE_REDEMPTION,
|
||||
focusWidget: true,
|
||||
},
|
||||
{ initialSearchParams }
|
||||
)
|
||||
|
||||
const bookLink = `${pathname}?${searchParams}`
|
||||
|
||||
const [items] = contentData.all_usepointsmodal.items
|
||||
|
||||
const linkListItems = items.link_group.map((link) => ({
|
||||
text: link.text,
|
||||
isExternal: link.isExternal,
|
||||
href: link.href,
|
||||
illustration: link.illustration,
|
||||
onClick: () => trackLinkListClick(link.text),
|
||||
}))
|
||||
|
||||
return (
|
||||
<>
|
||||
<UsePointsButton
|
||||
variant={buttonVariant}
|
||||
onPress={() => {
|
||||
setIsOpen(true)
|
||||
trackButtonClick("use points")
|
||||
}}
|
||||
className={className}
|
||||
/>
|
||||
<Modal isOpen={isOpen} onToggle={setIsOpen} withActions>
|
||||
<div className={styles.modalContent}>
|
||||
<div className={styles.header}>
|
||||
<Typography variant="Tag/sm">
|
||||
<p className={styles.pointsText}>
|
||||
{intl.formatMessage({
|
||||
id: "myPages.membershipPoints.youHave",
|
||||
defaultMessage: "You have",
|
||||
})}
|
||||
</p>
|
||||
</Typography>
|
||||
<div className={styles.points}>
|
||||
<Typography variant="Title/lg">
|
||||
<p className={styles.pointsNumber}>
|
||||
{intl.formatNumber(points)}
|
||||
</p>
|
||||
</Typography>
|
||||
<Typography variant="Tag/sm">
|
||||
<p className={styles.pointsText}>
|
||||
{intl.formatMessage({
|
||||
id: "myPages.membershipPoints.pointsToSpend",
|
||||
defaultMessage: "points to spend",
|
||||
})}
|
||||
</p>
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{points >= 10000 ? (
|
||||
<InfoCard
|
||||
image={items.image}
|
||||
heading={intl.formatMessage({
|
||||
id: "myPages.membershipPointsModal.heading",
|
||||
defaultMessage: "You’ve earned a night away",
|
||||
})}
|
||||
bodyText={intl.formatMessage({
|
||||
id: "myPages.membershipPointsModal.bodytext",
|
||||
defaultMessage: "Reward nights start at 10,000 points",
|
||||
})}
|
||||
primaryButton={{
|
||||
href: bookLink,
|
||||
title: intl.formatMessage({
|
||||
id: "myPages.membershipPointsModal.bookNow",
|
||||
defaultMessage: "Book now",
|
||||
}),
|
||||
forceReload: true,
|
||||
}}
|
||||
secondaryButton={{
|
||||
href: rewardNightsURL,
|
||||
title: intl.formatMessage({
|
||||
id: "myPages.membershipPointsModal.priceList",
|
||||
defaultMessage: "Price list",
|
||||
}),
|
||||
openInNewTab: true,
|
||||
materialIcon: (
|
||||
<MaterialIcon
|
||||
icon="open_in_new"
|
||||
color="CurrentColor"
|
||||
size={20}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
onPrimaryButtonClick={() => trackButtonClick("book now")}
|
||||
onSecondaryButtonClick={() => trackButtonClick("price list")}
|
||||
theme="primaryDark"
|
||||
imagePosition="top"
|
||||
height="dynamic"
|
||||
></InfoCard>
|
||||
) : (
|
||||
<InfoCard
|
||||
heading={intl.formatMessage({
|
||||
id: "myPages.membershipPointsModal.heading",
|
||||
defaultMessage:
|
||||
"Earn at least 10 000 points for a reward night",
|
||||
})}
|
||||
secondaryButton={{
|
||||
href: rewardNightsURL,
|
||||
title: intl.formatMessage({
|
||||
id: "myPages.membershipPointsModal.priceList",
|
||||
defaultMessage: "Price list",
|
||||
}),
|
||||
openInNewTab: true,
|
||||
materialIcon: (
|
||||
<MaterialIcon
|
||||
icon="open_in_new"
|
||||
color="CurrentColor"
|
||||
size={20}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
onSecondaryButtonClick={() => trackButtonClick("price list")}
|
||||
theme="primaryDark"
|
||||
imagePosition="top"
|
||||
height="dynamic"
|
||||
></InfoCard>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<LinkList linkListItems={linkListItems}></LinkList>
|
||||
</div>
|
||||
<MessageBanner
|
||||
type="info"
|
||||
text={intl.formatMessage({
|
||||
id: "myPages.membershipPointsModal.infoBanner",
|
||||
defaultMessage:
|
||||
"Spending points do not affect your level progress.",
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -5,10 +5,12 @@ import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
import { isBoostedBySas } from "@scandic-hotels/trpc/routers/user/helpers"
|
||||
|
||||
import { membershipLevels } from "@/constants/membershipLevels"
|
||||
import { getUsePointsModal } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import MembershipLevelIcon from "@/components/Levels/Icon"
|
||||
import { getIntl } from "@/i18n"
|
||||
|
||||
import { UsePointsModal } from "./UsePoints/UsePointsModal"
|
||||
import SasBoostStatus from "./SasBoostStatus"
|
||||
|
||||
import styles from "./membershipOverviewCard.module.css"
|
||||
@@ -24,6 +26,8 @@ export default async function MembershipOverviewCard({
|
||||
}: MembershipOverviewCardProps) {
|
||||
const intl = await getIntl()
|
||||
|
||||
const usePointsData = await getUsePointsModal()
|
||||
|
||||
if (!user.membership?.membershipLevel) {
|
||||
return null
|
||||
}
|
||||
@@ -70,17 +74,28 @@ export default async function MembershipOverviewCard({
|
||||
color="Border/Divider/Brand/OnPrimary 3/Default"
|
||||
/>
|
||||
|
||||
<Typography variant="Title/Overline/sm">
|
||||
<h3 className={styles.headingText}>
|
||||
{intl.formatMessage({
|
||||
id: "common.pointsToSpend",
|
||||
defaultMessage: "Points to spend",
|
||||
})}
|
||||
</h3>
|
||||
</Typography>
|
||||
<Typography variant="Title/lg">
|
||||
<p className={styles.pointsValue}>{pointsToSpendText}</p>
|
||||
</Typography>
|
||||
<div className={styles.bottom}>
|
||||
<div>
|
||||
<Typography variant="Title/Overline/sm">
|
||||
<h3 className={styles.headingText}>
|
||||
{intl.formatMessage({
|
||||
id: "common.pointsToSpend",
|
||||
defaultMessage: "Points to spend",
|
||||
})}
|
||||
</h3>
|
||||
</Typography>
|
||||
<Typography variant="Title/lg">
|
||||
<p className={styles.pointsValue}>{pointsToSpendText}</p>
|
||||
</Typography>
|
||||
</div>
|
||||
{user.membership.currentPoints > 0 && usePointsData && (
|
||||
<UsePointsModal
|
||||
buttonVariant="Primary"
|
||||
contentData={usePointsData}
|
||||
points={user.membership.currentPoints}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -23,13 +23,26 @@
|
||||
.divider {
|
||||
margin: var(--Space-x4) 0;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--Space-x1);
|
||||
}
|
||||
.pointsValue {
|
||||
color: var(--Text-Brand-OnPrimary-3-Accent);
|
||||
}
|
||||
@media screen and (max-width: 767px) {
|
||||
.bottom {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: var(--Space-x4);
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.card {
|
||||
padding: var(--Space-x3) var(--Space-x4);
|
||||
padding: var(--Space-x3) var(--Space-x4) var(--Space-x4);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,11 @@
|
||||
flex-direction: column;
|
||||
gap: var(--Space-x2);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
.usePointsButton {
|
||||
margin-top: var(--Space-x2);
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 768px) {
|
||||
.content {
|
||||
grid-template-columns: auto 1fr;
|
||||
@@ -105,6 +109,9 @@
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
}
|
||||
.usePointsButton {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
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 { getUsePointsModal } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import { getIntl } from "@/i18n"
|
||||
import { getLang } from "@/i18n/serverContext"
|
||||
|
||||
import { UsePointsModal } from "../../Overview/MembershipOverviewCard/UsePoints/UsePointsModal"
|
||||
import ExpiringPointsSeeAllButton from "./ExpiringPointsSeeAllButton"
|
||||
import { getExpiryLabel } from "./utils"
|
||||
|
||||
@@ -25,6 +24,8 @@ export default async function PointsToSpendCard({
|
||||
const intl = await getIntl()
|
||||
const lang = await getLang()
|
||||
|
||||
const usePointsData = await getUsePointsModal()
|
||||
|
||||
if (!user.membership) {
|
||||
return null
|
||||
}
|
||||
@@ -74,18 +75,13 @@ export default async function PointsToSpendCard({
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
{hasPointsToSpend && (
|
||||
<ButtonLink href={spendPoints[lang]} target="_blank" variant="Text">
|
||||
{intl.formatMessage({
|
||||
id: "points.pointsToSpendCard.howToSpendCta",
|
||||
defaultMessage: "How to spend points",
|
||||
})}
|
||||
<MaterialIcon
|
||||
icon="chevron_right"
|
||||
color="CurrentColor"
|
||||
size={24}
|
||||
/>
|
||||
</ButtonLink>
|
||||
{hasPointsToSpend && usePointsData && (
|
||||
<UsePointsModal
|
||||
buttonVariant="Text"
|
||||
contentData={usePointsData}
|
||||
points={user.membership.currentPoints}
|
||||
className={styles.usePointsButton}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,8 +13,11 @@ export default function InfoCard({
|
||||
image,
|
||||
primaryButton,
|
||||
secondaryButton,
|
||||
onPrimaryButtonClick,
|
||||
onSecondaryButtonClick,
|
||||
theme = "one",
|
||||
imagePosition = "right",
|
||||
height = "fixed",
|
||||
}: InfoCardProps) {
|
||||
return (
|
||||
<article className={styles.container}>
|
||||
@@ -30,7 +33,7 @@ export default function InfoCard({
|
||||
height={179}
|
||||
focalPoint={image.focalPoint}
|
||||
dimensions={image.dimensions}
|
||||
className={styles.image}
|
||||
className={imagePosition === "top" ? styles.imageTop : styles.image}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -40,8 +43,11 @@ export default function InfoCard({
|
||||
bodyText={bodyText}
|
||||
primaryButton={primaryButton}
|
||||
secondaryButton={secondaryButton}
|
||||
onPrimaryButtonClick={onPrimaryButtonClick}
|
||||
onSecondaryButtonClick={onSecondaryButtonClick}
|
||||
className={styles.card}
|
||||
theme={theme}
|
||||
height={height}
|
||||
/>
|
||||
</article>
|
||||
)
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
height: 179px; /* Exact mobile height from Figma */
|
||||
border-radius: var(--Corner-radius-md);
|
||||
}
|
||||
.imageTop {
|
||||
width: 100%;
|
||||
height: 180px;
|
||||
border-radius: var(--Corner-radius-md);
|
||||
}
|
||||
|
||||
.imageContainer {
|
||||
display: flex;
|
||||
@@ -32,6 +37,14 @@
|
||||
.container:has(.image-left) {
|
||||
grid-template-columns: 1fr 456px;
|
||||
}
|
||||
.container:has(.image-top) {
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--Space-x025);
|
||||
}
|
||||
|
||||
.image-top {
|
||||
order: 0;
|
||||
}
|
||||
|
||||
.image-right {
|
||||
order: 2;
|
||||
@@ -44,4 +57,7 @@
|
||||
.image {
|
||||
height: 320px; /* Desktop height from Figma */
|
||||
}
|
||||
.imageTop {
|
||||
height: 256px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
margin-right: var(--Space-x2);
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
text-wrap: balance;
|
||||
text-wrap: wrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -101,6 +101,9 @@
|
||||
gap: var(--Space-x1);
|
||||
justify-content: center;
|
||||
}
|
||||
.button {
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.buttonContainer {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { ImageVaultAsset } from "@scandic-hotels/common/utils/imageVault"
|
||||
import type { VariantProps } from "class-variance-authority"
|
||||
import type { JSX } from "react"
|
||||
|
||||
import type { ApiImage } from "@/types/components/image"
|
||||
import type { cardVariants } from "./variants"
|
||||
@@ -11,15 +12,17 @@ export interface CardProps
|
||||
href: string
|
||||
title: string
|
||||
openInNewTab?: boolean
|
||||
isExternal?: boolean
|
||||
forceReload?: boolean
|
||||
scrollOnClick?: boolean
|
||||
materialIcon?: JSX.Element
|
||||
} | null
|
||||
secondaryButton?: {
|
||||
href: string
|
||||
title: string
|
||||
openInNewTab?: boolean
|
||||
isExternal?: boolean
|
||||
forceReload?: boolean
|
||||
scrollOnClick?: boolean
|
||||
materialIcon?: JSX.Element
|
||||
} | null
|
||||
scriptedTopTitle?: string | null
|
||||
heading?: string | null
|
||||
|
||||
@@ -93,15 +93,32 @@ export default function Card({
|
||||
) : null}
|
||||
<div className={styles.buttonContainer}>
|
||||
{primaryButton ? (
|
||||
<Button asChild theme={buttonTheme} size="small">
|
||||
<Link
|
||||
href={primaryButton.href}
|
||||
target={primaryButton.openInNewTab ? "_blank" : undefined}
|
||||
onClick={onPrimaryButtonClick}
|
||||
scroll={primaryButton.scrollOnClick ?? true}
|
||||
>
|
||||
{primaryButton.title}
|
||||
</Link>
|
||||
<Button
|
||||
asChild
|
||||
theme={buttonTheme}
|
||||
size="small"
|
||||
className={styles.button}
|
||||
>
|
||||
{primaryButton.forceReload ? (
|
||||
<a
|
||||
href={primaryButton.href}
|
||||
target={primaryButton.openInNewTab ? "_blank" : undefined}
|
||||
onClick={onPrimaryButtonClick}
|
||||
>
|
||||
{primaryButton.title}
|
||||
{primaryButton.materialIcon}
|
||||
</a>
|
||||
) : (
|
||||
<Link
|
||||
href={primaryButton.href}
|
||||
target={primaryButton.openInNewTab ? "_blank" : undefined}
|
||||
onClick={onPrimaryButtonClick}
|
||||
scroll={primaryButton.scrollOnClick ?? true}
|
||||
>
|
||||
{primaryButton.title}
|
||||
{primaryButton.materialIcon}
|
||||
</Link>
|
||||
)}
|
||||
</Button>
|
||||
) : null}
|
||||
{secondaryButton ? (
|
||||
@@ -109,6 +126,7 @@ export default function Card({
|
||||
asChild
|
||||
theme={buttonTheme}
|
||||
size="small"
|
||||
className={styles.button}
|
||||
intent="secondary"
|
||||
disabled
|
||||
>
|
||||
@@ -119,6 +137,7 @@ export default function Card({
|
||||
scroll={secondaryButton.scrollOnClick ?? true}
|
||||
>
|
||||
{secondaryButton.title}
|
||||
{secondaryButton.materialIcon}
|
||||
</Link>
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
export const REWARDS_PER_PAGE = 6
|
||||
|
||||
export const rewardNightsURL =
|
||||
"https://www.scandichotels.com/scandic-friends/spend-points/reward-nights"
|
||||
|
||||
@@ -251,3 +251,10 @@ export const getProfilingConsent = cache(
|
||||
return caller.contentstack.profilingConsent.get()
|
||||
}
|
||||
)
|
||||
|
||||
export const getUsePointsModal = cache(
|
||||
async function getMemoizedUsePointsModal() {
|
||||
const caller = await serverClient()
|
||||
return caller.contentstack.usePointsModal.get()
|
||||
}
|
||||
)
|
||||
|
||||
@@ -12,11 +12,14 @@ type CardTheme = Exclude<
|
||||
export interface InfoCardProps {
|
||||
scriptedTopTitle?: string
|
||||
heading: string
|
||||
bodyText: string
|
||||
bodyText?: string
|
||||
image?: ImageVaultAsset
|
||||
imagePosition?: "left" | "right"
|
||||
imagePosition?: "left" | "right" | "top"
|
||||
primaryButton?: CardProps["primaryButton"]
|
||||
secondaryButton?: CardProps["secondaryButton"]
|
||||
onPrimaryButtonClick?: () => void
|
||||
onSecondaryButtonClick?: () => void
|
||||
theme?: CardTheme
|
||||
className?: string
|
||||
height?: "fixed" | "dynamic"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user