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 { isBoostedBySas } from "@scandic-hotels/trpc/routers/user/helpers"
|
||||||
|
|
||||||
import { membershipLevels } from "@/constants/membershipLevels"
|
import { membershipLevels } from "@/constants/membershipLevels"
|
||||||
|
import { getUsePointsModal } from "@/lib/trpc/memoizedRequests"
|
||||||
|
|
||||||
import MembershipLevelIcon from "@/components/Levels/Icon"
|
import MembershipLevelIcon from "@/components/Levels/Icon"
|
||||||
import { getIntl } from "@/i18n"
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
|
import { UsePointsModal } from "./UsePoints/UsePointsModal"
|
||||||
import SasBoostStatus from "./SasBoostStatus"
|
import SasBoostStatus from "./SasBoostStatus"
|
||||||
|
|
||||||
import styles from "./membershipOverviewCard.module.css"
|
import styles from "./membershipOverviewCard.module.css"
|
||||||
@@ -24,6 +26,8 @@ export default async function MembershipOverviewCard({
|
|||||||
}: MembershipOverviewCardProps) {
|
}: MembershipOverviewCardProps) {
|
||||||
const intl = await getIntl()
|
const intl = await getIntl()
|
||||||
|
|
||||||
|
const usePointsData = await getUsePointsModal()
|
||||||
|
|
||||||
if (!user.membership?.membershipLevel) {
|
if (!user.membership?.membershipLevel) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -70,17 +74,28 @@ export default async function MembershipOverviewCard({
|
|||||||
color="Border/Divider/Brand/OnPrimary 3/Default"
|
color="Border/Divider/Brand/OnPrimary 3/Default"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Typography variant="Title/Overline/sm">
|
<div className={styles.bottom}>
|
||||||
<h3 className={styles.headingText}>
|
<div>
|
||||||
{intl.formatMessage({
|
<Typography variant="Title/Overline/sm">
|
||||||
id: "common.pointsToSpend",
|
<h3 className={styles.headingText}>
|
||||||
defaultMessage: "Points to spend",
|
{intl.formatMessage({
|
||||||
})}
|
id: "common.pointsToSpend",
|
||||||
</h3>
|
defaultMessage: "Points to spend",
|
||||||
</Typography>
|
})}
|
||||||
<Typography variant="Title/lg">
|
</h3>
|
||||||
<p className={styles.pointsValue}>{pointsToSpendText}</p>
|
</Typography>
|
||||||
</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>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,13 +23,26 @@
|
|||||||
.divider {
|
.divider {
|
||||||
margin: var(--Space-x4) 0;
|
margin: var(--Space-x4) 0;
|
||||||
}
|
}
|
||||||
|
.bottom {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--Space-x1);
|
||||||
|
}
|
||||||
.pointsValue {
|
.pointsValue {
|
||||||
color: var(--Text-Brand-OnPrimary-3-Accent);
|
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) {
|
@media screen and (min-width: 1367px) {
|
||||||
.card {
|
.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;
|
flex-direction: column;
|
||||||
gap: var(--Space-x2);
|
gap: var(--Space-x2);
|
||||||
}
|
}
|
||||||
|
@media screen and (max-width: 767px) {
|
||||||
|
.usePointsButton {
|
||||||
|
margin-top: var(--Space-x2);
|
||||||
|
}
|
||||||
|
}
|
||||||
@media screen and (min-width: 768px) {
|
@media screen and (min-width: 768px) {
|
||||||
.content {
|
.content {
|
||||||
grid-template-columns: auto 1fr;
|
grid-template-columns: auto 1fr;
|
||||||
@@ -105,6 +109,9 @@
|
|||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
.usePointsButton {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 1367px) {
|
@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 MoneyHandEllipsisIcon from "@scandic-hotels/design-system/Icons/MoneyHandEllipsisIcon"
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import { spendPoints } from "@/constants/webHrefs"
|
import { getUsePointsModal } from "@/lib/trpc/memoizedRequests"
|
||||||
|
|
||||||
import { getIntl } from "@/i18n"
|
import { getIntl } from "@/i18n"
|
||||||
import { getLang } from "@/i18n/serverContext"
|
import { getLang } from "@/i18n/serverContext"
|
||||||
|
|
||||||
|
import { UsePointsModal } from "../../Overview/MembershipOverviewCard/UsePoints/UsePointsModal"
|
||||||
import ExpiringPointsSeeAllButton from "./ExpiringPointsSeeAllButton"
|
import ExpiringPointsSeeAllButton from "./ExpiringPointsSeeAllButton"
|
||||||
import { getExpiryLabel } from "./utils"
|
import { getExpiryLabel } from "./utils"
|
||||||
|
|
||||||
@@ -25,6 +24,8 @@ export default async function PointsToSpendCard({
|
|||||||
const intl = await getIntl()
|
const intl = await getIntl()
|
||||||
const lang = await getLang()
|
const lang = await getLang()
|
||||||
|
|
||||||
|
const usePointsData = await getUsePointsModal()
|
||||||
|
|
||||||
if (!user.membership) {
|
if (!user.membership) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -74,18 +75,13 @@ export default async function PointsToSpendCard({
|
|||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{hasPointsToSpend && (
|
{hasPointsToSpend && usePointsData && (
|
||||||
<ButtonLink href={spendPoints[lang]} target="_blank" variant="Text">
|
<UsePointsModal
|
||||||
{intl.formatMessage({
|
buttonVariant="Text"
|
||||||
id: "points.pointsToSpendCard.howToSpendCta",
|
contentData={usePointsData}
|
||||||
defaultMessage: "How to spend points",
|
points={user.membership.currentPoints}
|
||||||
})}
|
className={styles.usePointsButton}
|
||||||
<MaterialIcon
|
/>
|
||||||
icon="chevron_right"
|
|
||||||
color="CurrentColor"
|
|
||||||
size={24}
|
|
||||||
/>
|
|
||||||
</ButtonLink>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,8 +13,11 @@ export default function InfoCard({
|
|||||||
image,
|
image,
|
||||||
primaryButton,
|
primaryButton,
|
||||||
secondaryButton,
|
secondaryButton,
|
||||||
|
onPrimaryButtonClick,
|
||||||
|
onSecondaryButtonClick,
|
||||||
theme = "one",
|
theme = "one",
|
||||||
imagePosition = "right",
|
imagePosition = "right",
|
||||||
|
height = "fixed",
|
||||||
}: InfoCardProps) {
|
}: InfoCardProps) {
|
||||||
return (
|
return (
|
||||||
<article className={styles.container}>
|
<article className={styles.container}>
|
||||||
@@ -30,7 +33,7 @@ export default function InfoCard({
|
|||||||
height={179}
|
height={179}
|
||||||
focalPoint={image.focalPoint}
|
focalPoint={image.focalPoint}
|
||||||
dimensions={image.dimensions}
|
dimensions={image.dimensions}
|
||||||
className={styles.image}
|
className={imagePosition === "top" ? styles.imageTop : styles.image}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -40,8 +43,11 @@ export default function InfoCard({
|
|||||||
bodyText={bodyText}
|
bodyText={bodyText}
|
||||||
primaryButton={primaryButton}
|
primaryButton={primaryButton}
|
||||||
secondaryButton={secondaryButton}
|
secondaryButton={secondaryButton}
|
||||||
|
onPrimaryButtonClick={onPrimaryButtonClick}
|
||||||
|
onSecondaryButtonClick={onSecondaryButtonClick}
|
||||||
className={styles.card}
|
className={styles.card}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
|
height={height}
|
||||||
/>
|
/>
|
||||||
</article>
|
</article>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,6 +10,11 @@
|
|||||||
height: 179px; /* Exact mobile height from Figma */
|
height: 179px; /* Exact mobile height from Figma */
|
||||||
border-radius: var(--Corner-radius-md);
|
border-radius: var(--Corner-radius-md);
|
||||||
}
|
}
|
||||||
|
.imageTop {
|
||||||
|
width: 100%;
|
||||||
|
height: 180px;
|
||||||
|
border-radius: var(--Corner-radius-md);
|
||||||
|
}
|
||||||
|
|
||||||
.imageContainer {
|
.imageContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -32,6 +37,14 @@
|
|||||||
.container:has(.image-left) {
|
.container:has(.image-left) {
|
||||||
grid-template-columns: 1fr 456px;
|
grid-template-columns: 1fr 456px;
|
||||||
}
|
}
|
||||||
|
.container:has(.image-top) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: var(--Space-x025);
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-top {
|
||||||
|
order: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.image-right {
|
.image-right {
|
||||||
order: 2;
|
order: 2;
|
||||||
@@ -44,4 +57,7 @@
|
|||||||
.image {
|
.image {
|
||||||
height: 320px; /* Desktop height from Figma */
|
height: 320px; /* Desktop height from Figma */
|
||||||
}
|
}
|
||||||
|
.imageTop {
|
||||||
|
height: 256px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
margin-right: var(--Space-x2);
|
margin-right: var(--Space-x2);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-wrap: balance;
|
text-wrap: wrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,6 +101,9 @@
|
|||||||
gap: var(--Space-x1);
|
gap: var(--Space-x1);
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
.button {
|
||||||
|
min-width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
@media screen and (min-width: 768px) {
|
||||||
.buttonContainer {
|
.buttonContainer {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { ImageVaultAsset } from "@scandic-hotels/common/utils/imageVault"
|
import type { ImageVaultAsset } from "@scandic-hotels/common/utils/imageVault"
|
||||||
import type { VariantProps } from "class-variance-authority"
|
import type { VariantProps } from "class-variance-authority"
|
||||||
|
import type { JSX } from "react"
|
||||||
|
|
||||||
import type { ApiImage } from "@/types/components/image"
|
import type { ApiImage } from "@/types/components/image"
|
||||||
import type { cardVariants } from "./variants"
|
import type { cardVariants } from "./variants"
|
||||||
@@ -11,15 +12,17 @@ export interface CardProps
|
|||||||
href: string
|
href: string
|
||||||
title: string
|
title: string
|
||||||
openInNewTab?: boolean
|
openInNewTab?: boolean
|
||||||
isExternal?: boolean
|
forceReload?: boolean
|
||||||
scrollOnClick?: boolean
|
scrollOnClick?: boolean
|
||||||
|
materialIcon?: JSX.Element
|
||||||
} | null
|
} | null
|
||||||
secondaryButton?: {
|
secondaryButton?: {
|
||||||
href: string
|
href: string
|
||||||
title: string
|
title: string
|
||||||
openInNewTab?: boolean
|
openInNewTab?: boolean
|
||||||
isExternal?: boolean
|
forceReload?: boolean
|
||||||
scrollOnClick?: boolean
|
scrollOnClick?: boolean
|
||||||
|
materialIcon?: JSX.Element
|
||||||
} | null
|
} | null
|
||||||
scriptedTopTitle?: string | null
|
scriptedTopTitle?: string | null
|
||||||
heading?: string | null
|
heading?: string | null
|
||||||
|
|||||||
@@ -93,15 +93,32 @@ export default function Card({
|
|||||||
) : null}
|
) : null}
|
||||||
<div className={styles.buttonContainer}>
|
<div className={styles.buttonContainer}>
|
||||||
{primaryButton ? (
|
{primaryButton ? (
|
||||||
<Button asChild theme={buttonTheme} size="small">
|
<Button
|
||||||
<Link
|
asChild
|
||||||
href={primaryButton.href}
|
theme={buttonTheme}
|
||||||
target={primaryButton.openInNewTab ? "_blank" : undefined}
|
size="small"
|
||||||
onClick={onPrimaryButtonClick}
|
className={styles.button}
|
||||||
scroll={primaryButton.scrollOnClick ?? true}
|
>
|
||||||
>
|
{primaryButton.forceReload ? (
|
||||||
{primaryButton.title}
|
<a
|
||||||
</Link>
|
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>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
{secondaryButton ? (
|
{secondaryButton ? (
|
||||||
@@ -109,6 +126,7 @@ export default function Card({
|
|||||||
asChild
|
asChild
|
||||||
theme={buttonTheme}
|
theme={buttonTheme}
|
||||||
size="small"
|
size="small"
|
||||||
|
className={styles.button}
|
||||||
intent="secondary"
|
intent="secondary"
|
||||||
disabled
|
disabled
|
||||||
>
|
>
|
||||||
@@ -119,6 +137,7 @@ export default function Card({
|
|||||||
scroll={secondaryButton.scrollOnClick ?? true}
|
scroll={secondaryButton.scrollOnClick ?? true}
|
||||||
>
|
>
|
||||||
{secondaryButton.title}
|
{secondaryButton.title}
|
||||||
|
{secondaryButton.materialIcon}
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -1 +1,4 @@
|
|||||||
export const REWARDS_PER_PAGE = 6
|
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()
|
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 {
|
export interface InfoCardProps {
|
||||||
scriptedTopTitle?: string
|
scriptedTopTitle?: string
|
||||||
heading: string
|
heading: string
|
||||||
bodyText: string
|
bodyText?: string
|
||||||
image?: ImageVaultAsset
|
image?: ImageVaultAsset
|
||||||
imagePosition?: "left" | "right"
|
imagePosition?: "left" | "right" | "top"
|
||||||
primaryButton?: CardProps["primaryButton"]
|
primaryButton?: CardProps["primaryButton"]
|
||||||
secondaryButton?: CardProps["secondaryButton"]
|
secondaryButton?: CardProps["secondaryButton"]
|
||||||
|
onPrimaryButtonClick?: () => void
|
||||||
|
onSecondaryButtonClick?: () => void
|
||||||
theme?: CardTheme
|
theme?: CardTheme
|
||||||
className?: string
|
className?: string
|
||||||
|
height?: "fixed" | "dynamic"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
|
|
||||||
|
import { BOOK_NOW_SESSION_KEY } from "@scandic-hotels/common/constants/sessionKeys"
|
||||||
import { TrackingSDK } from "@scandic-hotels/tracking/TrackingSDK"
|
import { TrackingSDK } from "@scandic-hotels/tracking/TrackingSDK"
|
||||||
|
|
||||||
import { useBookingFlowConfig } from "../../../bookingFlowConfig/bookingFlowConfigContext"
|
import { useBookingFlowConfig } from "../../../bookingFlowConfig/bookingFlowConfigContext"
|
||||||
@@ -53,6 +54,9 @@ export default function BookingConfirmationTracking({
|
|||||||
if (trackingData?.paymentInfo) {
|
if (trackingData?.paymentInfo) {
|
||||||
clearPaymentInfoSessionStorage()
|
clearPaymentInfoSessionStorage()
|
||||||
}
|
}
|
||||||
|
if (trackingData?.hotelsTrackingData) {
|
||||||
|
sessionStorage.removeItem(BOOK_NOW_SESSION_KEY)
|
||||||
|
}
|
||||||
}, [trackingData])
|
}, [trackingData])
|
||||||
|
|
||||||
if (!trackingData) {
|
if (!trackingData) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { differenceInCalendarDays, format, isWeekend } from "date-fns"
|
|||||||
import { CancellationRuleEnum } from "@scandic-hotels/common/constants/booking"
|
import { CancellationRuleEnum } from "@scandic-hotels/common/constants/booking"
|
||||||
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
||||||
import { RateEnum } from "@scandic-hotels/common/constants/rate"
|
import { RateEnum } from "@scandic-hotels/common/constants/rate"
|
||||||
|
import { BOOK_NOW_SESSION_KEY } from "@scandic-hotels/common/constants/sessionKeys"
|
||||||
import {
|
import {
|
||||||
TrackingChannelEnum,
|
TrackingChannelEnum,
|
||||||
type TrackingSDKAncillaries,
|
type TrackingSDKAncillaries,
|
||||||
@@ -104,6 +105,11 @@ export function getTracking(
|
|||||||
: undefined,
|
: undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pointsModalConversion =
|
||||||
|
booking.roomPoints > 0 && typeof window !== "undefined"
|
||||||
|
? sessionStorage.getItem(BOOK_NOW_SESSION_KEY) === "true"
|
||||||
|
: undefined
|
||||||
|
|
||||||
const hotelsTrackingData: TrackingSDKHotelInfo = {
|
const hotelsTrackingData: TrackingSDKHotelInfo = {
|
||||||
ageOfChildren: rooms.map((r) => r.childrenAges?.join(",") ?? "").join("|"),
|
ageOfChildren: rooms.map((r) => r.childrenAges?.join(",") ?? "").join("|"),
|
||||||
analyticsRateCode: rooms
|
analyticsRateCode: rooms
|
||||||
@@ -157,6 +163,7 @@ export function getTracking(
|
|||||||
rewardNight: booking.roomPoints > 0 ? "yes" : "no",
|
rewardNight: booking.roomPoints > 0 ? "yes" : "no",
|
||||||
rewardNightAvailability: booking.roomPoints > 0 ? "true" : "false",
|
rewardNightAvailability: booking.roomPoints > 0 ? "true" : "false",
|
||||||
points: booking.roomPoints > 0 ? booking.roomPoints : undefined,
|
points: booking.roomPoints > 0 ? booking.roomPoints : undefined,
|
||||||
|
pointsModalConversion: pointsModalConversion ? "yes" : "no",
|
||||||
roomPrice: rooms.reduce((total, room) => total + room.roomPrice, 0),
|
roomPrice: rooms.reduce((total, room) => total + room.roomPrice, 0),
|
||||||
roomTypeCode: rooms.map((r) => r.roomTypeCode ?? "").join(","),
|
roomTypeCode: rooms.map((r) => r.roomTypeCode ?? "").join(","),
|
||||||
searchTerm: hotel.name,
|
searchTerm: hotel.name,
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { cx } from "class-variance-authority"
|
import { cx } from "class-variance-authority"
|
||||||
import { usePathname } from "next/navigation"
|
import { usePathname, useSearchParams } from "next/navigation"
|
||||||
|
import { useEffect } from "react"
|
||||||
import { useFormContext, useWatch } from "react-hook-form"
|
import { useFormContext, useWatch } from "react-hook-form"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
@@ -16,6 +17,7 @@ import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking"
|
|||||||
import { useBookingFlowConfig } from "../../../../bookingFlowConfig/bookingFlowConfigContext"
|
import { useBookingFlowConfig } from "../../../../bookingFlowConfig/bookingFlowConfigContext"
|
||||||
import useLang from "../../../../hooks/useLang"
|
import useLang from "../../../../hooks/useLang"
|
||||||
import GuestsRoomsPickerForm from "../../../BookingWidget/GuestsRoomsPicker"
|
import GuestsRoomsPickerForm from "../../../BookingWidget/GuestsRoomsPicker"
|
||||||
|
import { type BookingWidgetSchema, FOCUS_WIDGET } from "../../Client"
|
||||||
import DatePicker from "../../DatePicker"
|
import DatePicker from "../../DatePicker"
|
||||||
import { RemoveExtraRooms } from "./RemoveExtraRooms/RemoveExtraRooms"
|
import { RemoveExtraRooms } from "./RemoveExtraRooms/RemoveExtraRooms"
|
||||||
import { Search, SearchSkeleton } from "./Search"
|
import { Search, SearchSkeleton } from "./Search"
|
||||||
@@ -25,8 +27,6 @@ import Voucher, { VoucherSkeleton } from "./Voucher"
|
|||||||
|
|
||||||
import styles from "./formContent.module.css"
|
import styles from "./formContent.module.css"
|
||||||
|
|
||||||
import type { BookingWidgetSchema } from "../../Client"
|
|
||||||
|
|
||||||
type BookingWidgetFormContentProps = {
|
type BookingWidgetFormContentProps = {
|
||||||
formId: string
|
formId: string
|
||||||
onSubmit: () => void
|
onSubmit: () => void
|
||||||
@@ -42,6 +42,15 @@ export default function FormContent({
|
|||||||
formState: { errors, isDirty },
|
formState: { errors, isDirty },
|
||||||
} = useFormContext<BookingWidgetSchema>()
|
} = useFormContext<BookingWidgetSchema>()
|
||||||
const { bookingCodeEnabled, redemptionEnabled } = useBookingFlowConfig()
|
const { bookingCodeEnabled, redemptionEnabled } = useBookingFlowConfig()
|
||||||
|
const searchParams = useSearchParams()
|
||||||
|
const focusWidget = searchParams.get(FOCUS_WIDGET) === "true"
|
||||||
|
useEffect(() => {
|
||||||
|
if (!focusWidget) return
|
||||||
|
|
||||||
|
const url = new URL(window.location.href)
|
||||||
|
url.searchParams.delete(FOCUS_WIDGET)
|
||||||
|
window.history.replaceState({}, "", url.toString())
|
||||||
|
}, [focusWidget])
|
||||||
|
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
const pathName = usePathname()
|
const pathName = usePathname()
|
||||||
@@ -60,6 +69,7 @@ export default function FormContent({
|
|||||||
selectOnBlur={true}
|
selectOnBlur={true}
|
||||||
inputName="search"
|
inputName="search"
|
||||||
includeTypes={["cities", "hotels"]}
|
includeTypes={["cities", "hotels"]}
|
||||||
|
autoFocus={focusWidget}
|
||||||
/>
|
/>
|
||||||
{errors.search && <ValidationError />}
|
{errors.search && <ValidationError />}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -44,12 +44,17 @@ export type BookingWidgetClientProps = {
|
|||||||
data: BookingWidgetSearchData
|
data: BookingWidgetSearchData
|
||||||
pageSettingsBookingCodePromise: Promise<string> | null
|
pageSettingsBookingCodePromise: Promise<string> | null
|
||||||
}
|
}
|
||||||
|
export const FOCUS_WIDGET = "focusWidget"
|
||||||
|
|
||||||
export default function BookingWidgetClient({
|
export default function BookingWidgetClient({
|
||||||
type,
|
type,
|
||||||
data,
|
data,
|
||||||
pageSettingsBookingCodePromise,
|
pageSettingsBookingCodePromise,
|
||||||
}: BookingWidgetClientProps) {
|
}: BookingWidgetClientProps) {
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const searchParams = useSearchParams()
|
||||||
|
const focusWidget = searchParams.get(FOCUS_WIDGET) === "true"
|
||||||
|
|
||||||
|
const [isOpen, setIsOpen] = useState(focusWidget)
|
||||||
const bookingWidgetRef = useRef(null)
|
const bookingWidgetRef = useRef(null)
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
const bookingFlowConfig = useBookingFlowConfig()
|
const bookingFlowConfig = useBookingFlowConfig()
|
||||||
@@ -142,7 +147,6 @@ export default function BookingWidgetClient({
|
|||||||
reValidateMode: "onSubmit",
|
reValidateMode: "onSubmit",
|
||||||
})
|
})
|
||||||
|
|
||||||
const searchParams = useSearchParams()
|
|
||||||
const bookingCodeFromSearchParams = searchParams.get("bookingCode") || ""
|
const bookingCodeFromSearchParams = searchParams.get("bookingCode") || ""
|
||||||
const [bookingCode, setBookingCode] = useState(bookingCodeFromSearchParams)
|
const [bookingCode, setBookingCode] = useState(bookingCodeFromSearchParams)
|
||||||
|
|
||||||
|
|||||||
@@ -13,12 +13,13 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
@media screen and (max-width: 767px) {
|
||||||
.backdrop {
|
.backdrop {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
background-color: rgba(0, 0, 0, 0.4);
|
background-color: rgba(0, 0, 0, 0.4);
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export type BookingWidgetSearchData = {
|
|||||||
rooms?: GuestsRoom[]
|
rooms?: GuestsRoom[]
|
||||||
bookingCode?: string
|
bookingCode?: string
|
||||||
searchType?: BookingSearchType
|
searchType?: BookingSearchType
|
||||||
|
focusWidget?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BookingWidgetType = VariantProps<
|
export type BookingWidgetType = VariantProps<
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
import { BOOKING_WIDGET_STATE } from "@scandic-hotels/common/constants/bookingWidget"
|
import { BOOKING_WIDGET_STATE } from "@scandic-hotels/common/constants/sessionKeys"
|
||||||
import { dt } from "@scandic-hotels/common/dt"
|
import { dt } from "@scandic-hotels/common/dt"
|
||||||
|
|
||||||
import { adultsSchema, childAgeSchema, childBedSchema } from "../utils/url"
|
import { adultsSchema, childAgeSchema, childBedSchema } from "../utils/url"
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"./hooks/useHandleBookingStatus": "./lib/hooks/useHandleBookingStatus.ts",
|
"./hooks/useHandleBookingStatus": "./lib/hooks/useHandleBookingStatus.ts",
|
||||||
"./hooks/useBookingWidgetState": "./lib/hooks/useBookingWidgetState.ts",
|
"./hooks/useBookingWidgetState": "./lib/hooks/useBookingWidgetState.ts",
|
||||||
"./hooks/useBookingFlowContext": "./lib/hooks/useBookingFlowContext.ts",
|
"./hooks/useBookingFlowContext": "./lib/hooks/useBookingFlowContext.ts",
|
||||||
|
"./hooks/useBreakpoint": "./lib/hooks/useBreakpoint.ts",
|
||||||
"./pages/*": "./lib/pages/*.tsx",
|
"./pages/*": "./lib/pages/*.tsx",
|
||||||
"./stores/enter-details/types": "./lib/stores/enter-details/types.ts",
|
"./stores/enter-details/types": "./lib/stores/enter-details/types.ts",
|
||||||
"./stores/hotels-map": "./lib/stores/hotels-map.ts",
|
"./stores/hotels-map": "./lib/stores/hotels-map.ts",
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
export const BOOKING_WIDGET_STATE = "bookingWidgetState"
|
|
||||||
3
packages/common/constants/sessionKeys.ts
Normal file
3
packages/common/constants/sessionKeys.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export const BOOKING_WIDGET_STATE = "bookingWidgetState"
|
||||||
|
|
||||||
|
export const BOOK_NOW_SESSION_KEY = "bookNowFromPointsModal"
|
||||||
@@ -15,7 +15,6 @@
|
|||||||
"./polyfills": "./polyfills/index.ts",
|
"./polyfills": "./polyfills/index.ts",
|
||||||
"./constants/alert": "./constants/alert.ts",
|
"./constants/alert": "./constants/alert.ts",
|
||||||
"./constants/booking": "./constants/booking.ts",
|
"./constants/booking": "./constants/booking.ts",
|
||||||
"./constants/bookingWidget": "./constants/bookingWidget.ts",
|
|
||||||
"./constants/country": "./constants/country.ts",
|
"./constants/country": "./constants/country.ts",
|
||||||
"./constants/currency": "./constants/currency.ts",
|
"./constants/currency": "./constants/currency.ts",
|
||||||
"./constants/dateFormats": "./constants/dateFormats.ts",
|
"./constants/dateFormats": "./constants/dateFormats.ts",
|
||||||
@@ -30,6 +29,7 @@
|
|||||||
"./constants/rate": "./constants/rate.ts",
|
"./constants/rate": "./constants/rate.ts",
|
||||||
"./constants/rateType": "./constants/rateType.ts",
|
"./constants/rateType": "./constants/rateType.ts",
|
||||||
"./constants/routes/*": "./constants/routes/*.ts",
|
"./constants/routes/*": "./constants/routes/*.ts",
|
||||||
|
"./constants/sessionKeys": "./constants/sessionKeys.ts",
|
||||||
"./constants/signatureHotels": "./constants/signatureHotels.ts",
|
"./constants/signatureHotels": "./constants/signatureHotels.ts",
|
||||||
"./constants/paymentCallbackStatus": "./constants/paymentCallbackStatus.ts",
|
"./constants/paymentCallbackStatus": "./constants/paymentCallbackStatus.ts",
|
||||||
"./constants/scandicPartners": "./constants/scandicPartners.ts",
|
"./constants/scandicPartners": "./constants/scandicPartners.ts",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import CroissantCoffeeEggIcon from './Illustrations/CroissantCoffeeEgg'
|
|||||||
import CutleryOneIcon from './Illustrations/CutleryOne'
|
import CutleryOneIcon from './Illustrations/CutleryOne'
|
||||||
import CutleryTwoIcon from './Illustrations/CutleryTwo'
|
import CutleryTwoIcon from './Illustrations/CutleryTwo'
|
||||||
import GiftOpenIcon from './Illustrations/GiftOpen'
|
import GiftOpenIcon from './Illustrations/GiftOpen'
|
||||||
|
import GranolaIcon from './Illustrations/Granola'
|
||||||
import HandKeyIcon from './Illustrations/HandKey'
|
import HandKeyIcon from './Illustrations/HandKey'
|
||||||
import HotelNightIcon from './Illustrations/HotelNight'
|
import HotelNightIcon from './Illustrations/HotelNight'
|
||||||
import KidsIcon from './Illustrations/Kids'
|
import KidsIcon from './Illustrations/Kids'
|
||||||
@@ -35,6 +36,8 @@ export function IllustrationByIconName(iconName: IconName | null) {
|
|||||||
return HotelNightIcon
|
return HotelNightIcon
|
||||||
case IconName.GiftOpen:
|
case IconName.GiftOpen:
|
||||||
return GiftOpenIcon
|
return GiftOpenIcon
|
||||||
|
case IconName.Granola:
|
||||||
|
return GranolaIcon
|
||||||
case IconName.CutleryOne:
|
case IconName.CutleryOne:
|
||||||
return CutleryOneIcon
|
return CutleryOneIcon
|
||||||
case IconName.CutleryTwo:
|
case IconName.CutleryTwo:
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export default function BedIcon(props: IllustrationProps) {
|
|||||||
{...props}
|
{...props}
|
||||||
{...ariaProps}
|
{...ariaProps}
|
||||||
>
|
>
|
||||||
<path fill="#fff" d="M0 .75h358v201.375H0z" />
|
|
||||||
<g fill="#cd0921">
|
<g fill="#cd0921">
|
||||||
<path d="M175.163 98.517c-2.923-7.732-3.138-25.751-.561-34.552v-.012q.205-.7.436-1.319a.86.86 0 0 0-.609-1.133.9.9 0 0 0-.286-.012q-.038-.002-.066.006c-1.259.256-2.816.477-4.6.668l-.68.072q-.688.07-1.42.13c-.973.084-1.993.162-3.049.233q-1.192.081-2.446.144c-.555.03-1.122.053-1.695.083l-1.736.072-1.552.053c-.143 0-.292.006-.435.012-.525.012-1.05.03-1.581.042l-1.82.036-1.193.018-2.268.018h-2.255c-.543 0-1.086 0-1.629-.012-.978-.012-1.945-.024-2.906-.048-.471-.012-.942-.018-1.408-.036-.125 0-.244-.006-.37-.012-.465-.012-.93-.03-1.39-.041a219 219 0 0 1-3.705-.156q-1.146-.06-2.238-.13c-3.609-.24-6.694-.574-8.848-1.01a.853.853 0 0 0-.996.633.83.83 0 0 0 .017.448c.633 2.052 1.737 5.936 2.077 9.021l.465 7.452c.137 6.725-.453 13.658-1.772 18.169q-.204.706-.435 1.33a.85.85 0 0 0 .501 1.098q.053.02.107.03a.9.9 0 0 0 .352 0c.102-.018.209-.042.316-.06.179-.035.364-.065.555-.101a53 53 0 0 1 2.787-.4 87 87 0 0 1 1.748-.19c.823-.084 1.694-.156 2.601-.228 7.393-.572 17.184-.733 25.704-.47 2.339.112 4.684.226 7.023.333.209.012.418.03.621.042h.006q.615.043 1.205.096h.012q.589.046 1.151.1h.012l.549.055h.036l.513.053h.048l.483.054c.03 0 .054.006.084.012.143.018.292.036.435.048q.045.001.09.012l.411.053q.043.001.096.012l.394.054c.041 0 .083.012.125.018.119.018.233.03.346.048l.119.018c.114.017.221.035.334.047l.114.018.322.054q.063.008.125.024l.293.053.101.018.298.054a.848.848 0 0 0 .961-1.134zM235.606 98.517c-2.924-7.732-3.139-25.751-.561-34.552v-.012q.204-.7.435-1.319a.86.86 0 0 0-.608-1.133 1 1 0 0 0-.287-.012q-.037-.002-.065.006c-1.259.256-2.817.477-4.601.668l-.68.072q-.687.07-1.42.13c-.972.084-1.993.162-3.049.233q-1.191.081-2.446.144c-.555.03-1.122.053-1.695.083l-1.736.072-1.551.053c-.143 0-.292.006-.436.012-.525.012-1.05.03-1.581.042l-1.82.036-1.193.018-2.267.018h-2.256c-.543 0-1.086 0-1.629-.012a231 231 0 0 1-2.905-.048c-.472-.012-.943-.018-1.408-.036-.126 0-.245-.006-.37-.012-.466-.012-.931-.03-1.391-.041q-1.905-.065-3.705-.156-1.145-.06-2.237-.13c-3.61-.24-6.695-.574-8.849-1.01a.853.853 0 0 0-.996.633.8.8 0 0 0 .018.448c.632 2.052 1.736 5.936 2.076 9.021l.466 7.452c.137 6.725-.454 13.658-1.773 18.169q-.204.706-.435 1.33a.85.85 0 0 0 .501 1.098q.054.02.108.03a.9.9 0 0 0 .352 0c.101-.018.208-.042.316-.06.179-.035.364-.065.555-.101.835-.143 1.766-.28 2.786-.4q.834-.1 1.748-.19c.824-.084 1.695-.156 2.602-.228 7.392-.572 17.183-.733 25.704-.47 2.339.112 4.684.226 7.022.333.209.012.418.03.621.042h.006c.412.03.811.06 1.205.096h.012a59 59 0 0 1 1.152.1h.012l.549.055h.035l.513.053h.048l.483.054c.03 0 .054.006.084.012.143.018.292.036.436.048q.044.001.089.012l.412.053c.03 0 .059.006.095.012l.394.054c.042 0 .084.012.125.018.12.018.233.03.346.048l.12.018c.113.017.22.035.334.047l.113.018.322.054q.063.008.126.024l.292.053.102.018.298.054a.848.848 0 0 0 .96-1.134z" />
|
<path d="M175.163 98.517c-2.923-7.732-3.138-25.751-.561-34.552v-.012q.205-.7.436-1.319a.86.86 0 0 0-.609-1.133.9.9 0 0 0-.286-.012q-.038-.002-.066.006c-1.259.256-2.816.477-4.6.668l-.68.072q-.688.07-1.42.13c-.973.084-1.993.162-3.049.233q-1.192.081-2.446.144c-.555.03-1.122.053-1.695.083l-1.736.072-1.552.053c-.143 0-.292.006-.435.012-.525.012-1.05.03-1.581.042l-1.82.036-1.193.018-2.268.018h-2.255c-.543 0-1.086 0-1.629-.012-.978-.012-1.945-.024-2.906-.048-.471-.012-.942-.018-1.408-.036-.125 0-.244-.006-.37-.012-.465-.012-.93-.03-1.39-.041a219 219 0 0 1-3.705-.156q-1.146-.06-2.238-.13c-3.609-.24-6.694-.574-8.848-1.01a.853.853 0 0 0-.996.633.83.83 0 0 0 .017.448c.633 2.052 1.737 5.936 2.077 9.021l.465 7.452c.137 6.725-.453 13.658-1.772 18.169q-.204.706-.435 1.33a.85.85 0 0 0 .501 1.098q.053.02.107.03a.9.9 0 0 0 .352 0c.102-.018.209-.042.316-.06.179-.035.364-.065.555-.101a53 53 0 0 1 2.787-.4 87 87 0 0 1 1.748-.19c.823-.084 1.694-.156 2.601-.228 7.393-.572 17.184-.733 25.704-.47 2.339.112 4.684.226 7.023.333.209.012.418.03.621.042h.006q.615.043 1.205.096h.012q.589.046 1.151.1h.012l.549.055h.036l.513.053h.048l.483.054c.03 0 .054.006.084.012.143.018.292.036.435.048q.045.001.09.012l.411.053q.043.001.096.012l.394.054c.041 0 .083.012.125.018.119.018.233.03.346.048l.119.018c.114.017.221.035.334.047l.114.018.322.054q.063.008.125.024l.293.053.101.018.298.054a.848.848 0 0 0 .961-1.134zM235.606 98.517c-2.924-7.732-3.139-25.751-.561-34.552v-.012q.204-.7.435-1.319a.86.86 0 0 0-.608-1.133 1 1 0 0 0-.287-.012q-.037-.002-.065.006c-1.259.256-2.817.477-4.601.668l-.68.072q-.687.07-1.42.13c-.972.084-1.993.162-3.049.233q-1.191.081-2.446.144c-.555.03-1.122.053-1.695.083l-1.736.072-1.551.053c-.143 0-.292.006-.436.012-.525.012-1.05.03-1.581.042l-1.82.036-1.193.018-2.267.018h-2.256c-.543 0-1.086 0-1.629-.012a231 231 0 0 1-2.905-.048c-.472-.012-.943-.018-1.408-.036-.126 0-.245-.006-.37-.012-.466-.012-.931-.03-1.391-.041q-1.905-.065-3.705-.156-1.145-.06-2.237-.13c-3.61-.24-6.695-.574-8.849-1.01a.853.853 0 0 0-.996.633.8.8 0 0 0 .018.448c.632 2.052 1.736 5.936 2.076 9.021l.466 7.452c.137 6.725-.454 13.658-1.773 18.169q-.204.706-.435 1.33a.85.85 0 0 0 .501 1.098q.054.02.108.03a.9.9 0 0 0 .352 0c.101-.018.208-.042.316-.06.179-.035.364-.065.555-.101.835-.143 1.766-.28 2.786-.4q.834-.1 1.748-.19c.824-.084 1.695-.156 2.602-.228 7.392-.572 17.183-.733 25.704-.47 2.339.112 4.684.226 7.022.333.209.012.418.03.621.042h.006c.412.03.811.06 1.205.096h.012a59 59 0 0 1 1.152.1h.012l.549.055h.035l.513.053h.048l.483.054c.03 0 .054.006.084.012.143.018.292.036.436.048q.044.001.089.012l.412.053c.03 0 .059.006.095.012l.394.054c.042 0 .084.012.125.018.12.018.233.03.346.048l.12.018c.113.017.22.035.334.047l.113.018.322.054q.063.008.126.024l.292.053.102.018.298.054a.848.848 0 0 0 .96-1.134z" />
|
||||||
<path d="M254.949 112.682V99.627c0-9.004-7.297-16.307-16.307-16.307H120.85c-9.004 0-16.307 7.297-16.307 16.307v13.055h150.4" />
|
<path d="M254.949 112.682V99.627c0-9.004-7.297-16.307-16.307-16.307H120.85c-9.004 0-16.307 7.297-16.307 16.307v13.055h150.4" />
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -12,7 +12,6 @@ export default function CutleryOneIcon(props: IllustrationProps) {
|
|||||||
{...props}
|
{...props}
|
||||||
{...ariaProps}
|
{...ariaProps}
|
||||||
>
|
>
|
||||||
<path fill="#fff" d="M0 .75h358v201.375H0z" />
|
|
||||||
<path
|
<path
|
||||||
fill="#cd0921"
|
fill="#cd0921"
|
||||||
d="M214.511 120.917v66.998a5.684 5.684 0 0 1-5.686 5.686h-54.847c-5.381 0-9.739-4.363-9.739-9.74V80.541c7.337 4.216 14.669 8.427 22.012 12.653 1.626.927 3.243 1.854 4.869 2.792 6.41 3.684 12.831 7.373 19.241 11.062q3.96 2.264 7.92 4.55c5.411 3.106 10.818 6.217 16.23 9.324z"
|
d="M214.511 120.917v66.998a5.684 5.684 0 0 1-5.686 5.686h-54.847c-5.381 0-9.739-4.363-9.739-9.74V80.541c7.337 4.216 14.669 8.427 22.012 12.653 1.626.927 3.243 1.854 4.869 2.792 6.41 3.684 12.831 7.373 19.241 11.062q3.96 2.264 7.92 4.55c5.411 3.106 10.818 6.217 16.23 9.324z"
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export default function CutleryTwoIcon(props: IllustrationProps) {
|
|||||||
{...props}
|
{...props}
|
||||||
{...ariaProps}
|
{...ariaProps}
|
||||||
>
|
>
|
||||||
<path fill="#fff" d="M0 .75h358v201.375H0z" />
|
|
||||||
<path
|
<path
|
||||||
fill="#cd0921"
|
fill="#cd0921"
|
||||||
d="m239.807 134.166-16.218 55.21a4.88 4.88 0 0 1-6.062 3.309l-45.197-13.277a8.366 8.366 0 0 1-5.668-10.384l25.011-85.141c5.026 5.25 10.049 10.495 15.076 15.755a923 923 0 0 1 3.337 3.48c4.391 4.587 8.788 9.181 13.178 13.773q2.715 2.824 5.425 5.667 5.559 5.805 11.117 11.612z"
|
d="m239.807 134.166-16.218 55.21a4.88 4.88 0 0 1-6.062 3.309l-45.197-13.277a8.366 8.366 0 0 1-5.668-10.384l25.011-85.141c5.026 5.25 10.049 10.495 15.076 15.755a923 923 0 0 1 3.337 3.48c4.391 4.587 8.788 9.181 13.178 13.773q2.715 2.824 5.425 5.667 5.559 5.805 11.117 11.612z"
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export default function GiftOpenIcon(props: IllustrationProps) {
|
|||||||
{...props}
|
{...props}
|
||||||
{...ariaProps}
|
{...ariaProps}
|
||||||
>
|
>
|
||||||
<path fill="#fff" d="M0 .75h358v201.375H0z" />
|
|
||||||
<g fill="#cd0921">
|
<g fill="#cd0921">
|
||||||
<path d="M139.044 79.068c1.224-24.56 2.219-25.555 26.779-26.78-24.56-1.223-25.555-2.218-26.779-26.778-1.224 24.56-2.219 25.555-26.779 26.779 24.56 1.224 25.555 2.219 26.779 26.779M261.491 55.972c-24.56 1.224-25.555 2.218-26.779 26.779-1.224-24.56-2.219-25.555-26.779-26.78 24.56-1.224 25.555-2.218 26.779-26.778 1.224 24.56 2.219 25.554 26.779 26.779M267.672 136.383c1.225-24.56 2.219-25.554 26.779-26.779-24.56-1.224-25.554-2.218-26.779-26.779-1.224 24.561-2.218 25.555-26.779 26.779 24.561 1.225 25.555 2.219 26.779 26.779M154.185 186.459l-17.479-4.928.864-39.991 16.615 4.718zM201.786 185.989l17.48-4.822-.86-39.092-16.62 4.612z" />
|
<path d="M139.044 79.068c1.224-24.56 2.219-25.555 26.779-26.78-24.56-1.223-25.555-2.218-26.779-26.778-1.224 24.56-2.219 25.555-26.779 26.779 24.56 1.224 25.555 2.219 26.779 26.779M261.491 55.972c-24.56 1.224-25.555 2.218-26.779 26.779-1.224-24.56-2.219-25.555-26.779-26.78 24.56-1.224 25.555-2.218 26.779-26.778 1.224 24.56 2.219 25.554 26.779 26.779M267.672 136.383c1.225-24.56 2.219-25.554 26.779-26.779-24.56-1.224-25.554-2.218-26.779-26.779-1.224 24.561-2.218 25.555-26.779 26.779 24.561 1.225 25.555 2.219 26.779 26.779M154.185 186.459l-17.479-4.928.864-39.991 16.615 4.718zM201.786 185.989l17.48-4.822-.86-39.092-16.62 4.612z" />
|
||||||
</g>
|
</g>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -12,7 +12,6 @@ export default function HandKeyIcon(props: IllustrationProps) {
|
|||||||
{...props}
|
{...props}
|
||||||
{...ariaProps}
|
{...ariaProps}
|
||||||
>
|
>
|
||||||
<path fill="#fff" d="M0 .375h358V201.75H0z" />
|
|
||||||
<path
|
<path
|
||||||
fill="#cd0921"
|
fill="#cd0921"
|
||||||
d="M233.791 56.101c3.926-2.673 6.515-7.188 6.515-12.303 0-7.492-5.537-13.691-12.742-14.719a14.8 14.8 0 0 0-4.206-.011c-7.239 1.01-12.798 7.21-12.798 14.724 0 5.133 2.6 9.659 6.559 12.338l-7.66 33.205h31.947v-.242l-7.609-32.997z"
|
d="M233.791 56.101c3.926-2.673 6.515-7.188 6.515-12.303 0-7.492-5.537-13.691-12.742-14.719a14.8 14.8 0 0 0-4.206-.011c-7.239 1.01-12.798 7.21-12.798 14.724 0 5.133 2.6 9.659 6.559 12.338l-7.66 33.205h31.947v-.242l-7.609-32.997z"
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export default function HotelNightIcon(props: IllustrationProps) {
|
|||||||
{...props}
|
{...props}
|
||||||
{...ariaProps}
|
{...ariaProps}
|
||||||
>
|
>
|
||||||
<path fill="#fff" d="M0 0h358v201.375H0z" />
|
|
||||||
<path
|
<path
|
||||||
fill="#cd0921"
|
fill="#cd0921"
|
||||||
d="M135.451 24.737c0-.282.255-.542.538-.542h3.377c.31 0 .537.255.537.542v7.158h8.122v-7.158c0-.282.227-.542.537-.542h3.378c.282 0 .537.255.537.542v18.796a.56.56 0 0 1-.537.542h-3.378a.54.54 0 0 1-.537-.542v-7.468h-8.122v7.468a.54.54 0 0 1-.537.542h-3.377a.56.56 0 0 1-.538-.542zM166.042 23.912c5.68 0 10.22 4.573 10.22 10.253s-4.545 10.192-10.22 10.192-10.192-4.517-10.192-10.192c0-5.676 4.517-10.253 10.192-10.253m0 15.905c3.123 0 5.68-2.558 5.68-5.652s-2.557-5.708-5.68-5.708-5.652 2.585-5.652 5.708c0 3.122 2.557 5.652 5.652 5.652M182.299 28.374h-4.058a.54.54 0 0 1-.538-.542v-3.095c0-.282.227-.542.538-.542h12.606c.31 0 .537.255.537.542v3.095a.54.54 0 0 1-.537.542h-4.059v15.164a.56.56 0 0 1-.537.542h-3.405a.56.56 0 0 1-.538-.542V28.374zM194.376 24.737c0-.282.227-.542.538-.542h11.754c.31 0 .537.255.537.542v3.095a.54.54 0 0 1-.537.542h-7.867v3.521h6.472c.283 0 .537.255.537.538v3.094c0 .31-.254.538-.537.538h-6.472v3.831h7.867c.31 0 .537.255.537.542v3.095a.54.54 0 0 1-.537.542h-11.754a.54.54 0 0 1-.538-.542zM210.796 24.737c0-.282.227-.542.537-.542h3.377c.283 0 .538.255.538.542v15.164h6.759c.311 0 .538.255.538.542v3.095a.54.54 0 0 1-.538.542h-10.679a.54.54 0 0 1-.537-.542V24.742zM135.451 49.602v128.483h87.098V49.602zm23.832 107.617h-14.826v-18.861h14.826zm0-27.496h-14.826v-18.861h14.826zm0-25.968h-14.826v-18.86h14.826zm0-25.633h-14.826v-18.86h14.826zm27.13 51.601h-14.825v-18.861h14.825zm0-25.968h-14.825v-18.86h14.825zm0-25.633h-14.825v-18.86h14.825zm27.13 78.925h-14.825v-18.86h14.825zm0-27.491h-14.825v-18.861h14.825zm0-25.972h-14.825v-18.86h14.825zm0-25.63h-14.825v-18.86h14.825z"
|
d="M135.451 24.737c0-.282.255-.542.538-.542h3.377c.31 0 .537.255.537.542v7.158h8.122v-7.158c0-.282.227-.542.537-.542h3.378c.282 0 .537.255.537.542v18.796a.56.56 0 0 1-.537.542h-3.378a.54.54 0 0 1-.537-.542v-7.468h-8.122v7.468a.54.54 0 0 1-.537.542h-3.377a.56.56 0 0 1-.538-.542zM166.042 23.912c5.68 0 10.22 4.573 10.22 10.253s-4.545 10.192-10.22 10.192-10.192-4.517-10.192-10.192c0-5.676 4.517-10.253 10.192-10.253m0 15.905c3.123 0 5.68-2.558 5.68-5.652s-2.557-5.708-5.68-5.708-5.652 2.585-5.652 5.708c0 3.122 2.557 5.652 5.652 5.652M182.299 28.374h-4.058a.54.54 0 0 1-.538-.542v-3.095c0-.282.227-.542.538-.542h12.606c.31 0 .537.255.537.542v3.095a.54.54 0 0 1-.537.542h-4.059v15.164a.56.56 0 0 1-.537.542h-3.405a.56.56 0 0 1-.538-.542V28.374zM194.376 24.737c0-.282.227-.542.538-.542h11.754c.31 0 .537.255.537.542v3.095a.54.54 0 0 1-.537.542h-7.867v3.521h6.472c.283 0 .537.255.537.538v3.094c0 .31-.254.538-.537.538h-6.472v3.831h7.867c.31 0 .537.255.537.542v3.095a.54.54 0 0 1-.537.542h-11.754a.54.54 0 0 1-.538-.542zM210.796 24.737c0-.282.227-.542.537-.542h3.377c.283 0 .538.255.538.542v15.164h6.759c.311 0 .538.255.538.542v3.095a.54.54 0 0 1-.538.542h-10.679a.54.54 0 0 1-.537-.542V24.742zM135.451 49.602v128.483h87.098V49.602zm23.832 107.617h-14.826v-18.861h14.826zm0-27.496h-14.826v-18.861h14.826zm0-25.968h-14.826v-18.86h14.826zm0-25.633h-14.826v-18.86h14.826zm27.13 51.601h-14.825v-18.861h14.825zm0-25.968h-14.825v-18.86h14.825zm0-25.633h-14.825v-18.86h14.825zm27.13 78.925h-14.825v-18.86h14.825zm0-27.491h-14.825v-18.861h14.825zm0-25.972h-14.825v-18.86h14.825zm0-25.63h-14.825v-18.86h14.825z"
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export default function KidsIcon(props: IllustrationProps) {
|
|||||||
{...props}
|
{...props}
|
||||||
{...ariaProps}
|
{...ariaProps}
|
||||||
>
|
>
|
||||||
<path fill="#fff" d="M0 .125h358V201.5H0z" />
|
|
||||||
<path
|
<path
|
||||||
fill="#cd0921"
|
fill="#cd0921"
|
||||||
d="M118.57 86.176 99.517 70.244a4.12 4.12 0 0 0-5.56.247l-.814.814a4.125 4.125 0 0 0-.253 5.554l15.898 19.082a4.12 4.12 0 0 0 6.082.275l3.964-3.964a4.12 4.12 0 0 0-.269-6.076zm-4.284 3.425-1.988 1.988c-.86.86-2.539.522-3.634-.724l-11.18-12.776c-.96-1.095-1.118-2.516-.36-3.268l.405-.405c.758-.758 2.179-.595 3.274.365l12.758 11.198c1.247 1.09 1.578 2.768.719 3.628zM269.276 77.225a37.8 37.8 0 0 0-5.048-8.39c-1.174-1.477-10.726-12.074-27.376-15.213-24.226-4.565-48.171 9.53-48.053 16.656.023 1.522.567 5.7-.494 10.367-.09.398-1.033.662-.73 1.117.241.365.174.416 1.64.438a9 9 0 0 1 2.555.421 12.3 12.3 0 0 1 4.532 3.853 12 12 0 0 1 1.623 3.195c12.343-.309 18.582 1.505 29.201 3.751 10.417 2.207 25.152 5.958 41.842 22.182 2.268.005 5.626-.32 9.08-2.072 6.643-3.37 11.349-10.844 11.276-16.258-.09-6.963-8.126-14.432-20.048-20.042z"
|
d="M118.57 86.176 99.517 70.244a4.12 4.12 0 0 0-5.56.247l-.814.814a4.125 4.125 0 0 0-.253 5.554l15.898 19.082a4.12 4.12 0 0 0 6.082.275l3.964-3.964a4.12 4.12 0 0 0-.269-6.076zm-4.284 3.425-1.988 1.988c-.86.86-2.539.522-3.634-.724l-11.18-12.776c-.96-1.095-1.118-2.516-.36-3.268l.405-.405c.758-.758 2.179-.595 3.274.365l12.758 11.198c1.247 1.09 1.578 2.768.719 3.628zM269.276 77.225a37.8 37.8 0 0 0-5.048-8.39c-1.174-1.477-10.726-12.074-27.376-15.213-24.226-4.565-48.171 9.53-48.053 16.656.023 1.522.567 5.7-.494 10.367-.09.398-1.033.662-.73 1.117.241.365.174.416 1.64.438a9 9 0 0 1 2.555.421 12.3 12.3 0 0 1 4.532 3.853 12 12 0 0 1 1.623 3.195c12.343-.309 18.582 1.505 29.201 3.751 10.417 2.207 25.152 5.958 41.842 22.182 2.268.005 5.626-.32 9.08-2.072 6.643-3.37 11.349-10.844 11.276-16.258-.09-6.963-8.126-14.432-20.048-20.042z"
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export default function KidsMocktailIcon(props: IllustrationProps) {
|
|||||||
{...props}
|
{...props}
|
||||||
{...ariaProps}
|
{...ariaProps}
|
||||||
>
|
>
|
||||||
<path fill="#fff" d="M0 0h358v201.375H0z" />
|
|
||||||
<path
|
<path
|
||||||
fill="#4d001b"
|
fill="#4d001b"
|
||||||
d="M214.311 85.868c-.189.043-.082.166.435.169l.376-.182zM217.741 86.554c.615-.03 1.333 0 .988-.28-.63.048-.769-.018-1.444.1-.512-.055-.883-.2-.789-.26-.185.015-.802-.093-1.088.013.331.102.709.136 1.11.196.401.066.832.101 1.283.106.181.072-.011.095-.06.125M234.579 128.048l.035-.318a3 3 0 0 0-.035.318M215.073 148.9c.218-.092.302-.146.306-.168-.199.094-.361.171-.306.168M210.824 152.428c.05-.118.083-.221.134-.324-.07.115-.119.242-.134.324M234.578 128.049a7 7 0 0 1-.121.809c.058.063.105-.411.121-.809M224.47 88.397c-.154-.09-.3-.13-.432-.169.134.058.268.105.393.182.012-.006.022-.01.039-.014M234.461 114.389l-.001-.252a1.4 1.4 0 0 0-.081-.209zM234.188 103.979c.028.229.208.287.295.367a1.3 1.3 0 0 1-.192-.489c-.032.002-.066.038-.103.122M226.102 90.25l-.248-.156zM234.424 121.9a.2.2 0 0 0-.061.096c.021 0 .042-.038.061-.096M207.532 78.325c-.012-.008-.02.004-.031.001.018.061.03.08.031 0M214.174 86.309l-.169.005c-.019.031-.039.062-.04.094zM208.604 161.143l.03-.022c.149-.165.06-.076-.03.022M148.897 137.848c-.152 1.008-.195-1.64-.364-.123.067-.141.265 1.379.364.123M224.697 88.56l.377.228a5 5 0 0 0-.377-.227M148.533 137.725h-.001l-.016.156zM189.544 168.344a.9.9 0 0 0 .233.052c-.01-.017-.063-.033-.233-.052M203.745 163.244a2.1 2.1 0 0 0-.453.278c.17-.127.319-.212.453-.278M180.989 169.08c-.223-.011-.299-.033-.603-.007a1 1 0 0 0 .093.019c.11-.024.256-.036.51-.012M172.06 168.717l.162.048.283.015zM198.443 58.135c.194.06.313.12.406.177-.132-.129 1.19-.091-.406-.177M224.428 88.483c.066-.01.144.013.269.078l-.266-.15c-.035.007-.052.025-.003.072M180.639 170.421c-.481-.004-.961-.002-1.443-.021.405.026.928.026 1.443.021M152.164 78.53l-.015-.305a1.3 1.3 0 0 0 .015.305M174.913 170.276a6 6 0 0 0-.801-.172c-.061.009-.129.015-.17.032z"
|
d="M214.311 85.868c-.189.043-.082.166.435.169l.376-.182zM217.741 86.554c.615-.03 1.333 0 .988-.28-.63.048-.769-.018-1.444.1-.512-.055-.883-.2-.789-.26-.185.015-.802-.093-1.088.013.331.102.709.136 1.11.196.401.066.832.101 1.283.106.181.072-.011.095-.06.125M234.579 128.048l.035-.318a3 3 0 0 0-.035.318M215.073 148.9c.218-.092.302-.146.306-.168-.199.094-.361.171-.306.168M210.824 152.428c.05-.118.083-.221.134-.324-.07.115-.119.242-.134.324M234.578 128.049a7 7 0 0 1-.121.809c.058.063.105-.411.121-.809M224.47 88.397c-.154-.09-.3-.13-.432-.169.134.058.268.105.393.182.012-.006.022-.01.039-.014M234.461 114.389l-.001-.252a1.4 1.4 0 0 0-.081-.209zM234.188 103.979c.028.229.208.287.295.367a1.3 1.3 0 0 1-.192-.489c-.032.002-.066.038-.103.122M226.102 90.25l-.248-.156zM234.424 121.9a.2.2 0 0 0-.061.096c.021 0 .042-.038.061-.096M207.532 78.325c-.012-.008-.02.004-.031.001.018.061.03.08.031 0M214.174 86.309l-.169.005c-.019.031-.039.062-.04.094zM208.604 161.143l.03-.022c.149-.165.06-.076-.03.022M148.897 137.848c-.152 1.008-.195-1.64-.364-.123.067-.141.265 1.379.364.123M224.697 88.56l.377.228a5 5 0 0 0-.377-.227M148.533 137.725h-.001l-.016.156zM189.544 168.344a.9.9 0 0 0 .233.052c-.01-.017-.063-.033-.233-.052M203.745 163.244a2.1 2.1 0 0 0-.453.278c.17-.127.319-.212.453-.278M180.989 169.08c-.223-.011-.299-.033-.603-.007a1 1 0 0 0 .093.019c.11-.024.256-.036.51-.012M172.06 168.717l.162.048.283.015zM198.443 58.135c.194.06.313.12.406.177-.132-.129 1.19-.091-.406-.177M224.428 88.483c.066-.01.144.013.269.078l-.266-.15c-.035.007-.052.025-.003.072M180.639 170.421c-.481-.004-.961-.002-1.443-.021.405.026.928.026 1.443.021M152.164 78.53l-.015-.305a1.3 1.3 0 0 0 .015.305M174.913 170.276a6 6 0 0 0-.801-.172c-.061.009-.129.015-.17.032z"
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export default function MoneyHandIcon(props: IllustrationProps) {
|
|||||||
{...props}
|
{...props}
|
||||||
{...ariaProps}
|
{...ariaProps}
|
||||||
>
|
>
|
||||||
<path fill="#fff" d="M0 .375h358V201.75H0z" />
|
|
||||||
<path
|
<path
|
||||||
fill="#cd0921"
|
fill="#cd0921"
|
||||||
d="M182.948 70.156c3.461 1.57 5.572 3.66 5.572 5.955v4.74c4.967 1.614 8.158 4.127 8.158 6.948v5.627c0 2.413-2.335 4.6-6.119 6.194l.001 5.231c0 1.128-.511 2.207-1.441 3.199.8.929 1.236 1.93 1.236 2.973v5.626c0 .539-.116 1.066-.339 1.578 1.647 1.253 2.583 2.69 2.583 4.217v1.31c-13.253 3.856-26.384 8.648-33.773 11.461-5.31-1.611-8.756-4.209-8.756-7.144v-5.627c0-.538.116-1.066.339-1.578-1.646-1.252-2.581-2.689-2.581-4.217v-5.626c0-1.129.511-2.208 1.441-3.2-.801-.928-1.237-1.929-1.237-2.972v-5.627c0-2.413 2.335-4.6 6.119-6.193v-4.344c-4.967-1.616-8.158-4.128-8.158-6.95v-5.626c0-3.178 4.048-5.963 10.121-7.517-4.525-3.584-7.428-9.127-7.428-15.348 0-10.807 8.76-19.568 19.567-19.568h4.843c10.807 0 19.568 8.76 19.568 19.568 0 7.214-3.905 13.517-9.716 16.91"
|
d="M182.948 70.156c3.461 1.57 5.572 3.66 5.572 5.955v4.74c4.967 1.614 8.158 4.127 8.158 6.948v5.627c0 2.413-2.335 4.6-6.119 6.194l.001 5.231c0 1.128-.511 2.207-1.441 3.199.8.929 1.236 1.93 1.236 2.973v5.626c0 .539-.116 1.066-.339 1.578 1.647 1.253 2.583 2.69 2.583 4.217v1.31c-13.253 3.856-26.384 8.648-33.773 11.461-5.31-1.611-8.756-4.209-8.756-7.144v-5.627c0-.538.116-1.066.339-1.578-1.646-1.252-2.581-2.689-2.581-4.217v-5.626c0-1.129.511-2.208 1.441-3.2-.801-.928-1.237-1.929-1.237-2.972v-5.627c0-2.413 2.335-4.6 6.119-6.193v-4.344c-4.967-1.616-8.158-4.128-8.158-6.95v-5.626c0-3.178 4.048-5.963 10.121-7.517-4.525-3.584-7.428-9.127-7.428-15.348 0-10.807 8.76-19.568 19.567-19.568h4.843c10.807 0 19.568 8.76 19.568 19.568 0 7.214-3.905 13.517-9.716 16.91"
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ export enum IconName {
|
|||||||
GiftOpen = 'GiftOpen',
|
GiftOpen = 'GiftOpen',
|
||||||
Globe = 'Globe',
|
Globe = 'Globe',
|
||||||
Golf = 'Golf',
|
Golf = 'Golf',
|
||||||
|
Granola = 'Granola',
|
||||||
Guard = 'Guard',
|
Guard = 'Guard',
|
||||||
Hairdresser = 'Hairdresser',
|
Hairdresser = 'Hairdresser',
|
||||||
HairdryerInRoomAllScandic = 'HairdryerInRoomAllScandic',
|
HairdryerInRoomAllScandic = 'HairdryerInRoomAllScandic',
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
|
import { LinkList } from './index'
|
||||||
|
import { IconName } from '../Icons/iconName'
|
||||||
|
import type { LinkListItemProps } from './LinkListItem'
|
||||||
|
|
||||||
|
const meta: Meta<typeof LinkList> = {
|
||||||
|
title: 'Components/LinkList',
|
||||||
|
component: LinkList,
|
||||||
|
argTypes: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default meta
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof LinkList>
|
||||||
|
|
||||||
|
const illustrationItems = [
|
||||||
|
{
|
||||||
|
text: 'First Link Item',
|
||||||
|
isExternal: false,
|
||||||
|
illustration: {
|
||||||
|
illustration: 'Granola' as IconName,
|
||||||
|
size: 'medium',
|
||||||
|
},
|
||||||
|
} as LinkListItemProps,
|
||||||
|
{
|
||||||
|
text: 'Second Link Item',
|
||||||
|
isExternal: true,
|
||||||
|
illustration: {
|
||||||
|
illustration: 'Coin' as IconName,
|
||||||
|
size: 'small',
|
||||||
|
},
|
||||||
|
} as LinkListItemProps,
|
||||||
|
]
|
||||||
|
|
||||||
|
const textItems = [
|
||||||
|
{
|
||||||
|
text: 'First Link Item',
|
||||||
|
isExternal: false,
|
||||||
|
} as LinkListItemProps,
|
||||||
|
{
|
||||||
|
text: 'Second Link Item',
|
||||||
|
isExternal: true,
|
||||||
|
} as LinkListItemProps,
|
||||||
|
]
|
||||||
|
|
||||||
|
export const IllustrationLinkList: Story = {
|
||||||
|
args: {
|
||||||
|
linkListItems: illustrationItems,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TextOnlyLinkList: Story = {
|
||||||
|
args: {
|
||||||
|
linkListItems: textItems,
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { IconProps, LogoAndIllustrationProps } from '../../../Icons'
|
||||||
|
import { IconName } from '../../../Icons/iconName'
|
||||||
|
import { IllustrationByIconName } from '../../../Icons/IllustrationByIconName'
|
||||||
|
import { FC } from 'react'
|
||||||
|
|
||||||
|
export interface IllustrationIconProps extends LogoAndIllustrationProps {
|
||||||
|
illustration: IconName
|
||||||
|
size?: 'small' | 'medium' | 'large'
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapIllustrationToIcon(illustration: IconName): FC<IconProps> | null {
|
||||||
|
if (!IllustrationByIconName(illustration)) return null
|
||||||
|
return IllustrationByIconName(illustration)
|
||||||
|
}
|
||||||
|
|
||||||
|
const sizeMap = {
|
||||||
|
small: { width: 60, height: 60 },
|
||||||
|
medium: { width: 80, height: 80 },
|
||||||
|
large: { width: 120, height: 120 },
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export function IllustrationIcon({
|
||||||
|
illustration,
|
||||||
|
size = 'large',
|
||||||
|
...props
|
||||||
|
}: IllustrationIconProps) {
|
||||||
|
const IllustrationComponent = mapIllustrationToIcon(illustration)
|
||||||
|
if (!IllustrationComponent) return null
|
||||||
|
return (
|
||||||
|
<IllustrationComponent
|
||||||
|
{...props}
|
||||||
|
height={sizeMap[size].height}
|
||||||
|
width={sizeMap[size].width}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||||
|
import { LinkListItem } from './index'
|
||||||
|
import { IconName } from '../../Icons/iconName'
|
||||||
|
|
||||||
|
const meta: Meta<typeof LinkListItem> = {
|
||||||
|
title: 'Components/LinkListItem',
|
||||||
|
component: LinkListItem,
|
||||||
|
argTypes: {
|
||||||
|
isExternal: {
|
||||||
|
control: { type: 'boolean' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default meta
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof LinkListItem>
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
text: 'Link Item',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IllustrationItem: Story = {
|
||||||
|
args: {
|
||||||
|
text: 'Link Item',
|
||||||
|
isExternal: false,
|
||||||
|
illustration: {
|
||||||
|
illustration: 'Granola' as IconName,
|
||||||
|
size: 'medium',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TextOnlyItem: Story = {
|
||||||
|
args: {
|
||||||
|
text: 'Link Item',
|
||||||
|
isExternal: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { MaterialIcon } from '../../Icons/MaterialIcon'
|
||||||
|
import { Typography } from '../../Typography'
|
||||||
|
|
||||||
|
import styles from './linkListItem.module.css'
|
||||||
|
import {
|
||||||
|
IllustrationIcon,
|
||||||
|
IllustrationIconProps,
|
||||||
|
} from '../LinkListItem/IllustrationIcon'
|
||||||
|
|
||||||
|
import { cx } from 'class-variance-authority'
|
||||||
|
import { Link } from 'react-aria-components'
|
||||||
|
|
||||||
|
export interface LinkListItemProps {
|
||||||
|
text: string
|
||||||
|
isExternal?: boolean
|
||||||
|
href: string
|
||||||
|
className?: string
|
||||||
|
illustration?: IllustrationIconProps
|
||||||
|
onClick?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LinkListItem({
|
||||||
|
text,
|
||||||
|
isExternal,
|
||||||
|
href,
|
||||||
|
illustration,
|
||||||
|
onClick,
|
||||||
|
}: LinkListItemProps) {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href={href}
|
||||||
|
target={'_blank'}
|
||||||
|
onClick={onClick}
|
||||||
|
className={cx(styles.content, {
|
||||||
|
[styles.graphic]: illustration,
|
||||||
|
[styles.noGraphic]: !illustration,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{illustration && (
|
||||||
|
<div className={styles.illustrationWrapper}>
|
||||||
|
<IllustrationIcon
|
||||||
|
illustration={illustration.illustration}
|
||||||
|
size={illustration.size}
|
||||||
|
className={styles.illustration}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Typography variant="Body/Paragraph/mdBold">
|
||||||
|
<p>{text}</p>
|
||||||
|
</Typography>
|
||||||
|
<MaterialIcon
|
||||||
|
color="Icon/Interactive/Default"
|
||||||
|
icon={isExternal ? 'open_in_new' : 'arrow_forward'}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
.list {
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkListItem {
|
||||||
|
border: 1px solid var(--Base-Border-Subtle);
|
||||||
|
background-color: var(--Surface-Primary-Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkListItem + .linkListItem {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkListItem:first-child {
|
||||||
|
border-radius: var(--Corner-radius-md) var(--Corner-radius-md) 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkListItem:last-child {
|
||||||
|
border-radius: 0 0 var(--Corner-radius-md) var(--Corner-radius-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkListItem:hover {
|
||||||
|
background-color: var(--Surface-Primary-Hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: grid;
|
||||||
|
align-items: center;
|
||||||
|
padding: var(--Space-x2);
|
||||||
|
gap: var(--Space-x2);
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--Text-Interactive-Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
.graphic {
|
||||||
|
grid-template-columns: 80px 1fr auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noGraphic {
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.illustrationWrapper {
|
||||||
|
position: relative;
|
||||||
|
border-radius: var(--Corner-radius-sm);
|
||||||
|
background-color: var(--Surface-Primary-Hover-Accent);
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.illustration {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
23
packages/design-system/lib/components/LinkList/index.tsx
Normal file
23
packages/design-system/lib/components/LinkList/index.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { LinkListItem, type LinkListItemProps } from './LinkListItem'
|
||||||
|
import styles from './LinkListItem/linkListItem.module.css'
|
||||||
|
export interface LinkListProps {
|
||||||
|
linkListItems: LinkListItemProps[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LinkList({ linkListItems }: LinkListProps) {
|
||||||
|
return (
|
||||||
|
<ul className={styles.list}>
|
||||||
|
{linkListItems.map((item, index) => (
|
||||||
|
<li className={styles.linkListItem} key={index}>
|
||||||
|
<LinkListItem
|
||||||
|
text={item.text}
|
||||||
|
isExternal={item.isExternal}
|
||||||
|
href={item.href}
|
||||||
|
illustration={item.illustration}
|
||||||
|
onClick={item.onClick}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
/* For removing focus outline when modal opens first time */
|
/* For removing focus outline when modal opens first time */
|
||||||
outline: 0 none;
|
outline: 0 none;
|
||||||
|
|
||||||
max-height: 100dvh;
|
max-height: 90dvh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
"./Icons/GiftOpenIcon": "./lib/components/Icons/Illustrations/GiftOpen.tsx",
|
"./Icons/GiftOpenIcon": "./lib/components/Icons/Illustrations/GiftOpen.tsx",
|
||||||
"./Icons/GrandHotelOsloIcon": "./lib/components/Icons/Logos/GrandHotelOslo.tsx",
|
"./Icons/GrandHotelOsloIcon": "./lib/components/Icons/Logos/GrandHotelOslo.tsx",
|
||||||
"./Icons/HairdresserIcon": "./lib/components/Icons/Nucleo/Amenities_Facilities/hairdresser-1.tsx",
|
"./Icons/HairdresserIcon": "./lib/components/Icons/Nucleo/Amenities_Facilities/hairdresser-1.tsx",
|
||||||
|
"./Icons/GranolaIcon": "./lib/components/Icons/Illustrations/Granola.tsx",
|
||||||
"./Icons/HairdryerIcon": "./lib/components/Icons/Customised/Amenities_Facilities/Hairdryer.tsx",
|
"./Icons/HairdryerIcon": "./lib/components/Icons/Customised/Amenities_Facilities/Hairdryer.tsx",
|
||||||
"./Icons/HandKeyIcon": "./lib/components/Icons/Illustrations/HandKey.tsx",
|
"./Icons/HandKeyIcon": "./lib/components/Icons/Illustrations/HandKey.tsx",
|
||||||
"./Icons/HandGiftIcon": "./lib/components/Icons/Illustrations/HandGift.tsx",
|
"./Icons/HandGiftIcon": "./lib/components/Icons/Illustrations/HandGift.tsx",
|
||||||
@@ -145,6 +146,8 @@
|
|||||||
"./Label": "./lib/components/Label/index.tsx",
|
"./Label": "./lib/components/Label/index.tsx",
|
||||||
"./Lightbox": "./lib/components/Lightbox/index.tsx",
|
"./Lightbox": "./lib/components/Lightbox/index.tsx",
|
||||||
"./Link": "./lib/components/Link/index.tsx",
|
"./Link": "./lib/components/Link/index.tsx",
|
||||||
|
"./LinkList": "./lib/components/LinkList/index.tsx",
|
||||||
|
"./LinkListItem": "./lib/components/LinkList/LinkListItem/index.tsx",
|
||||||
"./LoadingSpinner": "./lib/components/LoadingSpinner/index.tsx",
|
"./LoadingSpinner": "./lib/components/LoadingSpinner/index.tsx",
|
||||||
"./LocalCallCharges": "./lib/components/LocalCallCharges/index.tsx",
|
"./LocalCallCharges": "./lib/components/LocalCallCharges/index.tsx",
|
||||||
"./LoginButton": "./lib/components/LoginButton/index.tsx",
|
"./LoginButton": "./lib/components/LoginButton/index.tsx",
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ export type TrackingSDKHotelInfo = {
|
|||||||
rewardNight?: string
|
rewardNight?: string
|
||||||
rewardNightAvailability?: string
|
rewardNightAvailability?: string
|
||||||
points?: number | string // Should be sent on confirmation page and enter-details page
|
points?: number | string // Should be sent on confirmation page and enter-details page
|
||||||
|
pointsModalConversion?: string // If the booking involved points and the points modal
|
||||||
roomPrice?: number | string
|
roomPrice?: number | string
|
||||||
roomTypeCode?: string
|
roomTypeCode?: string
|
||||||
roomTypeName?: string
|
roomTypeName?: string
|
||||||
|
|||||||
117
packages/trpc/lib/graphql/Query/UsePointsModal.graphql.ts
Normal file
117
packages/trpc/lib/graphql/Query/UsePointsModal.graphql.ts
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import { gql } from "graphql-tag"
|
||||||
|
|
||||||
|
import { AccountPageRef } from "../Fragments/AccountPage/Ref.graphql"
|
||||||
|
import { CampaignOverviewPageRef } from "../Fragments/CampaignOverviewPage/Ref.graphql"
|
||||||
|
import { CampaignPageRef } from "../Fragments/CampaignPage/Ref.graphql"
|
||||||
|
import { CollectionPageRef } from "../Fragments/CollectionPage/Ref.graphql"
|
||||||
|
import { ContentPageRef } from "../Fragments/ContentPage/Ref.graphql"
|
||||||
|
import { DestinationCityPageRef } from "../Fragments/DestinationCityPage/Ref.graphql"
|
||||||
|
import { DestinationCountryPageRef } from "../Fragments/DestinationCountryPage/Ref.graphql"
|
||||||
|
import { DestinationOverviewPageRef } from "../Fragments/DestinationOverviewPage/Ref.graphql"
|
||||||
|
import { HotelPageRef } from "../Fragments/HotelPage/Ref.graphql"
|
||||||
|
import { LoyaltyPageRef } from "../Fragments/LoyaltyPage/Ref.graphql"
|
||||||
|
import { AccountPageLink } from "../Fragments/PageLink/AccountPageLink.graphql"
|
||||||
|
import { CampaignOverviewPageLink } from "../Fragments/PageLink/CampaignOverviewPageLink.graphql"
|
||||||
|
import { CampaignPageLink } from "../Fragments/PageLink/CampaignPageLink.graphql"
|
||||||
|
import { CollectionPageLink } from "../Fragments/PageLink/CollectionPageLink.graphql"
|
||||||
|
import { ContentPageLink } from "../Fragments/PageLink/ContentPageLink.graphql"
|
||||||
|
import { DestinationCityPageLink } from "../Fragments/PageLink/DestinationCityPageLink.graphql"
|
||||||
|
import { DestinationCountryPageLink } from "../Fragments/PageLink/DestinationCountryPageLink.graphql"
|
||||||
|
import { DestinationOverviewPageLink } from "../Fragments/PageLink/DestinationOverviewPageLink.graphql"
|
||||||
|
import { HotelPageLink } from "../Fragments/PageLink/HotelPageLink.graphql"
|
||||||
|
import { LoyaltyPageLink } from "../Fragments/PageLink/LoyaltyPageLink.graphql"
|
||||||
|
import { PromoCampaignPageLink } from "../Fragments/PageLink/PromoCampaignPageLink.graphql"
|
||||||
|
import { StartPageLink } from "../Fragments/PageLink/StartPageLink.graphql"
|
||||||
|
import { PromoCampaignPageRef } from "../Fragments/PromoCampaignPage/Ref.graphql"
|
||||||
|
import { StartPageRef } from "../Fragments/StartPage/Ref.graphql"
|
||||||
|
|
||||||
|
export const GetUsePointsModal = gql`
|
||||||
|
query GetUsePointsModal($locale: String!) {
|
||||||
|
all_usepointsmodal(locale: $locale) {
|
||||||
|
items {
|
||||||
|
image
|
||||||
|
link_group {
|
||||||
|
link_text
|
||||||
|
illustration
|
||||||
|
illustration_size
|
||||||
|
is_contentstack_link
|
||||||
|
external_link {
|
||||||
|
href
|
||||||
|
}
|
||||||
|
linkConnection {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
__typename
|
||||||
|
...AccountPageLink
|
||||||
|
...CampaignOverviewPageLink
|
||||||
|
...CampaignPageLink
|
||||||
|
...CollectionPageLink
|
||||||
|
...ContentPageLink
|
||||||
|
...DestinationCityPageLink
|
||||||
|
...DestinationCountryPageLink
|
||||||
|
...DestinationOverviewPageLink
|
||||||
|
...HotelPageLink
|
||||||
|
...LoyaltyPageLink
|
||||||
|
...StartPageLink
|
||||||
|
...PromoCampaignPageLink
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${AccountPageLink}
|
||||||
|
${CampaignOverviewPageLink}
|
||||||
|
${CampaignPageLink}
|
||||||
|
${CollectionPageLink}
|
||||||
|
${ContentPageLink}
|
||||||
|
${DestinationCityPageLink}
|
||||||
|
${DestinationCountryPageLink}
|
||||||
|
${DestinationOverviewPageLink}
|
||||||
|
${HotelPageLink}
|
||||||
|
${LoyaltyPageLink}
|
||||||
|
${StartPageLink}
|
||||||
|
${PromoCampaignPageLink}
|
||||||
|
`
|
||||||
|
export const GetUsePointsModalRefs = gql`
|
||||||
|
query GetUsePointsModalRefs($locale: String!) {
|
||||||
|
all_usepointsmodal(locale: $locale) {
|
||||||
|
items {
|
||||||
|
link_group {
|
||||||
|
linkConnection {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
__typename
|
||||||
|
...AccountPageRef
|
||||||
|
...CampaignOverviewPageRef
|
||||||
|
...CampaignPageRef
|
||||||
|
...CollectionPageRef
|
||||||
|
...ContentPageRef
|
||||||
|
...DestinationCityPageRef
|
||||||
|
...DestinationCountryPageRef
|
||||||
|
...DestinationOverviewPageRef
|
||||||
|
...HotelPageRef
|
||||||
|
...LoyaltyPageRef
|
||||||
|
...StartPageRef
|
||||||
|
...PromoCampaignPageRef
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${AccountPageRef}
|
||||||
|
${CampaignOverviewPageRef}
|
||||||
|
${CampaignPageRef}
|
||||||
|
${CollectionPageRef}
|
||||||
|
${ContentPageRef}
|
||||||
|
${DestinationCityPageRef}
|
||||||
|
${DestinationCountryPageRef}
|
||||||
|
${DestinationOverviewPageRef}
|
||||||
|
${HotelPageRef}
|
||||||
|
${LoyaltyPageRef}
|
||||||
|
${StartPageRef}
|
||||||
|
${PromoCampaignPageRef}
|
||||||
|
`
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { mergeRouters } from "../../.."
|
||||||
|
import { usePointsModalQueryRouter } from "./query"
|
||||||
|
|
||||||
|
export const usePointsModalRouter = mergeRouters(usePointsModalQueryRouter)
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { transformedImageVaultAssetSchema } from "@scandic-hotels/common/utils/imageVault"
|
||||||
|
import { IconName } from "@scandic-hotels/design-system/Icons/iconName"
|
||||||
|
|
||||||
|
import { linkConnectionRefs } from "../schemas/linkConnection"
|
||||||
|
import { linkUnionSchema, transformPageLink } from "../schemas/pageLinks"
|
||||||
|
|
||||||
|
export type UsePointsModalData = z.output<typeof usePointsModalSchema>
|
||||||
|
export type UsePointsModalRefsData = z.output<typeof usePointsModalRefsSchema>
|
||||||
|
|
||||||
|
export const linkConnectionSchema = z
|
||||||
|
.object({
|
||||||
|
edges: z.array(
|
||||||
|
z.object({
|
||||||
|
node: linkUnionSchema,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
})
|
||||||
|
.transform((data) => {
|
||||||
|
if (data.edges.length) {
|
||||||
|
const linkNode = data.edges[0].node
|
||||||
|
if (linkNode) {
|
||||||
|
const link = transformPageLink(linkNode)
|
||||||
|
if (link && link.url) {
|
||||||
|
return link.url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
const usePointsModalItemsSchema = z.object({
|
||||||
|
image: transformedImageVaultAssetSchema,
|
||||||
|
link_group: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
link_text: z.string().default(""),
|
||||||
|
is_contentstack_link: z.boolean(),
|
||||||
|
illustration: z.nativeEnum(IconName).nullish(),
|
||||||
|
illustration_size: z
|
||||||
|
.enum(["small", "medium", "large"])
|
||||||
|
.nullish()
|
||||||
|
.default("large"),
|
||||||
|
external_link: z
|
||||||
|
.object({
|
||||||
|
href: z.string().default(""),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
linkConnection: linkConnectionSchema,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.transform((links) =>
|
||||||
|
links.map((link) => ({
|
||||||
|
text: link.link_text || "",
|
||||||
|
isExternal: !link.is_contentstack_link,
|
||||||
|
href: link.is_contentstack_link
|
||||||
|
? link.linkConnection || ""
|
||||||
|
: link.external_link?.href || "",
|
||||||
|
illustration: link.illustration
|
||||||
|
? {
|
||||||
|
illustration: link.illustration,
|
||||||
|
size: link.illustration_size || "large",
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
}))
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const usePointsModalSchema = z.object({
|
||||||
|
all_usepointsmodal: z.object({
|
||||||
|
items: z.array(usePointsModalItemsSchema),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const usePointsModalRefsSchema = z.object({
|
||||||
|
all_usepointsmodal: z.object({
|
||||||
|
items: z.array(
|
||||||
|
z.object({
|
||||||
|
link_group: z.array(linkConnectionRefs),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
})
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import { createCounter } from "@scandic-hotels/common/telemetry"
|
||||||
|
|
||||||
|
import { router } from "../../.."
|
||||||
|
import { notFound } from "../../../errors"
|
||||||
|
import {
|
||||||
|
GetUsePointsModal,
|
||||||
|
GetUsePointsModalRefs,
|
||||||
|
} from "../../../graphql/Query/UsePointsModal.graphql"
|
||||||
|
import { request } from "../../../graphql/request"
|
||||||
|
import { contentstackBaseProcedure } from "../../../procedures"
|
||||||
|
import {
|
||||||
|
type UsePointsModalData,
|
||||||
|
type UsePointsModalRefsData,
|
||||||
|
usePointsModalRefsSchema,
|
||||||
|
usePointsModalSchema,
|
||||||
|
} from "./output"
|
||||||
|
|
||||||
|
export const usePointsModalQueryRouter = router({
|
||||||
|
get: contentstackBaseProcedure.query(async ({ ctx }) => {
|
||||||
|
const { lang, uid } = ctx
|
||||||
|
|
||||||
|
const getRefsCounter = createCounter(
|
||||||
|
"trpc.contentstack",
|
||||||
|
"usePointsModal.get.refs"
|
||||||
|
)
|
||||||
|
const metricsRefs = getRefsCounter.init({
|
||||||
|
lang,
|
||||||
|
uid,
|
||||||
|
})
|
||||||
|
|
||||||
|
metricsRefs.start()
|
||||||
|
const refsResponse = await request<UsePointsModalRefsData>(
|
||||||
|
GetUsePointsModalRefs,
|
||||||
|
{ locale: lang },
|
||||||
|
{
|
||||||
|
key: `contentstack:usePointsModal:${lang}:refs`,
|
||||||
|
ttl: "max",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (!refsResponse.data) {
|
||||||
|
const notFoundError = notFound(refsResponse)
|
||||||
|
metricsRefs.noDataError()
|
||||||
|
throw notFoundError
|
||||||
|
}
|
||||||
|
|
||||||
|
const validatedRefsData = usePointsModalRefsSchema.safeParse(
|
||||||
|
refsResponse.data
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!validatedRefsData.success) {
|
||||||
|
metricsRefs.validationError(validatedRefsData.error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
metricsRefs.success()
|
||||||
|
|
||||||
|
const getCounter = createCounter("trpc.contentstack", "usePointsModal.get")
|
||||||
|
|
||||||
|
const metrics = getCounter.init({
|
||||||
|
lang,
|
||||||
|
uid,
|
||||||
|
})
|
||||||
|
metrics.start()
|
||||||
|
|
||||||
|
const response = await request<UsePointsModalData>(
|
||||||
|
GetUsePointsModal,
|
||||||
|
{ locale: lang },
|
||||||
|
{
|
||||||
|
key: `contentstack:usePointsModal:${lang}`,
|
||||||
|
ttl: "max",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!response.data) {
|
||||||
|
const notFoundError = notFound(response)
|
||||||
|
metrics.noDataError()
|
||||||
|
throw notFoundError
|
||||||
|
}
|
||||||
|
|
||||||
|
const validatedResponse = usePointsModalSchema.safeParse(response.data)
|
||||||
|
|
||||||
|
if (!validatedResponse.success) {
|
||||||
|
metrics.validationError(validatedResponse.error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
metrics.success()
|
||||||
|
return validatedResponse.data
|
||||||
|
}),
|
||||||
|
})
|
||||||
@@ -20,6 +20,7 @@ import { profilingConsentRouter } from "./profilingConsent"
|
|||||||
import { promoCampaignPageRouter } from "./promoCampaignPage"
|
import { promoCampaignPageRouter } from "./promoCampaignPage"
|
||||||
import { rewardRouter } from "./reward"
|
import { rewardRouter } from "./reward"
|
||||||
import { startPageRouter } from "./startPage"
|
import { startPageRouter } from "./startPage"
|
||||||
|
import { usePointsModalRouter } from "./UsePointsModal"
|
||||||
|
|
||||||
export const contentstackRouter = router({
|
export const contentstackRouter = router({
|
||||||
accountPage: accountPageRouter,
|
accountPage: accountPageRouter,
|
||||||
@@ -43,4 +44,5 @@ export const contentstackRouter = router({
|
|||||||
partner: partnerRouter,
|
partner: partnerRouter,
|
||||||
promoCampaignPage: promoCampaignPageRouter,
|
promoCampaignPage: promoCampaignPageRouter,
|
||||||
profilingConsent: profilingConsentRouter,
|
profilingConsent: profilingConsentRouter,
|
||||||
|
usePointsModal: usePointsModalRouter,
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user