Compare commits
12 Commits
fbdbd35813
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1dffeb6be7 | ||
|
|
1f1ed2e4f3 | ||
|
|
bc9eaf6706 | ||
|
|
549265cd34 | ||
|
|
989b18527e | ||
|
|
0cda37808e | ||
|
|
b3c4761ae5 | ||
|
|
dd65467573 | ||
|
|
eb45e6b294 | ||
|
|
6553fcf685 | ||
|
|
c2cf6b03a7 | ||
|
|
310ad7bc7f |
@@ -1,3 +1,4 @@
|
|||||||
.layout {
|
.layout {
|
||||||
font-family: var(--typography-Body-Regular-fontFamily);
|
font-family:
|
||||||
|
var(--Body-Paragraph-Font-family), var(--Body-Paragraph-Font-fallback);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,3 +52,4 @@ DTMC_ENTRA_ID_CLIENT=""
|
|||||||
DTMC_ENTRA_ID_ISSUER=""
|
DTMC_ENTRA_ID_ISSUER=""
|
||||||
DTMC_ENTRA_ID_SECRET=""
|
DTMC_ENTRA_ID_SECRET=""
|
||||||
|
|
||||||
|
NEXT_PUBLIC_NEW_POINTCLAIMS="true"
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
|
|
||||||
.layout {
|
.layout {
|
||||||
display: grid;
|
display: grid;
|
||||||
font-family: var(--typography-Body-Regular-fontFamily);
|
font-family:
|
||||||
|
var(--Body-Paragraph-Font-family), var(--Body-Paragraph-Font-fallback);
|
||||||
grid-template-rows: auto 1fr;
|
grid-template-rows: auto 1fr;
|
||||||
min-height: 100dvh;
|
min-height: 100dvh;
|
||||||
max-width: var(--max-width-page);
|
max-width: var(--max-width-page);
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
import { TrackingSDK } from "@scandic-hotels/tracking/TrackingSDK"
|
import { TrackingSDK } from "@scandic-hotels/tracking/TrackingSDK"
|
||||||
import {
|
import { getEurobonusMembership } from "@scandic-hotels/trpc/routers/user/helpers"
|
||||||
getEurobonusMembership,
|
|
||||||
scandicMembershipTypes,
|
|
||||||
} from "@scandic-hotels/trpc/routers/user/helpers"
|
|
||||||
|
|
||||||
import { env } from "@/env/server"
|
|
||||||
import {
|
import {
|
||||||
getBasicProfileSafely,
|
|
||||||
getProfileSafely,
|
getProfileSafely,
|
||||||
getProfilingConsent,
|
getProfilingConsent,
|
||||||
} from "@/lib/trpc/memoizedRequests"
|
} from "@/lib/trpc/memoizedRequests"
|
||||||
@@ -26,15 +21,7 @@ type MyPagesLayoutProps = React.PropsWithChildren<{
|
|||||||
breadcrumbs: React.ReactNode
|
breadcrumbs: React.ReactNode
|
||||||
}>
|
}>
|
||||||
|
|
||||||
export default async function MyPagesLayout(props: MyPagesLayoutProps) {
|
export default async function MyPagesLayout({
|
||||||
if (env.ENABLE_PROFILE_CONSENT) {
|
|
||||||
return <MyPagesLayoutWithConsent {...props} />
|
|
||||||
}
|
|
||||||
|
|
||||||
return <MyPagesLayoutBase {...props} />
|
|
||||||
}
|
|
||||||
|
|
||||||
async function MyPagesLayoutWithConsent({
|
|
||||||
breadcrumbs,
|
breadcrumbs,
|
||||||
children,
|
children,
|
||||||
}: MyPagesLayoutProps) {
|
}: MyPagesLayoutProps) {
|
||||||
@@ -84,25 +71,3 @@ async function MyPagesLayoutWithConsent({
|
|||||||
</ProfilingConsentAlertProvider>
|
</ProfilingConsentAlertProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function MyPagesLayoutBase({
|
|
||||||
breadcrumbs,
|
|
||||||
children,
|
|
||||||
}: MyPagesLayoutProps) {
|
|
||||||
const profile = await getBasicProfileSafely()
|
|
||||||
const eurobonusMembership = profile?.loyalty?.memberships?.find(
|
|
||||||
(m) => m.membershipType === scandicMembershipTypes.SAS_EB
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.container}>
|
|
||||||
<div className={styles.layout}>
|
|
||||||
{breadcrumbs}
|
|
||||||
<div className={styles.content}>{children}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{eurobonusMembership && <SASLevelUpgradeCheck />}
|
|
||||||
<Surprises />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,24 +1,13 @@
|
|||||||
import { redirect } from "next/navigation"
|
|
||||||
|
|
||||||
import { profile } from "@scandic-hotels/common/constants/routes/myPages"
|
|
||||||
import { TrackingSDK } from "@scandic-hotels/tracking/TrackingSDK"
|
import { TrackingSDK } from "@scandic-hotels/tracking/TrackingSDK"
|
||||||
|
|
||||||
import { env } from "@/env/server"
|
|
||||||
import { getProfile } from "@/lib/trpc/memoizedRequests"
|
import { getProfile } from "@/lib/trpc/memoizedRequests"
|
||||||
import { serverClient } from "@/lib/trpc/server"
|
import { serverClient } from "@/lib/trpc/server"
|
||||||
|
|
||||||
import { ProfilingConsent } from "@/components/Forms/ProfilingConsent"
|
import { ProfilingConsent } from "@/components/Forms/ProfilingConsent"
|
||||||
import { getLang } from "@/i18n/serverContext"
|
|
||||||
|
|
||||||
import styles from "./page.module.css"
|
import styles from "./page.module.css"
|
||||||
|
|
||||||
export default async function ProfilingConsentSlot() {
|
export default async function ProfilingConsentSlot() {
|
||||||
const lang = await getLang()
|
|
||||||
|
|
||||||
if (!env.ENABLE_PROFILE_CONSENT) {
|
|
||||||
redirect(profile[lang])
|
|
||||||
}
|
|
||||||
|
|
||||||
const caller = await serverClient()
|
const caller = await serverClient()
|
||||||
const accountPage = await caller.contentstack.accountPage.get()
|
const accountPage = await caller.contentstack.accountPage.get()
|
||||||
const user = await getProfile()
|
const user = await getProfile()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
.layout {
|
.layout {
|
||||||
display: grid;
|
display: grid;
|
||||||
font-family: var(--typography-Body-Regular-fontFamily);
|
font-family:
|
||||||
|
var(--Body-Paragraph-Font-family), var(--Body-Paragraph-Font-fallback);
|
||||||
grid-template-rows: auto 1fr;
|
grid-template-rows: auto 1fr;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
.layout {
|
.layout {
|
||||||
font-family: var(--typography-Body-Regular-fontFamily);
|
font-family:
|
||||||
|
var(--Body-Paragraph-Font-family), var(--Body-Paragraph-Font-fallback);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { redirect } from "next/navigation"
|
import { redirect } from "next/navigation"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
import Footnote from "@scandic-hotels/design-system/Footnote"
|
|
||||||
import Image from "@scandic-hotels/design-system/Image"
|
import Image from "@scandic-hotels/design-system/Image"
|
||||||
import Link from "@scandic-hotels/design-system/OldDSLink"
|
import { TextLink } from "@scandic-hotels/design-system/TextLink"
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import { env } from "@/env/server"
|
import { env } from "@/env/server"
|
||||||
@@ -94,27 +93,24 @@ export default async function SASxScandicLoginPage(
|
|||||||
{intentDescriptions[parsedParams.intent]}
|
{intentDescriptions[parsedParams.intent]}
|
||||||
</p>
|
</p>
|
||||||
</Typography>
|
</Typography>
|
||||||
<Footnote textAlign="center">
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||||
{intl.formatMessage(
|
<p style={{ textAlign: "center" }}>
|
||||||
{
|
{intl.formatMessage(
|
||||||
id: "linkEuroBonusAccount.manualRedirectLinkMessage",
|
{
|
||||||
defaultMessage:
|
id: "linkEuroBonusAccount.manualRedirectLinkMessage",
|
||||||
"If you are not redirected automatically, please <loginLink>click here</loginLink>.",
|
defaultMessage:
|
||||||
},
|
"If you are not redirected automatically, please <loginLink>click here</loginLink>.",
|
||||||
{
|
},
|
||||||
loginLink: (str) => (
|
{
|
||||||
<Link
|
loginLink: (str) => (
|
||||||
href={loginLink}
|
<TextLink typography="Link/sm" href={loginLink}>
|
||||||
color="red"
|
{str}
|
||||||
size="tiny"
|
</TextLink>
|
||||||
textDecoration="underline"
|
),
|
||||||
>
|
}
|
||||||
{str}
|
)}
|
||||||
</Link>
|
</p>
|
||||||
),
|
</Typography>
|
||||||
}
|
|
||||||
)}
|
|
||||||
</Footnote>
|
|
||||||
</SASModal>
|
</SASModal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,8 @@
|
|||||||
width: 34px;
|
width: 34px;
|
||||||
height: 0px;
|
height: 0px;
|
||||||
padding: var(--Space-x3) 0;
|
padding: var(--Space-x3) 0;
|
||||||
font-family: var(--typography-Body-Regular-fontFamily);
|
font-family:
|
||||||
|
var(--Body-Paragraph-Font-family), var(--Body-Paragraph-Font-fallback);
|
||||||
border: 1px solid var(--Base-Border-Normal);
|
border: 1px solid var(--Base-Border-Normal);
|
||||||
border-radius: var(--Corner-Radius-md);
|
border-radius: var(--Corner-Radius-md);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
.layout {
|
.layout {
|
||||||
background-color: var(--Background-Primary);
|
background-color: var(--Background-Primary);
|
||||||
font-family: var(--typography-Body-Regular-fontFamily);
|
font-family:
|
||||||
|
var(--Body-Paragraph-Font-family), var(--Body-Paragraph-Font-fallback);
|
||||||
min-height: 100dvh;
|
min-height: 100dvh;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
.layout {
|
.layout {
|
||||||
font-family: var(--typography-Body-Regular-fontFamily);
|
font-family:
|
||||||
|
var(--Body-Paragraph-Font-family), var(--Body-Paragraph-Font-fallback);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,29 @@
|
|||||||
import { Alert } from "@scandic-hotels/design-system/Alert"
|
import { Alert } from "@scandic-hotels/design-system/Alert"
|
||||||
|
import { getAlertPhoneContactData } from "@scandic-hotels/trpc/routers/contentstack/base/utils"
|
||||||
|
|
||||||
|
import { serverClient } from "@/lib/trpc/server"
|
||||||
|
|
||||||
import type { AlertBlock } from "@scandic-hotels/trpc/types/blocks"
|
import type { AlertBlock } from "@scandic-hotels/trpc/types/blocks"
|
||||||
|
|
||||||
interface AlertBlockProps extends Pick<AlertBlock, "alert"> {}
|
interface AlertBlockProps extends Pick<AlertBlock, "alert"> {}
|
||||||
|
|
||||||
export function AlertBlock({ alert }: AlertBlockProps) {
|
export async function AlertBlock({ alert }: AlertBlockProps) {
|
||||||
|
const caller = await serverClient()
|
||||||
|
const contactConfig = await caller.contentstack.base.contact()
|
||||||
if (!alert) {
|
if (!alert) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Alert {...alert} />
|
const phoneContact =
|
||||||
|
alert.phoneContact && contactConfig
|
||||||
|
? getAlertPhoneContactData(alert, contactConfig)
|
||||||
|
: null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
{...alert}
|
||||||
|
phoneContact={phoneContact}
|
||||||
|
sidepeekCtaText={alert.sidepeekButton?.cta_text}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,18 +16,14 @@
|
|||||||
|
|
||||||
.iconTh {
|
.iconTh {
|
||||||
padding: var(--Space-x5) var(--Space-x2) var(--Space-x2);
|
padding: var(--Space-x5) var(--Space-x2) var(--Space-x2);
|
||||||
font-weight: var(--typography-Caption-Regular-fontWeight);
|
|
||||||
vertical-align: bottom;
|
vertical-align: bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
.summaryTh {
|
.summaryTh {
|
||||||
font-size: var(--typography-Caption-Regular-fontSize);
|
|
||||||
font-weight: var(--typography-Caption-Regular-fontWeight);
|
|
||||||
padding: 0 var(--Space-x2) var(--Space-x2);
|
padding: 0 var(--Space-x2) var(--Space-x2);
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
.select {
|
.select {
|
||||||
font-weight: var(--typography-Caption-Regular-fontWeight);
|
|
||||||
padding: 0 var(--Space-x2) var(--Space-x2);
|
padding: 0 var(--Space-x2) var(--Space-x2);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import MembershipLevelIcon from "@/components/Levels/Icon"
|
import MembershipLevelIcon from "@/components/Levels/Icon"
|
||||||
|
|
||||||
import LevelSummary from "../../LevelSummary"
|
import LevelSummary from "../../LevelSummary"
|
||||||
@@ -37,12 +39,14 @@ export default function DesktopHeader({
|
|||||||
<th />
|
<th />
|
||||||
{levels.map((level, idx) => {
|
{levels.map((level, idx) => {
|
||||||
return (
|
return (
|
||||||
<th
|
<Typography
|
||||||
key={"summary" + level.level_id + idx}
|
variant="Body/Supporting text (caption)/smRegular"
|
||||||
className={styles.summaryTh}
|
key={"name" + level.level_id + idx}
|
||||||
>
|
>
|
||||||
<LevelSummary level={level} />
|
<th className={styles.summaryTh}>
|
||||||
</th>
|
<LevelSummary level={level} />
|
||||||
|
</th>
|
||||||
|
</Typography>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -82,10 +82,12 @@ function RewardTableHeader({ name, description }: RewardTableHeaderProps) {
|
|||||||
</span>
|
</span>
|
||||||
</hgroup>
|
</hgroup>
|
||||||
</summary>
|
</summary>
|
||||||
<p
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||||
className={styles.rewardDescription}
|
<p
|
||||||
dangerouslySetInnerHTML={{ __html: description }}
|
className={styles.rewardDescription}
|
||||||
/>
|
dangerouslySetInnerHTML={{ __html: description }}
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
</details>
|
</details>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,14 +15,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.td {
|
.td {
|
||||||
font-size: var(--typography-Footnote-Regular-fontSize);
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rewardTh {
|
.rewardTh {
|
||||||
padding: var(--Space-x3) var(--Space-x2);
|
padding: var(--Space-x3) var(--Space-x2);
|
||||||
font-size: var(--typography-Caption-Regular-fontSize);
|
|
||||||
font-weight: var(--typography-Caption-Regular-fontWeight);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.details[open] .chevron {
|
.details[open] .chevron {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import styles from "./levelSummary.module.css"
|
import styles from "./levelSummary.module.css"
|
||||||
|
|
||||||
import type { LevelSummaryProps } from "@/types/components/overviewTable"
|
import type { LevelSummaryProps } from "@/types/components/overviewTable"
|
||||||
@@ -32,7 +34,9 @@ export default function LevelSummary({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.levelSummary}>
|
<div className={styles.levelSummary}>
|
||||||
<span className={styles.levelRequirements}>{pointsMsg}</span>
|
<Typography variant="Label/xsRegular">
|
||||||
|
<span className={styles.levelRequirements}>{pointsMsg}</span>
|
||||||
|
</Typography>
|
||||||
{showDescription && (
|
{showDescription && (
|
||||||
<p className={styles.levelSummaryText}>{level.description}</p>
|
<p className={styles.levelSummaryText}>{level.description}</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -8,16 +8,14 @@
|
|||||||
|
|
||||||
.levelRequirements {
|
.levelRequirements {
|
||||||
border-radius: var(--Corner-Radius-md);
|
border-radius: var(--Corner-Radius-md);
|
||||||
background-color: var(--Scandic-Brand-Pale-Peach);
|
background-color: var(--Surface-Brand-Primary-1-Default);
|
||||||
color: var(--Scandic-Peach-80);
|
color: var(--Text-Interactive-Secondary);
|
||||||
padding: var(--Space-x05) var(--Space-x1);
|
padding: var(--Space-x05) var(--Space-x1);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.levelSummaryText {
|
.levelSummaryText {
|
||||||
font-size: var(--typography-Caption-Regular-fontSize);
|
|
||||||
line-height: var(--typography-Body-Regular-lineHeight);
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,12 +24,3 @@
|
|||||||
padding: var(--Space-x05) var(--Space-x1);
|
padding: var(--Space-x05) var(--Space-x1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media screen and (min-width: 1367px) {
|
|
||||||
.levelRequirements {
|
|
||||||
font-size: var(--typography-Footnote-Regular-fontSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
.levelSummaryText {
|
|
||||||
font-size: var(--typography-Caption-Regular-fontSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -27,10 +27,12 @@ export default function RewardCard({
|
|||||||
</span>
|
</span>
|
||||||
</hgroup>
|
</hgroup>
|
||||||
</summary>
|
</summary>
|
||||||
<p
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||||
className={styles.rewardCardDescription}
|
<p
|
||||||
dangerouslySetInnerHTML={{ __html: description }}
|
className={styles.rewardCardDescription}
|
||||||
/>
|
dangerouslySetInnerHTML={{ __html: description }}
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.rewardComparison}>
|
<div className={styles.rewardComparison}>
|
||||||
|
|||||||
@@ -12,8 +12,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.rewardCardDescription {
|
.rewardCardDescription {
|
||||||
font-size: var(--typography-Caption-Regular-fontSize);
|
|
||||||
line-height: 150%;
|
|
||||||
padding-right: var(--Space-x4);
|
padding-right: var(--Space-x4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Minus } from "react-feather"
|
import { Minus } from "react-feather"
|
||||||
|
|
||||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||||
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import styles from "./rewardValue.module.css"
|
import styles from "./rewardValue.module.css"
|
||||||
|
|
||||||
@@ -21,8 +22,8 @@ export default function RewardValue({ reward }: RewardValueProps) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className={styles.rewardValueContainer}>
|
<Typography variant="Body/Paragraph/mdBold">
|
||||||
<span className={styles.rewardValue}>{reward.value}</span>
|
<div className={styles.rewardValueContainer}>{reward.value}</div>
|
||||||
</div>
|
</Typography>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,17 +7,6 @@
|
|||||||
text-wrap: balance;
|
text-wrap: balance;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rewardValue {
|
|
||||||
font-size: var(--typography-Body-Bold-fontSize);
|
|
||||||
font-weight: var(--typography-Body-Bold-fontWeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rewardValueDetails {
|
|
||||||
font-size: var(--typography-Footnote-Regular-fontSize);
|
|
||||||
text-align: center;
|
|
||||||
color: var(--UI-Grey-80);
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkIcon {
|
.checkIcon {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,430 @@
|
|||||||
|
/* eslint-disable formatjs/no-literal-string-in-jsx */
|
||||||
|
/* TODO remove disable and add i18n */
|
||||||
|
/* TODO add analytics */
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
|
import { cx } from "class-variance-authority"
|
||||||
|
import { useState } from "react"
|
||||||
|
import { FormProvider, useForm, useWatch } from "react-hook-form"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
import z from "zod"
|
||||||
|
|
||||||
|
import { dt } from "@scandic-hotels/common/dt"
|
||||||
|
import { Button } from "@scandic-hotels/design-system/Button"
|
||||||
|
import { FormInput } from "@scandic-hotels/design-system/Form/FormInput"
|
||||||
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||||
|
import { LoadingSpinner } from "@scandic-hotels/design-system/LoadingSpinner"
|
||||||
|
import { MessageBanner } from "@scandic-hotels/design-system/MessageBanner"
|
||||||
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
import { trpc } from "@scandic-hotels/trpc/client"
|
||||||
|
|
||||||
|
import useLang from "@/hooks/useLang"
|
||||||
|
|
||||||
|
import styles from "./claimPoints.module.css"
|
||||||
|
|
||||||
|
type PointClaimBookingInfo = {
|
||||||
|
from: string
|
||||||
|
to: string
|
||||||
|
city: string
|
||||||
|
hotel: string
|
||||||
|
}
|
||||||
|
export function ClaimPointsWizard({
|
||||||
|
onSuccess,
|
||||||
|
onClose,
|
||||||
|
}: {
|
||||||
|
onSuccess: () => void
|
||||||
|
onClose: () => void
|
||||||
|
}) {
|
||||||
|
const [state, setState] = useState<
|
||||||
|
"initial" | "loading" | "invalid" | "form"
|
||||||
|
>("initial")
|
||||||
|
const [bookingDetails, setBookingDetails] =
|
||||||
|
useState<PointClaimBookingInfo | null>(null)
|
||||||
|
|
||||||
|
const { data, isLoading } = trpc.user.getSafely.useQuery()
|
||||||
|
|
||||||
|
if (state === "invalid") {
|
||||||
|
return <InvalidBooking onClose={onClose} />
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state === "form") {
|
||||||
|
if (isLoading) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ClaimPointsForm
|
||||||
|
onSuccess={onSuccess}
|
||||||
|
initialData={{
|
||||||
|
...bookingDetails,
|
||||||
|
firstName: data?.firstName ?? "",
|
||||||
|
lastName: data?.lastName ?? "",
|
||||||
|
email: data?.email ?? "",
|
||||||
|
phone: data?.phoneNumber ?? "",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBookingNumberEvent = (event: BookingNumberEvent) => {
|
||||||
|
switch (event.type) {
|
||||||
|
case "submit":
|
||||||
|
setState("loading")
|
||||||
|
break
|
||||||
|
case "error":
|
||||||
|
setState("initial")
|
||||||
|
break
|
||||||
|
case "invalid":
|
||||||
|
setState("invalid")
|
||||||
|
break
|
||||||
|
case "success":
|
||||||
|
setBookingDetails(event.data)
|
||||||
|
setState("form")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.introWrapper}>
|
||||||
|
{state === "loading" && (
|
||||||
|
<div
|
||||||
|
className={styles.spinner}
|
||||||
|
aria-live="polite"
|
||||||
|
aria-label="Loading booking details, please wait.."
|
||||||
|
>
|
||||||
|
<LoadingSpinner />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className={cx(styles.options, { [styles.hidden]: state === "loading" })}
|
||||||
|
>
|
||||||
|
<section className={styles.sectionCard}>
|
||||||
|
<div className={styles.sectionInfo}>
|
||||||
|
<Typography variant="Body/Paragraph/mdBold">
|
||||||
|
<h4>Claim points with booking number</h4>
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||||
|
<p>
|
||||||
|
Enter a valid booking number to load booking details
|
||||||
|
automatically.
|
||||||
|
</p>
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
<BookingNumberInput onEvent={handleBookingNumberEvent} />
|
||||||
|
</section>
|
||||||
|
<Divider />
|
||||||
|
<section className={styles.sectionCard}>
|
||||||
|
<div className={styles.sectionInfo}>
|
||||||
|
<Typography variant="Body/Paragraph/mdBold">
|
||||||
|
<h4>Claim points without booking number</h4>
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||||
|
<p>You need to add booking details in a form.</p>
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
<Button variant="Secondary" onPress={() => setState("form")}>
|
||||||
|
Fill form to claim points
|
||||||
|
</Button>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<MessageBanner
|
||||||
|
type="info"
|
||||||
|
text="Points can be claimed up to 6 months back if you were a member at the time of your stay."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type BookingNumberFormData = {
|
||||||
|
bookingNumber: string
|
||||||
|
}
|
||||||
|
type BookingNumberEvent =
|
||||||
|
| { type: "submit" }
|
||||||
|
| { type: "success"; data: PointClaimBookingInfo }
|
||||||
|
| { type: "error" }
|
||||||
|
| { type: "invalid" }
|
||||||
|
function BookingNumberInput({
|
||||||
|
onEvent,
|
||||||
|
}: {
|
||||||
|
onEvent: (event: BookingNumberEvent) => void
|
||||||
|
}) {
|
||||||
|
const lang = useLang()
|
||||||
|
const form = useForm<BookingNumberFormData>({
|
||||||
|
resolver: zodResolver(
|
||||||
|
z.object({
|
||||||
|
bookingNumber: z
|
||||||
|
.string()
|
||||||
|
// TODO Check UX for validation as different environments have different lengths
|
||||||
|
.min(9, { message: "Booking number must be 10 digits" })
|
||||||
|
.max(10, { message: "Booking number must be 10 digits" }),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
defaultValues: {
|
||||||
|
bookingNumber: "",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const confirmationNumber = useWatch({
|
||||||
|
name: "bookingNumber",
|
||||||
|
control: form.control,
|
||||||
|
})
|
||||||
|
|
||||||
|
const { refetch, isFetching } =
|
||||||
|
trpc.booking.findBookingForCurrentUser.useQuery(
|
||||||
|
{
|
||||||
|
confirmationNumber,
|
||||||
|
lang,
|
||||||
|
},
|
||||||
|
{ enabled: false }
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
onEvent({ type: "submit" })
|
||||||
|
const result = await refetch()
|
||||||
|
if (!result.data) {
|
||||||
|
onEvent({ type: "error" })
|
||||||
|
form.setError("bookingNumber", {
|
||||||
|
type: "manual",
|
||||||
|
message:
|
||||||
|
"We could not find a booking with this number registered in your name.",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = result.data
|
||||||
|
|
||||||
|
// TODO validate if this should be check out or check in date
|
||||||
|
const checkOutDate = dt(data.booking.checkOutDate)
|
||||||
|
const sixMonthsAgo = dt().subtract(6, "months")
|
||||||
|
if (checkOutDate.isBefore(sixMonthsAgo, "day")) {
|
||||||
|
onEvent({ type: "invalid" })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
onEvent({
|
||||||
|
type: "success",
|
||||||
|
data: {
|
||||||
|
from: data.booking.checkInDate,
|
||||||
|
to: data.booking.checkOutDate,
|
||||||
|
city: data.hotel.cityName,
|
||||||
|
hotel: data.hotel.name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormProvider {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(handleSubmit)}>
|
||||||
|
<FormInput
|
||||||
|
name="bookingNumber"
|
||||||
|
label="Booking number"
|
||||||
|
leftIcon={<MaterialIcon icon="edit_document" />}
|
||||||
|
description="Enter your 10-digit booking number"
|
||||||
|
maxLength={10}
|
||||||
|
showClearContentIcon
|
||||||
|
disabled={isFetching}
|
||||||
|
autoFocus
|
||||||
|
autoComplete="off"
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value
|
||||||
|
if (value.length !== 10) return
|
||||||
|
|
||||||
|
form.handleSubmit(handleSubmit)()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</FormProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function InvalidBooking({ onClose }: { onClose: () => void }) {
|
||||||
|
return (
|
||||||
|
<div className={styles.invalidWrapper}>
|
||||||
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
|
<p>
|
||||||
|
We can’t add these points to your account as it has been longer than 6
|
||||||
|
months since your stay.
|
||||||
|
</p>
|
||||||
|
</Typography>
|
||||||
|
<Button variant="Primary" fullWidth onPress={onClose}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type PointClaimUserInfo = {
|
||||||
|
firstName: string
|
||||||
|
lastName: string
|
||||||
|
email: string
|
||||||
|
phone: string
|
||||||
|
}
|
||||||
|
function ClaimPointsForm({
|
||||||
|
onSuccess,
|
||||||
|
initialData,
|
||||||
|
}: {
|
||||||
|
onSuccess: () => void
|
||||||
|
initialData: Partial<PointClaimBookingInfo & PointClaimUserInfo> | null
|
||||||
|
}) {
|
||||||
|
const form = useForm({
|
||||||
|
resolver: zodResolver(
|
||||||
|
z.object({
|
||||||
|
from: z.string().min(1, { message: "Arrival date is required" }),
|
||||||
|
to: z.string().min(1, { message: "Departure date is required" }),
|
||||||
|
city: z.string().min(1, { message: "City is required" }),
|
||||||
|
hotel: z.string().min(1, { message: "Hotel is required" }),
|
||||||
|
firstName: z.string().min(1, { message: "First name is required" }),
|
||||||
|
lastName: z.string().min(1, { message: "Last name is required" }),
|
||||||
|
email: z
|
||||||
|
.string()
|
||||||
|
.email("Enter a valid email")
|
||||||
|
.min(1, { message: "Email is required" }),
|
||||||
|
phone: z.string().min(1, { message: "Phone is required" }),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
defaultValues: {
|
||||||
|
from: initialData?.from || "",
|
||||||
|
to: initialData?.to || "",
|
||||||
|
city: initialData?.city || "",
|
||||||
|
hotel: initialData?.hotel || "",
|
||||||
|
firstName: initialData?.firstName || "",
|
||||||
|
lastName: initialData?.lastName || "",
|
||||||
|
email: initialData?.email || "",
|
||||||
|
phone: initialData?.phone || "",
|
||||||
|
},
|
||||||
|
mode: "all",
|
||||||
|
})
|
||||||
|
|
||||||
|
const { mutate, isPending } = trpc.user.claimPoints.useMutation({
|
||||||
|
onSuccess,
|
||||||
|
})
|
||||||
|
|
||||||
|
const autoFocusField = getAutoFocus(initialData)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormProvider {...form}>
|
||||||
|
<form
|
||||||
|
className={styles.form}
|
||||||
|
onSubmit={form.handleSubmit((data) => mutate(data))}
|
||||||
|
>
|
||||||
|
<div className={styles.formInputs}>
|
||||||
|
{!initialData?.firstName && (
|
||||||
|
<FormInput
|
||||||
|
name="firstName"
|
||||||
|
label="First name"
|
||||||
|
autoFocus={autoFocusField === "firstName"}
|
||||||
|
readOnly={isPending}
|
||||||
|
registerOptions={{ required: true }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!initialData?.lastName && (
|
||||||
|
<FormInput
|
||||||
|
name="lastName"
|
||||||
|
label="Last name"
|
||||||
|
autoFocus={autoFocusField === "lastName"}
|
||||||
|
readOnly={isPending}
|
||||||
|
registerOptions={{ required: true }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!initialData?.email && (
|
||||||
|
<FormInput
|
||||||
|
name="email"
|
||||||
|
label="Email"
|
||||||
|
type="email"
|
||||||
|
autoFocus={autoFocusField === "email"}
|
||||||
|
readOnly={isPending}
|
||||||
|
registerOptions={{ required: true }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!initialData?.phone && (
|
||||||
|
<FormInput
|
||||||
|
name="phone"
|
||||||
|
label="Phone"
|
||||||
|
type="tel"
|
||||||
|
autoFocus={autoFocusField === "phone"}
|
||||||
|
readOnly={isPending}
|
||||||
|
registerOptions={{ required: true }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<FormInput
|
||||||
|
name="from"
|
||||||
|
label="Arrival (YYYY-MM-DD)"
|
||||||
|
leftIcon={<MaterialIcon icon="calendar_today" />}
|
||||||
|
autoFocus={autoFocusField === "from"}
|
||||||
|
readOnly={isPending}
|
||||||
|
registerOptions={{ required: true }}
|
||||||
|
/>
|
||||||
|
<FormInput
|
||||||
|
name="to"
|
||||||
|
label="Departure (YYYY-MM-DD)"
|
||||||
|
leftIcon={<MaterialIcon icon="calendar_today" />}
|
||||||
|
readOnly={isPending}
|
||||||
|
registerOptions={{ required: true }}
|
||||||
|
/>
|
||||||
|
<FormInput
|
||||||
|
name="city"
|
||||||
|
label="City"
|
||||||
|
readOnly={isPending}
|
||||||
|
registerOptions={{ required: true }}
|
||||||
|
/>
|
||||||
|
<FormInput
|
||||||
|
name="hotel"
|
||||||
|
label="Hotel"
|
||||||
|
readOnly={isPending}
|
||||||
|
registerOptions={{ required: true }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<MessageBanner
|
||||||
|
type="info"
|
||||||
|
text="Points can be claimed up to 6 months back if you were a member at the time of your stay."
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
variant="Primary"
|
||||||
|
fullWidth
|
||||||
|
isDisabled={!form.formState.isValid}
|
||||||
|
isPending={isPending}
|
||||||
|
className={styles.formSubmit}
|
||||||
|
>
|
||||||
|
Send points claim
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</FormProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAutoFocus(userInfo: Partial<PointClaimUserInfo> | null) {
|
||||||
|
if (!userInfo?.firstName) {
|
||||||
|
return "firstName"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userInfo?.lastName) {
|
||||||
|
return "lastName"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userInfo?.email) {
|
||||||
|
return "email"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userInfo?.phone) {
|
||||||
|
return "phone"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "from"
|
||||||
|
}
|
||||||
|
|
||||||
|
function Divider() {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.divider}>
|
||||||
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: "common.or",
|
||||||
|
defaultMessage: "or",
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -6,3 +6,100 @@
|
|||||||
gap: var(--Space-x2);
|
gap: var(--Space-x2);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialog {
|
||||||
|
max-width: 560px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.introWrapper {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--Space-x3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.options {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--Space-x1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sectionCard {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--Space-x3);
|
||||||
|
padding: var(--Space-x2);
|
||||||
|
background-color: var(--Surface-Primary-OnSurface-Default);
|
||||||
|
border-radius: var(--Corner-Radius-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sectionInfo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--Space-x1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
background-color: var(--Base-Surface-Primary-light-Normal);
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bookingInputDescription {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--Space-x05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--Space-x3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.formInputs {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--Space-x2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.formSubmit {
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
& > span {
|
||||||
|
position: relative;
|
||||||
|
padding: 0 var(--Space-x2);
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
bottom: calc(50% - 1px);
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
height: 1px;
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--Border-Default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invalidWrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--Space-x3);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,18 +1,110 @@
|
|||||||
|
/* eslint-disable formatjs/no-literal-string-in-jsx */
|
||||||
|
/* TODO remove disable and add i18n */
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import { Dialog } from "react-aria-components"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { Button } from "@scandic-hotels/design-system/Button"
|
||||||
import ButtonLink from "@scandic-hotels/design-system/ButtonLink"
|
import ButtonLink from "@scandic-hotels/design-system/ButtonLink"
|
||||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||||
|
import Modal from "@scandic-hotels/design-system/Modal"
|
||||||
|
import { toast } from "@scandic-hotels/design-system/Toast"
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import { missingPoints } from "@/constants/missingPointsHrefs"
|
import { missingPoints } from "@/constants/missingPointsHrefs"
|
||||||
|
import { env } from "@/env/client"
|
||||||
|
|
||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
|
|
||||||
|
import { ClaimPointsWizard } from "./ClaimPointsWizard"
|
||||||
|
|
||||||
import styles from "./claimPoints.module.css"
|
import styles from "./claimPoints.module.css"
|
||||||
|
|
||||||
export default function ClaimPoints() {
|
export default function ClaimPoints() {
|
||||||
|
const intl = useIntl()
|
||||||
|
const [openModal, setOpenModal] = useLinkableModalState("claim-points")
|
||||||
|
|
||||||
|
const useNewFlow = env.NEXT_PUBLIC_NEW_POINTCLAIMS
|
||||||
|
if (!useNewFlow) {
|
||||||
|
return <OldClaimPointsLink />
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.claim}>
|
||||||
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||||
|
<p>
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: "points.claimPoints.missingPreviousStay",
|
||||||
|
defaultMessage: "Missing a previous stay?",
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</Typography>
|
||||||
|
<Button variant="Text" size="sm" onPress={() => setOpenModal(true)}>
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: "points.claimPoints.cta",
|
||||||
|
defaultMessage: "Claim points",
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Modal
|
||||||
|
title="Add missing points"
|
||||||
|
isOpen={openModal}
|
||||||
|
onToggle={(open) => setOpenModal(open)}
|
||||||
|
>
|
||||||
|
<Dialog aria-label="TODO" className={styles.dialog}>
|
||||||
|
{({ close }) => (
|
||||||
|
<ClaimPointsWizard
|
||||||
|
onSuccess={() => {
|
||||||
|
toast.info(
|
||||||
|
<>
|
||||||
|
<Typography variant="Body/Paragraph/mdBold">
|
||||||
|
<p>We're on it!</p>
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
|
<p>
|
||||||
|
If your points have not been added to your account
|
||||||
|
within 2 weeks, please contact us.
|
||||||
|
</p>
|
||||||
|
</Typography>
|
||||||
|
</>,
|
||||||
|
{
|
||||||
|
duration: Infinity,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
close()
|
||||||
|
}}
|
||||||
|
onClose={close}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Dialog>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function useLinkableModalState(target: string) {
|
||||||
|
const [openModal, setOpenModal] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const params = new URLSearchParams(window.location.search)
|
||||||
|
const claimPoints = params.get("target") === target
|
||||||
|
|
||||||
|
if (claimPoints) {
|
||||||
|
params.delete("target")
|
||||||
|
const newUrl = `${window.location.pathname}?${params.toString()}`
|
||||||
|
window.history.replaceState({}, "", newUrl)
|
||||||
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||||
|
setOpenModal(true)
|
||||||
|
}
|
||||||
|
}, [target])
|
||||||
|
|
||||||
|
return [openModal, setOpenModal] as const
|
||||||
|
}
|
||||||
|
|
||||||
|
function OldClaimPointsLink() {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
|
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ function getDescription(transaction: Transaction, intl: IntlShape) {
|
|||||||
|
|
||||||
if (isNonTransactional && transaction.attributes.nights === 0) {
|
if (isNonTransactional && transaction.attributes.nights === 0) {
|
||||||
return intl.formatMessage({
|
return intl.formatMessage({
|
||||||
id: "myPoints.pointTransactions.pointsActivity",
|
id: "earnAndBurn.journeyTable.pointsActivity",
|
||||||
defaultMessage: "Point activity",
|
defaultMessage: "Point activity",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -113,7 +113,7 @@ function getDescription(transaction: Transaction, intl: IntlShape) {
|
|||||||
if (hotelInformation?.name) {
|
if (hotelInformation?.name) {
|
||||||
return intl.formatMessage(
|
return intl.formatMessage(
|
||||||
{
|
{
|
||||||
id: "myPoints.pointTransactions.stayAt",
|
id: "earnAndBurn.journeyTable.stayAt",
|
||||||
defaultMessage: "Stay at {hotelName}",
|
defaultMessage: "Stay at {hotelName}",
|
||||||
},
|
},
|
||||||
{ hotelName: hotelInformation.name }
|
{ hotelName: hotelInformation.name }
|
||||||
@@ -124,53 +124,53 @@ function getDescription(transaction: Transaction, intl: IntlShape) {
|
|||||||
case Transactions.rewardType.stayAdj:
|
case Transactions.rewardType.stayAdj:
|
||||||
if (transaction.attributes.hotelOperaId === "ORS") {
|
if (transaction.attributes.hotelOperaId === "ORS") {
|
||||||
return intl.formatMessage({
|
return intl.formatMessage({
|
||||||
id: "myPoints.pointTransactions.formerScandicHotel",
|
id: "earnAndBurn.journeyTable.formerScandicHotel",
|
||||||
defaultMessage: "Former Scandic Hotel",
|
defaultMessage: "Former Scandic Hotel",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (isBalfwd) {
|
if (isBalfwd) {
|
||||||
return intl.formatMessage({
|
return intl.formatMessage({
|
||||||
id: "myPoints.pointTransactions.pointsEarnedPriorMay2021",
|
id: "earnAndBurn.journeyTable.pointsEarnedPriorMay2021",
|
||||||
defaultMessage: "Points earned prior to May 1, 2021",
|
defaultMessage: "Points earned prior to May 1, 2021",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
case Transactions.rewardType.redgift:
|
case Transactions.rewardType.redgift:
|
||||||
return intl.formatMessage({
|
return intl.formatMessage({
|
||||||
id: "myPoints.pointTransactions.redGift",
|
id: "earnAndBurn.journeyTable.redGift",
|
||||||
defaultMessage: "Reward Gift",
|
defaultMessage: "Reward Gift",
|
||||||
})
|
})
|
||||||
case Transactions.rewardType.rewardNight:
|
case Transactions.rewardType.rewardNight:
|
||||||
return intl.formatMessage({
|
return intl.formatMessage({
|
||||||
id: "myPoints.pointTransactions.rewardNight",
|
id: "earnAndBurn.journeyTable.rewardNight",
|
||||||
defaultMessage: "Reward Night",
|
defaultMessage: "Reward Night",
|
||||||
})
|
})
|
||||||
case Transactions.rewardType.ancillary:
|
case Transactions.rewardType.ancillary:
|
||||||
return intl.formatMessage({
|
return intl.formatMessage({
|
||||||
id: "myPoints.pointTransactions.extrasToBooking",
|
id: "earnAndBurn.journeyTable.extrasToBooking",
|
||||||
defaultMessage: "Extras to your booking",
|
defaultMessage: "Extras to your booking",
|
||||||
})
|
})
|
||||||
|
|
||||||
case Transactions.rewardType.enrollment:
|
case Transactions.rewardType.enrollment:
|
||||||
return intl.formatMessage({
|
return intl.formatMessage({
|
||||||
id: "myPoints.pointTransactions.signUpBonus",
|
id: "earnAndBurn.journeyTable.signUpBonus",
|
||||||
defaultMessage: "Sign up bonus",
|
defaultMessage: "Sign up bonus",
|
||||||
})
|
})
|
||||||
|
|
||||||
case Transactions.rewardType.mastercard_points:
|
case Transactions.rewardType.mastercard_points:
|
||||||
return intl.formatMessage({
|
return intl.formatMessage({
|
||||||
id: "myPoints.pointTransactions.scandicFriendsMastercard",
|
id: "earnAndBurn.journeyTable.scandicFriendsMastercard",
|
||||||
defaultMessage: "Scandic Friends Mastercard",
|
defaultMessage: "Scandic Friends Mastercard",
|
||||||
})
|
})
|
||||||
|
|
||||||
case Transactions.rewardType.tui_points:
|
case Transactions.rewardType.tui_points:
|
||||||
return intl.formatMessage({
|
return intl.formatMessage({
|
||||||
id: "myPoints.pointTransactions.tuiPoints",
|
id: "earnAndBurn.journeyTable.tuiPoints",
|
||||||
defaultMessage: "TUI Points",
|
defaultMessage: "TUI Points",
|
||||||
})
|
})
|
||||||
|
|
||||||
case Transactions.rewardType.pointShop:
|
case Transactions.rewardType.pointShop:
|
||||||
return intl.formatMessage({
|
return intl.formatMessage({
|
||||||
id: "myPoints.pointTransactions.pointShop",
|
id: "earnAndBurn.journeyTable.pointShop",
|
||||||
defaultMessage: "Scandic Friends Point Shop",
|
defaultMessage: "Scandic Friends Point Shop",
|
||||||
})
|
})
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { env } from "@/env/server"
|
|
||||||
|
|
||||||
import SignupForm from "@/components/Forms/Signup"
|
import SignupForm from "@/components/Forms/Signup"
|
||||||
|
|
||||||
import type { SignupFormWrapperProps } from "@/types/components/blocks/dynamicContent"
|
import type { SignupFormWrapperProps } from "@/types/components/blocks/dynamicContent"
|
||||||
@@ -7,10 +5,5 @@ import type { SignupFormWrapperProps } from "@/types/components/blocks/dynamicCo
|
|||||||
export default async function SignupFormWrapper({
|
export default async function SignupFormWrapper({
|
||||||
dynamic_content,
|
dynamic_content,
|
||||||
}: SignupFormWrapperProps) {
|
}: SignupFormWrapperProps) {
|
||||||
return (
|
return <SignupForm {...dynamic_content} />
|
||||||
<SignupForm
|
|
||||||
{...dynamic_content}
|
|
||||||
enableProfileConsent={env.ENABLE_PROFILE_CONSENT}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { useMediaQuery } from "usehooks-ts"
|
|||||||
|
|
||||||
import { useMarkerHover } from "@scandic-hotels/common/hooks/map/useMarkerHover"
|
import { useMarkerHover } from "@scandic-hotels/common/hooks/map/useMarkerHover"
|
||||||
import { InfoWindow } from "@scandic-hotels/design-system/Map/InfoWindow"
|
import { InfoWindow } from "@scandic-hotels/design-system/Map/InfoWindow"
|
||||||
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import { useDestinationPageCitiesMapStore } from "@/stores/destination-page-cities-map"
|
import { useDestinationPageCitiesMapStore } from "@/stores/destination-page-cities-map"
|
||||||
|
|
||||||
@@ -79,7 +80,10 @@ export default function CityClusterMarker({
|
|||||||
})}
|
})}
|
||||||
anchorPoint={AdvancedMarkerAnchorPoint.CENTER}
|
anchorPoint={AdvancedMarkerAnchorPoint.CENTER}
|
||||||
>
|
>
|
||||||
<span className={styles.count}>{sizeAsText}</span>
|
<Typography variant="Title/Subtitle/md">
|
||||||
|
<span>{sizeAsText}</span>
|
||||||
|
</Typography>
|
||||||
|
|
||||||
{isDesktop && isHovered ? (
|
{isDesktop && isHovered ? (
|
||||||
<InfoWindow
|
<InfoWindow
|
||||||
position={position}
|
position={position}
|
||||||
|
|||||||
@@ -20,9 +20,3 @@
|
|||||||
height: 46px !important;
|
height: 46px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.count {
|
|
||||||
font-family: var(--typography-Body-Regular-fontFamily);
|
|
||||||
font-size: var(--typography-Subtitle-2-fontSize);
|
|
||||||
font-weight: var(--typography-Subtitle-2-fontWeight);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -19,12 +19,13 @@ div.months {
|
|||||||
td.day,
|
td.day,
|
||||||
td.rangeEnd,
|
td.rangeEnd,
|
||||||
td.rangeStart {
|
td.rangeStart {
|
||||||
font-family: var(--typography-Body-Bold-fontFamily);
|
font-family:
|
||||||
font-size: var(--typography-Body-Bold-fontSize);
|
var(--Body-Paragraph-Font-family), var(--Body-Paragraph-Font-fallback);
|
||||||
font-weight: 500;
|
font-size: var(--Body-Paragraph-Size);
|
||||||
letter-spacing: var(--typography-Body-Bold-letterSpacing);
|
font-weight: var(--Body-Paragraph-Font-weight-2);
|
||||||
line-height: var(--typography-Body-Bold-lineHeight);
|
letter-spacing: var(--Body-Paragraph-Letter-spacing);
|
||||||
text-decoration: var(--typography-Body-Bold-textDecoration);
|
line-height: 1.5;
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
td.rangeEnd,
|
td.rangeEnd,
|
||||||
@@ -90,14 +91,16 @@ td.day[data-outside="true"] ~ td.day[data-disabled="true"] button.dayButton,
|
|||||||
}
|
}
|
||||||
|
|
||||||
.weekDay {
|
.weekDay {
|
||||||
color: var(--UI-Text-Placeholder);
|
color: var(--Text-Tertiary);
|
||||||
font-family: var(--typography-Footnote-Labels-fontFamily);
|
font-family:
|
||||||
font-size: var(--typography-Footnote-Labels-fontSize);
|
var(--Title-Overline-sm-Font-family), var(--Title-Overline-sm-Font-fallback);
|
||||||
font-weight: var(--typography-Footnote-Labels-fontWeight);
|
font-size: var(--Title-Overline-sm-Size);
|
||||||
letter-spacing: var(--typography-Footnote-Labels-letterSpacing);
|
font-style: normal;
|
||||||
line-height: var(--typography-Footnote-Labels-lineHeight);
|
font-weight: var(--Title-Overline-sm-Font-weight);
|
||||||
text-decoration: var(--typography-Footnote-Labels-textDecoration);
|
line-height: 1.5;
|
||||||
text-transform: uppercase;
|
letter-spacing: var(--Title-Overline-sm-Letter-spacing);
|
||||||
|
text-transform: var(--Title-Overline-sm-Text-Transform);
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
|
|||||||
@@ -89,12 +89,13 @@ div.months {
|
|||||||
td.day,
|
td.day,
|
||||||
td.rangeEnd,
|
td.rangeEnd,
|
||||||
td.rangeStart {
|
td.rangeStart {
|
||||||
font-family: var(--typography-Body-Bold-fontFamily);
|
font-family:
|
||||||
font-size: var(--typography-Body-Bold-fontSize);
|
var(--Body-Paragraph-Font-family), var(--Body-Paragraph-Font-fallback);
|
||||||
font-weight: 500;
|
font-size: var(--Body-Paragraph-Size);
|
||||||
letter-spacing: var(--typography-Body-Bold-letterSpacing);
|
font-weight: var(--Body-Paragraph-Font-weight-2);
|
||||||
line-height: var(--typography-Body-Bold-lineHeight);
|
letter-spacing: var(--Body-Paragraph-Letter-spacing);
|
||||||
text-decoration: var(--typography-Body-Bold-textDecoration);
|
line-height: 1.5;
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
td.rangeEnd,
|
td.rangeEnd,
|
||||||
@@ -156,14 +157,16 @@ td.day[data-outside="true"] ~ td.day[data-disabled="true"] button.dayButton,
|
|||||||
}
|
}
|
||||||
|
|
||||||
.weekDay {
|
.weekDay {
|
||||||
color: var(--UI-Text-Placeholder);
|
color: var(--Text-Tertiary);
|
||||||
font-family: var(--typography-Caption-Labels-fontFamily);
|
font-family:
|
||||||
font-size: var(--typography-Caption-Labels-fontSize);
|
var(--Title-Overline-sm-Font-family), var(--Title-Overline-sm-Font-fallback);
|
||||||
font-weight: var(--typography-Caption-Labels-fontWeight);
|
font-size: var(--Title-Overline-sm-Size);
|
||||||
letter-spacing: var(--typography-Caption-Labels-letterSpacing);
|
font-style: normal;
|
||||||
line-height: var(--typography-Caption-Labels-lineHeight);
|
font-weight: var(--Title-Overline-sm-Font-weight);
|
||||||
text-decoration: var(--typography-Caption-Labels-textDecoration);
|
line-height: 1.5;
|
||||||
text-transform: uppercase;
|
letter-spacing: var(--Title-Overline-sm-Letter-spacing);
|
||||||
|
text-transform: var(--Title-Overline-sm-Text-Transform);
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 1367px) {
|
@media screen and (min-width: 1367px) {
|
||||||
|
|||||||
@@ -107,7 +107,6 @@ export default function Form({ user }: EditFormProps) {
|
|||||||
} else {
|
} else {
|
||||||
router.push(profile[lang])
|
router.push(profile[lang])
|
||||||
}
|
}
|
||||||
router.refresh() // Can be removed on NextJs 15
|
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,14 +48,9 @@ import styles from "./form.module.css"
|
|||||||
|
|
||||||
interface SignUpFormProps {
|
interface SignUpFormProps {
|
||||||
title: string
|
title: string
|
||||||
enableProfileConsent?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SignupForm({
|
export default function SignupForm({ title }: SignUpFormProps) {
|
||||||
title,
|
|
||||||
// Handled as a prop rather than a client env var due to limits in Netlify env var size.
|
|
||||||
enableProfileConsent = false,
|
|
||||||
}: SignUpFormProps) {
|
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
@@ -140,7 +135,7 @@ export default function SignupForm({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.formWrapper}>
|
<div className={styles.formWrapper}>
|
||||||
{enableProfileConsent && <ProfilingConsentModalReadOnly />}
|
<ProfilingConsentModalReadOnly />
|
||||||
{title ? (
|
{title ? (
|
||||||
<Typography variant="Title/md">
|
<Typography variant="Title/md">
|
||||||
<h2>{title}</h2>
|
<h2>{title}</h2>
|
||||||
@@ -293,41 +288,39 @@ export default function SignupForm({
|
|||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{enableProfileConsent && (
|
<section className={styles.personalization}>
|
||||||
<section className={styles.personalization}>
|
<header>
|
||||||
<header>
|
<Typography variant="Title/Subtitle/md">
|
||||||
<Typography variant="Title/Subtitle/md">
|
<h3>
|
||||||
<h3>
|
{intl.formatMessage({
|
||||||
{intl.formatMessage({
|
id: "signup.UnlockYourPersonalizedExperience",
|
||||||
id: "signup.UnlockYourPersonalizedExperience",
|
defaultMessage: "Unlock your personalized experience!",
|
||||||
defaultMessage: "Unlock your personalized experience!",
|
})}
|
||||||
})}
|
</h3>
|
||||||
</h3>
|
</Typography>
|
||||||
</Typography>
|
</header>
|
||||||
</header>
|
<Checkbox
|
||||||
<Checkbox
|
name="profilingConsent"
|
||||||
name="profilingConsent"
|
registerOptions={{ required: true }}
|
||||||
registerOptions={{ required: true }}
|
>
|
||||||
>
|
{intl.formatMessage({
|
||||||
{intl.formatMessage({
|
id: "signup.yesConsent",
|
||||||
id: "signup.yesConsent",
|
defaultMessage:
|
||||||
defaultMessage:
|
"I consent to Scandic using my information to give me even more personalized travel inspiration and offers from Scandic and trusted Scandic Friends partners. This means Scandic may use information about my interactions with Scandic Friends partners, and share details of my interactions with Scandic with those partners, to make the experience even more relevant to me.",
|
||||||
"I consent to Scandic using my information to give me even more personalized travel inspiration and offers from Scandic and trusted Scandic Friends partners. This means Scandic may use information about my interactions with Scandic Friends partners, and share details of my interactions with Scandic with those partners, to make the experience even more relevant to me.",
|
})}
|
||||||
})}
|
</Checkbox>
|
||||||
</Checkbox>
|
<TextLinkButton
|
||||||
<TextLinkButton
|
typography="Link/sm"
|
||||||
typography="Link/sm"
|
color="Primary"
|
||||||
color="Primary"
|
className={styles.personalizationButton}
|
||||||
className={styles.personalizationButton}
|
onClick={openPersonalizationModal}
|
||||||
onClick={openPersonalizationModal}
|
>
|
||||||
>
|
{intl.formatMessage({
|
||||||
{intl.formatMessage({
|
id: "signup.ReadMoreAboutPersonalization",
|
||||||
id: "signup.ReadMoreAboutPersonalization",
|
defaultMessage: "Read more about personalization at Scandic",
|
||||||
defaultMessage: "Read more about personalization at Scandic",
|
})}
|
||||||
})}
|
</TextLinkButton>
|
||||||
</TextLinkButton>
|
</section>
|
||||||
</section>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<section className={styles.terms}>
|
<section className={styles.terms}>
|
||||||
<header>
|
<header>
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
text-decoration-skip-ink: none;
|
text-decoration-skip-ink: none;
|
||||||
text-decoration-thickness: auto;
|
text-decoration-thickness: auto;
|
||||||
text-underline-offset: auto;
|
text-underline-offset: auto;
|
||||||
|
text-align: center;
|
||||||
text-underline-position: from-font;
|
text-underline-position: from-font;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useIntl } from "react-intl"
|
|||||||
|
|
||||||
import { sumPackages } from "@scandic-hotels/booking-flow/utils/SelectRate"
|
import { sumPackages } from "@scandic-hotels/booking-flow/utils/SelectRate"
|
||||||
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
||||||
|
import { dt } from "@scandic-hotels/common/dt"
|
||||||
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
|
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
|
||||||
import { trpc } from "@scandic-hotels/trpc/client"
|
import { trpc } from "@scandic-hotels/trpc/client"
|
||||||
|
|
||||||
@@ -66,9 +67,14 @@ export default function Steps({ closeModal }: ChangeDatesStepsProps) {
|
|||||||
|
|
||||||
setDates({ fromDate, toDate })
|
setDates({ fromDate, toDate })
|
||||||
|
|
||||||
|
const numberOfNights = dt(toDate).diff(dt(fromDate), "days")
|
||||||
|
|
||||||
const pkgsSum = sumPackages(packages)
|
const pkgsSum = sumPackages(packages)
|
||||||
const extraPrice =
|
const breakfastPrice = !!breakfast
|
||||||
pkgsSum.price + ((breakfast && breakfast.localPrice.totalPrice) || 0)
|
? breakfast.localPrice.price * numberOfNights
|
||||||
|
: 0
|
||||||
|
|
||||||
|
const extraPrice = pkgsSum.price + breakfastPrice
|
||||||
if (isLoggedIn && "member" in data.product && data.product.member) {
|
if (isLoggedIn && "member" in data.product && data.product.member) {
|
||||||
const { currency, pricePerStay } = data.product.member.localPrice
|
const { currency, pricePerStay } = data.product.member.localPrice
|
||||||
setNewPrice(formatPrice(intl, pricePerStay + extraPrice, currency))
|
setNewPrice(formatPrice(intl, pricePerStay + extraPrice, currency))
|
||||||
|
|||||||
@@ -4,13 +4,6 @@
|
|||||||
padding: var(--Space-x3) var(--Space-x2);
|
padding: var(--Space-x3) var(--Space-x2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.subtitle {
|
|
||||||
font-family: var(--typography-Subtitle-2-fontFamily);
|
|
||||||
font-size: var(--typography-Subtitle-2-Mobile-fontSize);
|
|
||||||
font-weight: var(--typography-Subtitle-2-fontWeight);
|
|
||||||
color: var(--Base-Text-High-contrast);
|
|
||||||
}
|
|
||||||
|
|
||||||
.list {
|
.list {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
width: 32px;
|
width: 32px;
|
||||||
font-size: var(--typography-Body-Bold-fontSize);
|
font-size: var(--Body-Paragraph-Size);
|
||||||
font-weight: var(--typography-Body-Bold-fontWeight);
|
font-weight: var(--Body-Paragraph-Font-weight-2);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { env } from "@/env/server"
|
|
||||||
|
|
||||||
import { getIntl } from "@/i18n"
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
import { Section } from "../Section"
|
import { Section } from "../Section"
|
||||||
@@ -17,7 +15,7 @@ export async function CommunicationSettings() {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<EmailSlot />
|
<EmailSlot />
|
||||||
{env.ENABLE_PROFILE_CONSENT && <PersonalizationSlot />}
|
<PersonalizationSlot />
|
||||||
</Section>
|
</Section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import { env } from "@/env/server"
|
|
||||||
import { getProfile, getProfilingConsent } from "@/lib/trpc/memoizedRequests"
|
import { getProfile, getProfilingConsent } from "@/lib/trpc/memoizedRequests"
|
||||||
|
|
||||||
import { GetMainIconByCSIdentifier, userHasConsent } from "../utils"
|
import { GetMainIconByCSIdentifier, userHasConsent } from "../utils"
|
||||||
@@ -9,8 +8,6 @@ import { BannerButton } from "./Button"
|
|||||||
import styles from "./profilingConsentBanner.module.css"
|
import styles from "./profilingConsentBanner.module.css"
|
||||||
|
|
||||||
export async function ProfilingConsentBanner() {
|
export async function ProfilingConsentBanner() {
|
||||||
if (!env.ENABLE_PROFILE_CONSENT) return null
|
|
||||||
|
|
||||||
const user = await getProfile()
|
const user = await getProfile()
|
||||||
if (!user || userHasConsent(user?.profilingConsent)) return null
|
if (!user || userHasConsent(user?.profilingConsent)) return null
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Profiling Consent
|
# Profiling Consent
|
||||||
|
|
||||||
Profiling consent allows users to opt in/out of personalized experiences. The feature is controlled by the `ENABLE_PROFILE_CONSENT` environment variable.
|
Profiling consent allows users to opt in/out of personalized experiences.
|
||||||
|
|
||||||
## User Journey
|
## User Journey
|
||||||
|
|
||||||
@@ -121,11 +121,9 @@ Replace `<memberKey>` with the actual `membershipNumber` or `profileId`.
|
|||||||
Required content for the feature:
|
Required content for the feature:
|
||||||
|
|
||||||
1. **Profiling Consent (config)**
|
1. **Profiling Consent (config)**
|
||||||
|
|
||||||
- Config needs to be created and published in each language
|
- Config needs to be created and published in each language
|
||||||
|
|
||||||
2. **/consent (account page)**
|
2. **/consent (account page)**
|
||||||
|
|
||||||
- Page needs to be created and published in each language
|
- Page needs to be created and published in each language
|
||||||
|
|
||||||
3. **/overview (account page)**
|
3. **/overview (account page)**
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--Space-x05);
|
gap: var(--Space-x05);
|
||||||
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
import Footnote from "@scandic-hotels/design-system/Footnote"
|
|
||||||
import {
|
import {
|
||||||
MaterialIcon,
|
MaterialIcon,
|
||||||
type MaterialIconSetIconProps,
|
type MaterialIconSetIconProps,
|
||||||
} from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
} from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||||
import Link from "@scandic-hotels/design-system/OldDSLink"
|
import { TextLink } from "@scandic-hotels/design-system/TextLink"
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
import { getValueFromContactConfig } from "@scandic-hotels/trpc/utils/contactConfig"
|
import { getValueFromContactConfig } from "@scandic-hotels/trpc/utils/contactConfig"
|
||||||
|
|
||||||
import { serverClient } from "@/lib/trpc/server"
|
import { serverClient } from "@/lib/trpc/server"
|
||||||
|
|
||||||
// import { getValueFromContactConfig } from "@/utils/contactConfig"
|
|
||||||
import styles from "./contactRow.module.css"
|
import styles from "./contactRow.module.css"
|
||||||
|
|
||||||
import type { ContactRowProps } from "@/types/components/sidebar/joinLoyaltyContact"
|
import type { ContactRowProps } from "@/types/components/sidebar/joinLoyaltyContact"
|
||||||
@@ -46,22 +44,27 @@ export default async function ContactRow({ contact }: ContactRowProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrapper}>
|
<div className={styles.wrapper}>
|
||||||
<Typography
|
{contact.display_text ? (
|
||||||
variant="Body/Paragraph/mdBold"
|
<Typography
|
||||||
className={styles.displayText}
|
variant="Body/Paragraph/mdBold"
|
||||||
>
|
className={styles.displayText}
|
||||||
<p>{contact.display_text}</p>
|
>
|
||||||
</Typography>
|
<p>{contact.display_text}</p>
|
||||||
<Link
|
</Typography>
|
||||||
|
) : null}
|
||||||
|
<TextLink
|
||||||
|
typography="Link/sm"
|
||||||
className={styles.link}
|
className={styles.link}
|
||||||
href={openableLink}
|
href={openableLink}
|
||||||
textDecoration="underline"
|
|
||||||
size="small"
|
|
||||||
>
|
>
|
||||||
{Icon ? <Icon size={20} color="Icon/Interactive/Default" /> : null}
|
{Icon ? <Icon size={20} color="Icon/Interactive/Default" /> : null}
|
||||||
{val}
|
{val}
|
||||||
</Link>
|
</TextLink>
|
||||||
{footnote && <Footnote color="burgundy">{footnote}</Footnote>}
|
{footnote && (
|
||||||
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||||
|
<p>{footnote}</p>
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,18 +13,9 @@
|
|||||||
gap: var(--Space-x15);
|
gap: var(--Space-x15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.contact > div {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 1367px) {
|
@media screen and (min-width: 1367px) {
|
||||||
.contactContainer {
|
.contactContainer {
|
||||||
align-items: start;
|
align-items: start;
|
||||||
padding-top: var(--Space-x2);
|
padding-top: var(--Space-x2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.contact > div {
|
|
||||||
justify-content: start;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
6
apps/scandic-web/env/client.ts
vendored
6
apps/scandic-web/env/client.ts
vendored
@@ -16,6 +16,11 @@ export const env = createEnv({
|
|||||||
.transform((s) =>
|
.transform((s) =>
|
||||||
getSemver("scandic-web", s, process.env.BRANCH || "development")
|
getSemver("scandic-web", s, process.env.BRANCH || "development")
|
||||||
),
|
),
|
||||||
|
NEXT_PUBLIC_NEW_POINTCLAIMS: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.default("false")
|
||||||
|
.transform((s) => s === "true"),
|
||||||
},
|
},
|
||||||
emptyStringAsUndefined: true,
|
emptyStringAsUndefined: true,
|
||||||
runtimeEnv: {
|
runtimeEnv: {
|
||||||
@@ -26,5 +31,6 @@ export const env = createEnv({
|
|||||||
process.env.NEXT_PUBLIC_SENTRY_CLIENT_SAMPLERATE,
|
process.env.NEXT_PUBLIC_SENTRY_CLIENT_SAMPLERATE,
|
||||||
NEXT_PUBLIC_PUBLIC_URL: process.env.NEXT_PUBLIC_PUBLIC_URL,
|
NEXT_PUBLIC_PUBLIC_URL: process.env.NEXT_PUBLIC_PUBLIC_URL,
|
||||||
NEXT_PUBLIC_RELEASE_TAG: process.env.NEXT_PUBLIC_RELEASE_TAG,
|
NEXT_PUBLIC_RELEASE_TAG: process.env.NEXT_PUBLIC_RELEASE_TAG,
|
||||||
|
NEXT_PUBLIC_NEW_POINTCLAIMS: process.env.NEXT_PUBLIC_NEW_POINTCLAIMS,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
6
apps/scandic-web/env/server.ts
vendored
6
apps/scandic-web/env/server.ts
vendored
@@ -96,11 +96,6 @@ export const env = createEnv({
|
|||||||
.refine((s) => s === "1" || s === "0")
|
.refine((s) => s === "1" || s === "0")
|
||||||
.transform((s) => s === "1")
|
.transform((s) => s === "1")
|
||||||
.default("0"),
|
.default("0"),
|
||||||
ENABLE_PROFILE_CONSENT: z
|
|
||||||
.string()
|
|
||||||
.refine((s) => s === "true" || s === "false")
|
|
||||||
.transform((s) => s === "true")
|
|
||||||
.default("false"),
|
|
||||||
RELEASE_TAG: z
|
RELEASE_TAG: z
|
||||||
.string()
|
.string()
|
||||||
.optional()
|
.optional()
|
||||||
@@ -160,7 +155,6 @@ export const env = createEnv({
|
|||||||
DTMC_ENTRA_ID_SECRET: process.env.DTMC_ENTRA_ID_SECRET,
|
DTMC_ENTRA_ID_SECRET: process.env.DTMC_ENTRA_ID_SECRET,
|
||||||
CHATBOT_LIVE_LANGS: process.env.CHATBOT_LIVE_LANGS,
|
CHATBOT_LIVE_LANGS: process.env.CHATBOT_LIVE_LANGS,
|
||||||
SEO_INERT: process.env.SEO_INERT,
|
SEO_INERT: process.env.SEO_INERT,
|
||||||
ENABLE_PROFILE_CONSENT: process.env.ENABLE_PROFILE_CONSENT,
|
|
||||||
RELEASE_TAG: process.env.NEXT_PUBLIC_RELEASE_TAG,
|
RELEASE_TAG: process.env.NEXT_PUBLIC_RELEASE_TAG,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 847 KiB After Width: | Height: | Size: 301 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.4 MiB After Width: | Height: | Size: 208 KiB |
@@ -1,13 +1,11 @@
|
|||||||
import Footnote from "@scandic-hotels/design-system/Footnote"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import styles from "./list.module.css"
|
import styles from "./list.module.css"
|
||||||
|
|
||||||
export default function Label({ children }: React.PropsWithChildren) {
|
export default function Label({ children }: React.PropsWithChildren) {
|
||||||
return (
|
return (
|
||||||
<li className={styles.label}>
|
<Typography variant="Title/Overline/sm">
|
||||||
<Footnote color="uiTextPlaceholder" textTransform="uppercase">
|
<li className={styles.label}>{children}</li>
|
||||||
{children}
|
</Typography>
|
||||||
</Footnote>
|
|
||||||
</li>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,5 +5,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
padding: 0 var(--Space-x1);
|
padding: 0 var(--Space-x1) var(--Space-x05);
|
||||||
|
color: var(--Text-Tertiary);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { useIntl } from "react-intl"
|
|||||||
import { useDebounceValue } from "usehooks-ts"
|
import { useDebounceValue } from "usehooks-ts"
|
||||||
|
|
||||||
import { Divider } from "@scandic-hotels/design-system/Divider"
|
import { Divider } from "@scandic-hotels/design-system/Divider"
|
||||||
import Footnote from "@scandic-hotels/design-system/Footnote"
|
|
||||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
import { trpc } from "@scandic-hotels/trpc/client"
|
import { trpc } from "@scandic-hotels/trpc/client"
|
||||||
@@ -192,16 +191,14 @@ export default function SearchList({
|
|||||||
{typeFilteredSearchHistory && typeFilteredSearchHistory.length > 0 && (
|
{typeFilteredSearchHistory && typeFilteredSearchHistory.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<Divider className={styles.noResultsDivider} />
|
<Divider className={styles.noResultsDivider} />
|
||||||
<Footnote
|
<Typography variant="Title/Overline/sm">
|
||||||
className={styles.text}
|
<p className={styles.text}>
|
||||||
color="uiTextPlaceholder"
|
{intl.formatMessage({
|
||||||
textTransform="uppercase"
|
id: "bookingWidget.searchList.latestSearches",
|
||||||
>
|
defaultMessage: "Latest searches",
|
||||||
{intl.formatMessage({
|
})}
|
||||||
id: "bookingWidget.searchList.latestSearches",
|
</p>
|
||||||
defaultMessage: "Latest searches",
|
</Typography>
|
||||||
})}
|
|
||||||
</Footnote>
|
|
||||||
<List
|
<List
|
||||||
getItemProps={getItemProps}
|
getItemProps={getItemProps}
|
||||||
highlightedIndex={highlightedIndex}
|
highlightedIndex={highlightedIndex}
|
||||||
@@ -226,12 +223,14 @@ export default function SearchList({
|
|||||||
if (displaySearchHistory) {
|
if (displaySearchHistory) {
|
||||||
return (
|
return (
|
||||||
<Dialog getMenuProps={getMenuProps}>
|
<Dialog getMenuProps={getMenuProps}>
|
||||||
<Footnote color="uiTextPlaceholder" textTransform="uppercase">
|
<Typography variant="Title/Overline/sm">
|
||||||
{intl.formatMessage({
|
<p className={styles.text}>
|
||||||
id: "bookingWidget.searchList.latestSearches",
|
{intl.formatMessage({
|
||||||
defaultMessage: "Latest searches",
|
id: "bookingWidget.searchList.latestSearches",
|
||||||
})}
|
defaultMessage: "Latest searches",
|
||||||
</Footnote>
|
})}
|
||||||
|
</p>
|
||||||
|
</Typography>
|
||||||
<List
|
<List
|
||||||
getItemProps={getItemProps}
|
getItemProps={getItemProps}
|
||||||
highlightedIndex={highlightedIndex}
|
highlightedIndex={highlightedIndex}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
|
|
||||||
.text {
|
.text {
|
||||||
padding: 0 var(--Space-x1);
|
padding: 0 var(--Space-x1);
|
||||||
|
color: var(--Text-Tertiary);
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
.textPlaceholderColor {
|
.textPlaceholderColor {
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export default function DatePickerRangeDesktop({
|
|||||||
range_start: styles.rangeStart,
|
range_start: styles.rangeStart,
|
||||||
root: `${classNames.root} ${styles.container}`,
|
root: `${classNames.root} ${styles.container}`,
|
||||||
week: styles.week,
|
week: styles.week,
|
||||||
weekday: `${classNames.weekday} ${styles.weekDay}`,
|
weekday: styles.weekDay,
|
||||||
nav: `${classNames.nav} ${styles.nav}`,
|
nav: `${classNames.nav} ${styles.nav}`,
|
||||||
button_next: `${classNames.button_next} ${styles.button_next}`,
|
button_next: `${classNames.button_next} ${styles.button_next}`,
|
||||||
button_previous: `${classNames.button_previous} ${styles.button_previous}`,
|
button_previous: `${classNames.button_previous} ${styles.button_previous}`,
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export default function DatePickerRangeMobile({
|
|||||||
range_start: styles.rangeStart,
|
range_start: styles.rangeStart,
|
||||||
root: `${classNames.root} ${styles.root}`,
|
root: `${classNames.root} ${styles.root}`,
|
||||||
week: styles.week,
|
week: styles.week,
|
||||||
weekday: `${classNames.weekday} ${styles.weekDay}`,
|
weekday: styles.weekDay,
|
||||||
}}
|
}}
|
||||||
disabled={[
|
disabled={[
|
||||||
{ from: lastDayOfPreviousMonth, to: yesterday },
|
{ from: lastDayOfPreviousMonth, to: yesterday },
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ div.months {
|
|||||||
td.day,
|
td.day,
|
||||||
td.rangeEnd,
|
td.rangeEnd,
|
||||||
td.rangeStart {
|
td.rangeStart {
|
||||||
font-family: var(--typography-Body-Bold-fontFamily);
|
font-family: var(--Body-Paragraph-Font-family), var(--Body-Paragraph-Font-fallback);
|
||||||
font-size: var(--typography-Body-Bold-fontSize);
|
font-size: var(--Body-Paragraph-Size);
|
||||||
font-weight: 500;
|
font-weight: var(--Body-Paragraph-Font-weight-2);
|
||||||
letter-spacing: var(--typography-Body-Bold-letterSpacing);
|
letter-spacing: var(--Body-Paragraph-Letter-spacing);
|
||||||
line-height: var(--typography-Body-Bold-lineHeight);
|
line-height: 1.5;
|
||||||
text-decoration: var(--typography-Body-Bold-textDecoration);
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
td.rangeEnd,
|
td.rangeEnd,
|
||||||
@@ -92,14 +92,15 @@ td.day[data-outside="true"] ~ td.day[data-disabled="true"] button.dayButton,
|
|||||||
}
|
}
|
||||||
|
|
||||||
.weekDay {
|
.weekDay {
|
||||||
color: var(--UI-Text-Placeholder);
|
color: var(--Text-Tertiary);
|
||||||
font-family: var(--typography-Footnote-Labels-fontFamily);
|
font-family: var(--Title-Overline-sm-Font-family), var(--Title-Overline-sm-Font-fallback);
|
||||||
font-size: var(--typography-Footnote-Labels-fontSize);
|
font-size: var(--Title-Overline-sm-Size);
|
||||||
font-weight: var(--typography-Footnote-Labels-fontWeight);
|
font-style: normal;
|
||||||
letter-spacing: var(--typography-Footnote-Labels-letterSpacing);
|
font-weight: var(--Title-Overline-sm-Font-weight);
|
||||||
line-height: var(--typography-Footnote-Labels-lineHeight);
|
line-height: 1.5;
|
||||||
text-decoration: var(--typography-Footnote-Labels-textDecoration);
|
letter-spacing: var(--Title-Overline-sm-Letter-spacing);
|
||||||
text-transform: uppercase;
|
text-transform: var(--Title-Overline-sm-Text-Transform);
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
|
|||||||
@@ -97,12 +97,12 @@ div.months {
|
|||||||
td.day,
|
td.day,
|
||||||
td.rangeEnd,
|
td.rangeEnd,
|
||||||
td.rangeStart {
|
td.rangeStart {
|
||||||
font-family: var(--typography-Body-Bold-fontFamily);
|
font-family: var(--Body-Paragraph-Font-family), var(--Body-Paragraph-Font-fallback);
|
||||||
font-size: var(--typography-Body-Bold-fontSize);
|
font-size: var(--Body-Paragraph-Size);
|
||||||
font-weight: 500;
|
font-weight: var(--Body-Paragraph-Font-weight-2);
|
||||||
letter-spacing: var(--typography-Body-Bold-letterSpacing);
|
letter-spacing: var(--Body-Paragraph-Letter-spacing);
|
||||||
line-height: var(--typography-Body-Bold-lineHeight);
|
line-height: 1.5;
|
||||||
text-decoration: var(--typography-Body-Bold-textDecoration);
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
td.rangeEnd,
|
td.rangeEnd,
|
||||||
@@ -165,15 +165,15 @@ td.day[data-outside="true"] ~ td.day[data-disabled="true"] button.dayButton,
|
|||||||
}
|
}
|
||||||
|
|
||||||
.weekDay {
|
.weekDay {
|
||||||
color: var(--Base-Text-Medium-contrast);
|
color: var(--Text-Tertiary);
|
||||||
opacity: 1;
|
font-family: var(--Title-Overline-sm-Font-family), var(--Title-Overline-sm-Font-fallback);
|
||||||
font-family: var(--typography-Caption-Labels-fontFamily);
|
font-size: var(--Title-Overline-sm-Size);
|
||||||
font-size: var(--typography-Caption-Labels-fontSize);
|
font-style: normal;
|
||||||
font-weight: var(--typography-Caption-Labels-fontWeight);
|
font-weight: var(--Title-Overline-sm-Font-weight);
|
||||||
letter-spacing: var(--typography-Caption-Labels-letterSpacing);
|
line-height: 1.5;
|
||||||
line-height: var(--typography-Caption-Labels-lineHeight);
|
letter-spacing: var(--Title-Overline-sm-Letter-spacing);
|
||||||
text-decoration: var(--typography-Caption-Labels-textDecoration);
|
text-transform: var(--Title-Overline-sm-Text-Transform);
|
||||||
text-transform: uppercase;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 1367px) {
|
@media screen and (min-width: 1367px) {
|
||||||
|
|||||||
@@ -4,9 +4,8 @@ import { useIntl } from "react-intl"
|
|||||||
|
|
||||||
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
||||||
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
|
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
|
||||||
import Footnote from "@scandic-hotels/design-system/Footnote"
|
|
||||||
import Checkbox from "@scandic-hotels/design-system/Form/Checkbox"
|
import Checkbox from "@scandic-hotels/design-system/Form/Checkbox"
|
||||||
import Link from "@scandic-hotels/design-system/OldDSLink"
|
import { TextLink } from "@scandic-hotels/design-system/TextLink"
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import { useBookingFlowConfig } from "../../../../../bookingFlowConfig/bookingFlowConfigContext"
|
import { useBookingFlowConfig } from "../../../../../bookingFlowConfig/bookingFlowConfigContext"
|
||||||
@@ -83,8 +82,8 @@ export default function JoinScandicFriendsCard({
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
|
|
||||||
<div className={styles.terms}>
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||||
<Footnote color="uiTextPlaceholder">
|
<p className={styles.terms}>
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{
|
{
|
||||||
id: "enterDetails.joinScandicFriendsCard.terms",
|
id: "enterDetails.joinScandicFriendsCard.terms",
|
||||||
@@ -93,19 +92,18 @@ export default function JoinScandicFriendsCard({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
termsAndConditionsLink: (str) => (
|
termsAndConditionsLink: (str) => (
|
||||||
<Link
|
<TextLink
|
||||||
textDecoration="underline"
|
typography="Link/sm"
|
||||||
size="tiny"
|
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href={routes.membershipTermsAndConditions[lang]}
|
href={routes.membershipTermsAndConditions[lang]}
|
||||||
>
|
>
|
||||||
{str}
|
{str}
|
||||||
</Link>
|
</TextLink>
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
</Footnote>
|
</p>
|
||||||
</div>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
.terms {
|
.terms {
|
||||||
grid-area: terms;
|
grid-area: terms;
|
||||||
|
color: var(--Text-Secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
@media screen and (min-width: 768px) {
|
||||||
|
|||||||
@@ -4,9 +4,8 @@ import { useIntl } from "react-intl"
|
|||||||
|
|
||||||
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
||||||
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
|
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
|
||||||
import Footnote from "@scandic-hotels/design-system/Footnote"
|
|
||||||
import Checkbox from "@scandic-hotels/design-system/Form/Checkbox"
|
import Checkbox from "@scandic-hotels/design-system/Form/Checkbox"
|
||||||
import Link from "@scandic-hotels/design-system/OldDSLink"
|
import { TextLink } from "@scandic-hotels/design-system/TextLink"
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import { useBookingFlowConfig } from "../../../../../bookingFlowConfig/bookingFlowConfigContext"
|
import { useBookingFlowConfig } from "../../../../../bookingFlowConfig/bookingFlowConfigContext"
|
||||||
@@ -97,8 +96,8 @@ export function PartnerSASJoinScandicFriendsCard({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.terms}>
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||||
<Footnote color="uiTextPlaceholder">
|
<p className={styles.terms}>
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{
|
{
|
||||||
id: "enterDetails.joinScandicFriendsCard.terms",
|
id: "enterDetails.joinScandicFriendsCard.terms",
|
||||||
@@ -107,19 +106,18 @@ export function PartnerSASJoinScandicFriendsCard({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
termsAndConditionsLink: (str) => (
|
termsAndConditionsLink: (str) => (
|
||||||
<Link
|
<TextLink
|
||||||
textDecoration="underline"
|
typography="Link/sm"
|
||||||
size="tiny"
|
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href={routes.membershipTermsAndConditions[lang]}
|
href={routes.membershipTermsAndConditions[lang]}
|
||||||
>
|
>
|
||||||
{str}
|
{str}
|
||||||
</Link>
|
</TextLink>
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
</Footnote>
|
</p>
|
||||||
</div>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
|
|
||||||
.terms {
|
.terms {
|
||||||
grid-area: terms;
|
grid-area: terms;
|
||||||
|
color: var(--Text-Secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
@media screen and (min-width: 768px) {
|
||||||
|
|||||||
@@ -4,10 +4,9 @@ import { useIntl } from "react-intl"
|
|||||||
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
||||||
import { useLazyPathname } from "@scandic-hotels/common/hooks/useLazyPathname"
|
import { useLazyPathname } from "@scandic-hotels/common/hooks/useLazyPathname"
|
||||||
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
|
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
|
||||||
import Footnote from "@scandic-hotels/design-system/Footnote"
|
|
||||||
import Checkbox from "@scandic-hotels/design-system/Form/Checkbox"
|
import Checkbox from "@scandic-hotels/design-system/Form/Checkbox"
|
||||||
import { LoginButton } from "@scandic-hotels/design-system/LoginButton"
|
import { LoginButton } from "@scandic-hotels/design-system/LoginButton"
|
||||||
import Link from "@scandic-hotels/design-system/OldDSLink"
|
import { TextLink } from "@scandic-hotels/design-system/TextLink"
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
import { trackEvent } from "@scandic-hotels/tracking/base"
|
import { trackEvent } from "@scandic-hotels/tracking/base"
|
||||||
import { trackLoginClick } from "@scandic-hotels/tracking/navigation"
|
import { trackLoginClick } from "@scandic-hotels/tracking/navigation"
|
||||||
@@ -101,8 +100,8 @@ export function JoinScandicFriendsCard({ name = "join" }: Props) {
|
|||||||
})}
|
})}
|
||||||
</LoginButton>
|
</LoginButton>
|
||||||
|
|
||||||
<div className={styles.terms}>
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||||
<Footnote color="uiTextPlaceholder">
|
<p className={styles.terms}>
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{
|
{
|
||||||
id: "enterDetails.joinScandicFriendsCard.terms",
|
id: "enterDetails.joinScandicFriendsCard.terms",
|
||||||
@@ -111,19 +110,18 @@ export function JoinScandicFriendsCard({ name = "join" }: Props) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
termsAndConditionsLink: (str) => (
|
termsAndConditionsLink: (str) => (
|
||||||
<Link
|
<TextLink
|
||||||
textDecoration="underline"
|
typography="Link/sm"
|
||||||
size="tiny"
|
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href={routes.membershipTermsAndConditions[lang]}
|
href={routes.membershipTermsAndConditions[lang]}
|
||||||
>
|
>
|
||||||
{str}
|
{str}
|
||||||
</Link>
|
</TextLink>
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
</Footnote>
|
</p>
|
||||||
</div>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
|
|
||||||
.terms {
|
.terms {
|
||||||
grid-area: terms;
|
grid-area: terms;
|
||||||
|
color: var(--Text-Secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
@media screen and (min-width: 768px) {
|
||||||
|
|||||||
@@ -4,9 +4,8 @@ import { useIntl } from "react-intl"
|
|||||||
|
|
||||||
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
||||||
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
|
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
|
||||||
import Footnote from "@scandic-hotels/design-system/Footnote"
|
|
||||||
import Checkbox from "@scandic-hotels/design-system/Form/Checkbox"
|
import Checkbox from "@scandic-hotels/design-system/Form/Checkbox"
|
||||||
import Link from "@scandic-hotels/design-system/OldDSLink"
|
import { TextLink } from "@scandic-hotels/design-system/TextLink"
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
import { trpc } from "@scandic-hotels/trpc/client"
|
import { trpc } from "@scandic-hotels/trpc/client"
|
||||||
|
|
||||||
@@ -94,8 +93,8 @@ export function PartnerSASJoinScandicFriendsCard({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.terms}>
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||||
<Footnote color="uiTextPlaceholder">
|
<p className={styles.terms}>
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{
|
{
|
||||||
id: "enterDetails.joinScandicFriendsCard.terms",
|
id: "enterDetails.joinScandicFriendsCard.terms",
|
||||||
@@ -104,19 +103,18 @@ export function PartnerSASJoinScandicFriendsCard({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
termsAndConditionsLink: (str) => (
|
termsAndConditionsLink: (str) => (
|
||||||
<Link
|
<TextLink
|
||||||
textDecoration="underline"
|
typography="Link/sm"
|
||||||
size="tiny"
|
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href={routes.membershipTermsAndConditions[lang]}
|
href={routes.membershipTermsAndConditions[lang]}
|
||||||
>
|
>
|
||||||
{str}
|
{str}
|
||||||
</Link>
|
</TextLink>
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
</Footnote>
|
</p>
|
||||||
</div>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
|
|
||||||
.terms {
|
.terms {
|
||||||
grid-area: terms;
|
grid-area: terms;
|
||||||
|
color: var(--Text-Secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
@media screen and (min-width: 768px) {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { useIntl } from "react-intl"
|
|||||||
|
|
||||||
import { selectRate } from "@scandic-hotels/common/constants/routes/hotelReservation"
|
import { selectRate } from "@scandic-hotels/common/constants/routes/hotelReservation"
|
||||||
import { Button } from "@scandic-hotels/design-system/Button"
|
import { Button } from "@scandic-hotels/design-system/Button"
|
||||||
import Footnote from "@scandic-hotels/design-system/Footnote"
|
|
||||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
import { getHotelRoom } from "@scandic-hotels/trpc/routers/booking/helpers"
|
import { getHotelRoom } from "@scandic-hotels/trpc/routers/booking/helpers"
|
||||||
@@ -47,20 +46,14 @@ export default function SelectedRoom() {
|
|||||||
<div className={styles.wrapper} data-available={room.isAvailable}>
|
<div className={styles.wrapper} data-available={room.isAvailable}>
|
||||||
<div className={styles.main}>
|
<div className={styles.main}>
|
||||||
<div className={styles.headerContainer}>
|
<div className={styles.headerContainer}>
|
||||||
<Footnote
|
<Typography variant="Title/Overline/sm">
|
||||||
className={styles.title}
|
<h2 className={styles.title}>
|
||||||
asChild
|
|
||||||
textTransform="uppercase"
|
|
||||||
type="label"
|
|
||||||
color="uiTextHighContrast"
|
|
||||||
>
|
|
||||||
<h2>
|
|
||||||
{intl.formatMessage({
|
{intl.formatMessage({
|
||||||
id: "common.room",
|
id: "common.room",
|
||||||
defaultMessage: "Room",
|
defaultMessage: "Room",
|
||||||
})}
|
})}
|
||||||
</h2>
|
</h2>
|
||||||
</Footnote>
|
</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
variant="Title/Subtitle/md"
|
variant="Title/Subtitle/md"
|
||||||
className={styles.description}
|
className={styles.description}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.facilities {
|
.facilities {
|
||||||
font-family: var(--typography-Body-Bold-fontFamily);
|
font-family: var(--Body-Paragraph-Font-family), var(--Body-Paragraph-Font-fallback);
|
||||||
padding-bottom: var(--Space-x3);
|
padding-bottom: var(--Space-x3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -177,12 +177,12 @@ export function SelectHotelMapContent({
|
|||||||
>
|
>
|
||||||
<MaterialIcon icon="close" size={20} color="CurrentColor" />
|
<MaterialIcon icon="close" size={20} color="CurrentColor" />
|
||||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||||
<p>
|
<span>
|
||||||
{intl.formatMessage({
|
{intl.formatMessage({
|
||||||
id: "selectHotel.closeMap",
|
id: "selectHotel.closeMap",
|
||||||
defaultMessage: "Close the map",
|
defaultMessage: "Close the map",
|
||||||
})}
|
})}
|
||||||
</p>
|
</span>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
.link {
|
.link {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--Space-x05);
|
gap: var(--Space-x05);
|
||||||
align-items: baseline;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bookingCodeFilter {
|
.bookingCodeFilter {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
|
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
|
||||||
import Footnote from "@scandic-hotels/design-system/Footnote"
|
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import { useBookingFlowConfig } from "../../bookingFlowConfig/bookingFlowConfigContext"
|
import { useBookingFlowConfig } from "../../bookingFlowConfig/bookingFlowConfigContext"
|
||||||
@@ -37,9 +36,11 @@ export default function SignupPromoDesktop({
|
|||||||
data-testid="signup-promo-desktop"
|
data-testid="signup-promo-desktop"
|
||||||
>
|
>
|
||||||
{badgeContent && <span className={styles.badge}>{badgeContent}</span>}
|
{badgeContent && <span className={styles.badge}>{badgeContent}</span>}
|
||||||
<Footnote color="burgundy">
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||||
<Message price={price} isEnterDetailsPage={isEnterDetailsPage} />
|
<p>
|
||||||
</Footnote>
|
<Message price={price} isEnterDetailsPage={isEnterDetailsPage} />
|
||||||
|
</p>
|
||||||
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import Footnote from "@scandic-hotels/design-system/Footnote"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import { useBookingFlowConfig } from "../../bookingFlowConfig/bookingFlowConfigContext"
|
import { useBookingFlowConfig } from "../../bookingFlowConfig/bookingFlowConfigContext"
|
||||||
|
|
||||||
@@ -14,9 +14,11 @@ export default function SignupPromoMobile() {
|
|||||||
data-footer-spacing-signup
|
data-footer-spacing-signup
|
||||||
className={styles.memberDiscountBannerMobile}
|
className={styles.memberDiscountBannerMobile}
|
||||||
>
|
>
|
||||||
<Footnote color="burgundy">
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||||
<Message />
|
<p>
|
||||||
</Footnote>
|
<Message />
|
||||||
|
</p>
|
||||||
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
color: var(--Text-Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
.memberDiscountBannerDesktop {
|
.memberDiscountBannerDesktop {
|
||||||
@@ -16,10 +17,11 @@
|
|||||||
padding: var(--Space-x15) var(--Space-x2);
|
padding: var(--Space-x15) var(--Space-x2);
|
||||||
gap: var(--Space-x2);
|
gap: var(--Space-x2);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
color: var(--Text-Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
.red {
|
.red {
|
||||||
color: var(--Text-Accent-Primary);
|
color: var(--Scandic-Brand-Scandic-Red);
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge {
|
.badge {
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
.footnote {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footnoteFontOnly {
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bold {
|
|
||||||
font-family: var(--typography-Footnote-Bold-fontFamily);
|
|
||||||
font-size: var(--typography-Footnote-Bold-fontSize);
|
|
||||||
font-weight: var(--typography-Footnote-Bold-fontWeight);
|
|
||||||
letter-spacing: var(--typography-Footnote-Bold-letterSpacing);
|
|
||||||
line-height: var(--typography-Footnote-Bold-lineHeight);
|
|
||||||
text-decoration: var(--typography-Footnote-Bold-textDecoration);
|
|
||||||
}
|
|
||||||
|
|
||||||
.regular {
|
|
||||||
font-family: var(--typography-Footnote-Regular-fontFamily);
|
|
||||||
font-size: var(--typography-Footnote-Regular-fontSize);
|
|
||||||
font-weight: var(--typography-Footnote-Regular-fontWeight);
|
|
||||||
letter-spacing: var(--typography-Footnote-Regular-letterSpacing);
|
|
||||||
line-height: var(--typography-Footnote-Regular-lineHeight);
|
|
||||||
text-decoration: var(--typography-Footnote-Regular-textDecoration);
|
|
||||||
}
|
|
||||||
|
|
||||||
.labels {
|
|
||||||
font-family: var(--typography-Footnote-Labels-fontFamily);
|
|
||||||
font-size: var(--typography-Footnote-Labels-fontSize);
|
|
||||||
font-weight: var(--typography-Footnote-Labels-fontWeight);
|
|
||||||
letter-spacing: var(--typography-Footnote-Labels-letterSpacing);
|
|
||||||
line-height: var(--typography-Footnote-Labels-lineHeight);
|
|
||||||
text-decoration: var(--typography-Footnote-Labels-textDecoration);
|
|
||||||
}
|
|
||||||
|
|
||||||
.uppercase {
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.center {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.black {
|
|
||||||
color: var(--Main-Grey-100);
|
|
||||||
}
|
|
||||||
|
|
||||||
.burgundy {
|
|
||||||
color: var(--Scandic-Brand-Burgundy);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pale {
|
|
||||||
color: var(--Scandic-Brand-Pale-Peach);
|
|
||||||
}
|
|
||||||
|
|
||||||
.peach50 {
|
|
||||||
color: var(--Scandic-Peach-50);
|
|
||||||
}
|
|
||||||
|
|
||||||
.uiTextMediumContrast {
|
|
||||||
color: var(--UI-Text-Medium-contrast);
|
|
||||||
}
|
|
||||||
|
|
||||||
.uiTextHighContrast {
|
|
||||||
color: var(--UI-Text-High-contrast);
|
|
||||||
}
|
|
||||||
.uiTextPlaceholder {
|
|
||||||
color: var(--UI-Text-Placeholder);
|
|
||||||
}
|
|
||||||
|
|
||||||
.white {
|
|
||||||
color: var(--Main-Grey-White);
|
|
||||||
}
|
|
||||||
|
|
||||||
.baseTextDisabled {
|
|
||||||
color: var(--Base-Text-Disabled);
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import { Slot } from "@radix-ui/react-slot"
|
|
||||||
|
|
||||||
import { footnoteFontOnlyVariants, footnoteVariants } from "./variants"
|
|
||||||
|
|
||||||
import type { VariantProps } from "class-variance-authority"
|
|
||||||
|
|
||||||
interface FootnoteProps
|
|
||||||
extends
|
|
||||||
Omit<React.HTMLAttributes<HTMLParagraphElement>, "color">,
|
|
||||||
VariantProps<typeof footnoteVariants> {
|
|
||||||
asChild?: boolean
|
|
||||||
fontOnly?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use `@scandic-hotels/design-system/Typography` instead.
|
|
||||||
*/
|
|
||||||
export default function Footnote({
|
|
||||||
asChild = false,
|
|
||||||
className = "",
|
|
||||||
color,
|
|
||||||
fontOnly = false,
|
|
||||||
textAlign,
|
|
||||||
textTransform,
|
|
||||||
type,
|
|
||||||
...props
|
|
||||||
}: FootnoteProps) {
|
|
||||||
const Comp = asChild ? Slot : "p"
|
|
||||||
const classNames = fontOnly
|
|
||||||
? footnoteFontOnlyVariants({
|
|
||||||
className,
|
|
||||||
textAlign,
|
|
||||||
textTransform,
|
|
||||||
type,
|
|
||||||
})
|
|
||||||
: footnoteVariants({
|
|
||||||
className,
|
|
||||||
color,
|
|
||||||
textAlign,
|
|
||||||
textTransform,
|
|
||||||
type,
|
|
||||||
})
|
|
||||||
return <Comp className={classNames} {...props} />
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
import { cva } from "class-variance-authority"
|
|
||||||
|
|
||||||
import styles from "./footnote.module.css"
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
variants: {
|
|
||||||
type: {
|
|
||||||
regular: styles.regular,
|
|
||||||
bold: styles.bold,
|
|
||||||
label: styles.labels,
|
|
||||||
},
|
|
||||||
color: {
|
|
||||||
black: styles.black,
|
|
||||||
burgundy: styles.burgundy,
|
|
||||||
pale: styles.pale,
|
|
||||||
peach50: styles.peach50,
|
|
||||||
uiTextMediumContrast: styles.uiTextMediumContrast,
|
|
||||||
uiTextHighContrast: styles.uiTextHighContrast,
|
|
||||||
uiTextPlaceholder: styles.uiTextPlaceholder,
|
|
||||||
white: styles.white,
|
|
||||||
baseTextDisabled: styles.baseTextDisabled,
|
|
||||||
},
|
|
||||||
textAlign: {
|
|
||||||
center: styles.center,
|
|
||||||
left: styles.left,
|
|
||||||
},
|
|
||||||
textTransform: {
|
|
||||||
uppercase: styles.uppercase,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
type: "regular",
|
|
||||||
},
|
|
||||||
} as const
|
|
||||||
|
|
||||||
export const footnoteVariants = cva(styles.footnote, config)
|
|
||||||
|
|
||||||
const fontOnlyConfig = {
|
|
||||||
variants: {
|
|
||||||
type: {
|
|
||||||
regular: styles.regular,
|
|
||||||
bold: styles.bold,
|
|
||||||
label: styles.labels,
|
|
||||||
},
|
|
||||||
textAlign: {
|
|
||||||
center: styles.center,
|
|
||||||
left: styles.left,
|
|
||||||
},
|
|
||||||
textTransform: {
|
|
||||||
uppercase: styles.uppercase,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
type: "regular",
|
|
||||||
},
|
|
||||||
} as const
|
|
||||||
|
|
||||||
export const footnoteFontOnlyVariants = cva(
|
|
||||||
styles.footnoteFontOnly,
|
|
||||||
fontOnlyConfig
|
|
||||||
)
|
|
||||||
@@ -85,7 +85,10 @@ export const FormInput = forwardRef<HTMLInputElement, FormInputProps>(
|
|||||||
ref={mergeRefs(field.ref, ref)}
|
ref={mergeRefs(field.ref, ref)}
|
||||||
name={field.name}
|
name={field.name}
|
||||||
onBlur={field.onBlur}
|
onBlur={field.onBlur}
|
||||||
onChange={field.onChange}
|
onChange={(event) => {
|
||||||
|
field.onChange(event)
|
||||||
|
props.onChange?.(event)
|
||||||
|
}}
|
||||||
value={field.value ?? ""}
|
value={field.value ?? ""}
|
||||||
autoComplete={autoComplete}
|
autoComplete={autoComplete}
|
||||||
id={id ?? field.name}
|
id={id ?? field.name}
|
||||||
|
|||||||
@@ -35,8 +35,3 @@
|
|||||||
justify-content: start;
|
justify-content: start;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.perNight {
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: var(--typography-Caption-Regular-fontSize);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { cx } from "class-variance-authority"
|
import { cx } from "class-variance-authority"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { Divider } from "../../Divider"
|
|
||||||
import { RateTypeEnum } from "@scandic-hotels/common/constants/rateType"
|
import { RateTypeEnum } from "@scandic-hotels/common/constants/rateType"
|
||||||
|
import { Divider } from "../../Divider"
|
||||||
import { Typography } from "../../Typography"
|
import { Typography } from "../../Typography"
|
||||||
import styles from "./hotelPriceCard.module.css"
|
import styles from "./hotelPriceCard.module.css"
|
||||||
|
|
||||||
@@ -117,14 +117,16 @@ export function HotelPriceCard({
|
|||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
{productTypePrices.localPrice.currency}
|
{productTypePrices.localPrice.currency}
|
||||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||||
<span className={styles.perNight}>
|
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||||
/
|
<span>
|
||||||
{intl.formatMessage({
|
/
|
||||||
id: "common.night",
|
{intl.formatMessage({
|
||||||
defaultMessage: "night",
|
id: "common.night",
|
||||||
})}
|
defaultMessage: "night",
|
||||||
</span>
|
})}
|
||||||
|
</span>
|
||||||
|
</Typography>
|
||||||
</p>
|
</p>
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -160,6 +160,8 @@ import EditOutlined from "./generated/EditOutlined"
|
|||||||
import EditFilled from "./generated/EditFilled"
|
import EditFilled from "./generated/EditFilled"
|
||||||
import EditCalendarOutlined from "./generated/EditCalendarOutlined"
|
import EditCalendarOutlined from "./generated/EditCalendarOutlined"
|
||||||
import EditCalendarFilled from "./generated/EditCalendarFilled"
|
import EditCalendarFilled from "./generated/EditCalendarFilled"
|
||||||
|
import EditDocumentOutlined from "./generated/EditDocumentOutlined"
|
||||||
|
import EditDocumentFilled from "./generated/EditDocumentFilled"
|
||||||
import EditSquareOutlined from "./generated/EditSquareOutlined"
|
import EditSquareOutlined from "./generated/EditSquareOutlined"
|
||||||
import EditSquareFilled from "./generated/EditSquareFilled"
|
import EditSquareFilled from "./generated/EditSquareFilled"
|
||||||
import ElectricBikeOutlined from "./generated/ElectricBikeOutlined"
|
import ElectricBikeOutlined from "./generated/ElectricBikeOutlined"
|
||||||
@@ -642,6 +644,9 @@ const _materialIcons = {
|
|||||||
edit_calendar: {
|
edit_calendar: {
|
||||||
rounded: { outlined: EditCalendarOutlined, filled: EditCalendarFilled },
|
rounded: { outlined: EditCalendarOutlined, filled: EditCalendarFilled },
|
||||||
},
|
},
|
||||||
|
edit_document: {
|
||||||
|
rounded: { outlined: EditDocumentOutlined, filled: EditDocumentFilled },
|
||||||
|
},
|
||||||
edit_square: {
|
edit_square: {
|
||||||
rounded: { outlined: EditSquareOutlined, filled: EditSquareFilled },
|
rounded: { outlined: EditSquareOutlined, filled: EditSquareFilled },
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/* AUTO-GENERATED — DO NOT EDIT */
|
||||||
|
import type { SVGProps } from "react"
|
||||||
|
|
||||||
|
const EditDocumentFilled = (props: SVGProps<SVGSVGElement>) => (
|
||||||
|
<svg viewBox="0 -960 960 960" {...props}>
|
||||||
|
<path d="M220-80q-24 0-42-18t-18-42v-680q0-24 18-42t42-18h315q12 0 23.5 5t19.5 13l204 204q8 8 13 19.5t5 23.5v99q0 8-5 14.5t-13 8.5q-12 5-23 11.5T738-465L518-246q-8 8-13 19.5t-5 23.5v93q0 13-8.5 21.5T470-80zm340-30v-81q0-6 2-11t7-10l212-211q9-9 20-13t22-4q12 0 23 4.5t20 13.5l37 37q9 9 13 20t4 22-4.5 22.5T902-300L692-89q-5 5-10 7t-11 2h-81q-13 0-21.5-8.5T560-110m263-194 37-39-37-37-38 38zM550-600h190L520-820l220 220-220-220v190q0 13 8.5 21.5T550-600" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default EditDocumentFilled
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/* AUTO-GENERATED — DO NOT EDIT */
|
||||||
|
import type { SVGProps } from "react"
|
||||||
|
|
||||||
|
const EditDocumentOutlined = (props: SVGProps<SVGSVGElement>) => (
|
||||||
|
<svg viewBox="0 -960 960 960" {...props}>
|
||||||
|
<path d="M560-110v-81q0-5.57 2-10.78 2-5.22 7-10.22l211.61-210.77q9.11-9.12 20.25-13.18Q812-440 823-440q12 0 23 4.5t20 13.5l37 37q9 9 13 20t4 22-4.5 22.5-13.58 20.62L692-89q-5 5-10.22 7-5.21 2-10.78 2h-81q-12.75 0-21.37-8.63Q560-97.25 560-110m300-233-37-37zM620-140h38l121-122-37-37-122 121zM220-80q-24 0-42-18t-18-42v-680q0-24 18-42t42-18h315q12.44 0 23.72 5T578-862l204 204q8 8 13 19.28t5 23.72v71q0 12.75-8.68 21.37-8.67 8.63-21.5 8.63-12.82 0-21.32-8.63-8.5-8.62-8.5-21.37v-56H550q-12.75 0-21.37-8.63Q520-617.25 520-630v-190H220v680h250q12.75 0 21.38 8.68 8.62 8.67 8.62 21.5 0 12.82-8.62 21.32Q482.75-80 470-80zm0-60v-680zm541-141-19-18 37 37z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default EditDocumentOutlined
|
||||||
@@ -4,9 +4,7 @@ import { nodesToHtml } from "./utils"
|
|||||||
|
|
||||||
import styles from "./jsontohtml.module.css"
|
import styles from "./jsontohtml.module.css"
|
||||||
|
|
||||||
import { AlertTypeEnum } from "@scandic-hotels/common/constants/alert"
|
|
||||||
import { ImageVaultAsset } from "@scandic-hotels/common/utils/imageVault"
|
import { ImageVaultAsset } from "@scandic-hotels/common/utils/imageVault"
|
||||||
import { AlertSidepeekContent } from "../../types/sidepeekContent"
|
|
||||||
import { ContentBlockType } from "./types/rte/enums"
|
import { ContentBlockType } from "./types/rte/enums"
|
||||||
import type { RTENode } from "./types/rte/node"
|
import type { RTENode } from "./types/rte/node"
|
||||||
import type { RenderOptions } from "./types/rte/option"
|
import type { RenderOptions } from "./types/rte/option"
|
||||||
@@ -17,7 +15,7 @@ export type Node<T> = {
|
|||||||
|
|
||||||
export type Embeds =
|
export type Embeds =
|
||||||
| {
|
| {
|
||||||
__typename: Exclude<ContentBlockType, "ImageContainer" | "Alert">
|
__typename: Exclude<ContentBlockType, "ImageContainer">
|
||||||
system?: { uid: string } | null
|
system?: { uid: string } | null
|
||||||
url?: string | null
|
url?: string | null
|
||||||
permanent_url?: string | null
|
permanent_url?: string | null
|
||||||
@@ -31,25 +29,6 @@ export type Embeds =
|
|||||||
image_left?: ImageVaultAsset
|
image_left?: ImageVaultAsset
|
||||||
image_right?: ImageVaultAsset
|
image_right?: ImageVaultAsset
|
||||||
}
|
}
|
||||||
| {
|
|
||||||
__typename: "Alert"
|
|
||||||
system?: { uid: string } | null
|
|
||||||
type: AlertTypeEnum
|
|
||||||
heading: string | null
|
|
||||||
text: string
|
|
||||||
phoneContact?: {
|
|
||||||
displayText: string
|
|
||||||
phoneNumber: string
|
|
||||||
footnote?: string | null
|
|
||||||
} | null
|
|
||||||
sidepeekContent?: AlertSidepeekContent | null
|
|
||||||
sidepeekCtaText?: string | null
|
|
||||||
link?: {
|
|
||||||
url: string
|
|
||||||
title: string
|
|
||||||
keepSearchParams?: boolean
|
|
||||||
} | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EmbedByUid = Record<string, Node<Embeds>>
|
export type EmbedByUid = Record<string, Node<Embeds>>
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import {
|
|||||||
mapImageVaultAssetResponseToImageVaultAsset,
|
mapImageVaultAssetResponseToImageVaultAsset,
|
||||||
mapInsertResponseToImageVaultAsset,
|
mapInsertResponseToImageVaultAsset,
|
||||||
} from "@scandic-hotels/common/utils/imageVault"
|
} from "@scandic-hotels/common/utils/imageVault"
|
||||||
import { Alert } from "../Alert"
|
|
||||||
import { TextLink } from "../TextLink"
|
import { TextLink } from "../TextLink"
|
||||||
import type { EmbedByUid } from "./JsonToHtml"
|
import type { EmbedByUid } from "./JsonToHtml"
|
||||||
import type { Attributes } from "./types/rte/attrs"
|
import type { Attributes } from "./types/rte/attrs"
|
||||||
@@ -459,8 +458,6 @@ export const renderOptions: RenderOptions = {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
} else if (entry?.node.__typename === "Alert") {
|
|
||||||
return <Alert key={node.uid} {...entry.node} />
|
|
||||||
} else if (
|
} else if (
|
||||||
entry?.node.__typename === "AccountPage" ||
|
entry?.node.__typename === "AccountPage" ||
|
||||||
entry?.node.__typename === "CampaignOverviewPage" ||
|
entry?.node.__typename === "CampaignOverviewPage" ||
|
||||||
|
|||||||
@@ -50,30 +50,15 @@
|
|||||||
gap: var(--Space-x05);
|
gap: var(--Space-x05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumb {
|
|
||||||
font-family: var(--typography-Footnote-Bold-fontFamily);
|
|
||||||
font-size: var(--typography-Footnote-Bold-fontSize);
|
|
||||||
font-weight: 500;
|
|
||||||
letter-spacing: var(--typography-Footnote-Bold-letterSpacing);
|
|
||||||
line-height: var(--typography-Footnote-Bold-lineHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
.link.breadcrumb {
|
|
||||||
font-family: var(--typography-Footnote-Bold-fontFamily);
|
|
||||||
font-size: var(--typography-Footnote-Bold-fontSize);
|
|
||||||
font-weight: 500;
|
|
||||||
letter-spacing: var(--typography-Footnote-Bold-letterSpacing);
|
|
||||||
line-height: var(--typography-Footnote-Bold-lineHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
.myPageMobileDropdown {
|
.myPageMobileDropdown {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: var(--Scandic-Brand-Burgundy);
|
color: var(--Scandic-Brand-Burgundy);
|
||||||
font-family: var(--typography-Body-Regular-fontFamily);
|
font-family:
|
||||||
font-size: var(--typography-Body-Regular-fontSize);
|
var(--Body-Paragraph-Font-family), var(--Body-Paragraph-Font-fallback);
|
||||||
line-height: var(--typography-Body-Regular-lineHeight);
|
font-size: var(--Body-Paragraph-Size);
|
||||||
letter-spacing: var(--typography-Body-Regular-letterSpacing);
|
line-height: 1.5;
|
||||||
|
letter-spacing: var(--Body-Paragraph-Letter-spacing);
|
||||||
padding: var(--Space-x1);
|
padding: var(--Space-x1);
|
||||||
border-radius: var(--Corner-Radius-md);
|
border-radius: var(--Corner-Radius-md);
|
||||||
gap: var(--Space-x1);
|
gap: var(--Space-x1);
|
||||||
@@ -97,11 +82,12 @@
|
|||||||
.shortcut {
|
.shortcut {
|
||||||
display: grid;
|
display: grid;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-family: var(--typography-Body-Regular-fontFamily);
|
font-family:
|
||||||
font-size: var(--typography-Body-Regular-fontSize);
|
var(--Body-Paragraph-Font-family), var(--Body-Paragraph-Font-fallback);
|
||||||
font-weight: var(--typography-Body-Regular-fontWeight);
|
font-size: var(--Body-Paragraph-Size);
|
||||||
letter-spacing: var(--typography-Body-Regular-letterSpacing);
|
font-weight: var(--Body-Paragraph-Font-weight);
|
||||||
line-height: var(--typography-Body-Regular-lineHeight);
|
letter-spacing: var(--Body-Paragraph-Letter-spacing);
|
||||||
|
line-height: 1.5;
|
||||||
gap: var(--Space-x2);
|
gap: var(--Space-x2);
|
||||||
grid-template-columns: 1fr auto;
|
grid-template-columns: 1fr auto;
|
||||||
padding: var(--Space-x2) var(--Space-x3);
|
padding: var(--Space-x2) var(--Space-x3);
|
||||||
@@ -133,22 +119,13 @@
|
|||||||
line-height: 140%;
|
line-height: 140%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tiny should be removed, it's not a variant of the Link*/
|
|
||||||
.tiny {
|
|
||||||
font-family: var(--typography-Footnote-Regular-fontFamily);
|
|
||||||
font-size: var(--typography-Footnote-Regular-fontSize);
|
|
||||||
font-weight: var(--typography-Footnote-Regular-fontWeight);
|
|
||||||
letter-spacing: var(--typography-Footnote-Regular-letterSpacing);
|
|
||||||
line-height: var(--typography-Footnote-Regular-lineHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bold {
|
.bold {
|
||||||
font-family: var(--typography-Body-Bold-fontFamily);
|
font-family:
|
||||||
font-size: var(--typography-Body-Bold-fontSize);
|
var(--Body-Paragraph-Font-family), var(--Body-Paragraph-Font-fallback);
|
||||||
font-weight: 500
|
font-size: var(--Body-Paragraph-Size);
|
||||||
/* Should be fixed when variables starts working: var(--typography-Body-Bold-fontWeight) */;
|
font-weight: var(--Body-Paragraph-Font-weight-2);
|
||||||
letter-spacing: var(--typography-Body-Bold-letterSpacing);
|
letter-spacing: var(--Body-Paragraph-Letter-spacing);
|
||||||
line-height: var(--typography-Body-Bold-lineHeight);
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu {
|
.menu {
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ export const linkVariants = cva(styles.link, {
|
|||||||
size: {
|
size: {
|
||||||
small: styles.small,
|
small: styles.small,
|
||||||
large: styles.large,
|
large: styles.large,
|
||||||
tiny: styles.tiny,
|
|
||||||
none: "",
|
none: "",
|
||||||
},
|
},
|
||||||
textDecoration: {
|
textDecoration: {
|
||||||
@@ -29,7 +28,6 @@ export const linkVariants = cva(styles.link, {
|
|||||||
},
|
},
|
||||||
variant: {
|
variant: {
|
||||||
icon: styles.icon,
|
icon: styles.icon,
|
||||||
breadcrumb: styles.breadcrumb,
|
|
||||||
myPageMobileDropdown: styles.myPageMobileDropdown,
|
myPageMobileDropdown: styles.myPageMobileDropdown,
|
||||||
navigation: styles.navigation,
|
navigation: styles.navigation,
|
||||||
menu: styles.menu,
|
menu: styles.menu,
|
||||||
|
|||||||
@@ -1,131 +1,3 @@
|
|||||||
:root {
|
|
||||||
--typography-Body-Bold-fontFamily: "fira sans";
|
|
||||||
--typography-Body-Bold-fontSize: 16px;
|
|
||||||
--typography-Body-Bold-fontWeight: "medium";
|
|
||||||
--typography-Body-Bold-letterSpacing: 1.2000000476837158%;
|
|
||||||
--typography-Body-Bold-lineHeight: 150%;
|
|
||||||
--typography-Body-Bold-textDecoration: "none";
|
|
||||||
--typography-Body-Regular-fontFamily: "fira sans";
|
|
||||||
--typography-Body-Regular-fontSize: 16px;
|
|
||||||
--typography-Body-Regular-fontWeight: "regular";
|
|
||||||
--typography-Body-Regular-letterSpacing: 1.2000000476837158%;
|
|
||||||
--typography-Body-Regular-lineHeight: 150%;
|
|
||||||
--typography-Body-Regular-textDecoration: "none";
|
|
||||||
--typography-Body-Underline-fontFamily: "fira sans";
|
|
||||||
--typography-Body-Underline-fontSize: 16px;
|
|
||||||
--typography-Body-Underline-letterSpacing: 1.2000000476837158%;
|
|
||||||
--typography-Body-Underline-lineHeight: 150%;
|
|
||||||
--typography-Body-Underline-textDecoration: "underline";
|
|
||||||
--typography-Caption-Bold-fontFamily: "fira sans";
|
|
||||||
--typography-Caption-Bold-fontSize: 14px;
|
|
||||||
--typography-Caption-Bold-fontWeight: "medium";
|
|
||||||
--typography-Caption-Bold-letterSpacing: 1.399999976158142%;
|
|
||||||
--typography-Caption-Bold-lineHeight: 139.9999976158142%;
|
|
||||||
--typography-Caption-Bold-textDecoration: "none";
|
|
||||||
--typography-Caption-Labels-fontFamily: "brandon text";
|
|
||||||
--typography-Caption-Labels-fontSize: 14px;
|
|
||||||
--typography-Caption-Labels-fontWeight: "bold";
|
|
||||||
--typography-Caption-Labels-letterSpacing: 1.399999976158142%;
|
|
||||||
--typography-Caption-Labels-lineHeight: 150%;
|
|
||||||
--typography-Caption-Labels-textDecoration: "none";
|
|
||||||
--typography-Caption-Regular-fontFamily: "fira sans";
|
|
||||||
--typography-Caption-Regular-fontSize: 14px;
|
|
||||||
--typography-Caption-Regular-fontWeight: "regular";
|
|
||||||
--typography-Caption-Regular-letterSpacing: 1.399999976158142%;
|
|
||||||
--typography-Caption-Regular-lineHeight: 139.9999976158142%;
|
|
||||||
--typography-Caption-Regular-textDecoration: "none";
|
|
||||||
--typography-Caption-Underline-fontFamily: "fira sans";
|
|
||||||
--typography-Caption-Underline-fontSize: 14px;
|
|
||||||
--typography-Caption-Underline-fontWeight: "medium";
|
|
||||||
--typography-Caption-Underline-letterSpacing: 1.399999976158142%;
|
|
||||||
--typography-Caption-Underline-lineHeight: 139.9999976158142%;
|
|
||||||
--typography-Caption-Underline-textDecoration: "underline";
|
|
||||||
--typography-Footnote-Bold-fontFamily: "fira sans";
|
|
||||||
--typography-Footnote-Bold-fontSize: 12px;
|
|
||||||
--typography-Footnote-Bold-fontWeight: "medium";
|
|
||||||
--typography-Footnote-Bold-letterSpacing: 1.399999976158142%;
|
|
||||||
--typography-Footnote-Bold-lineHeight: 150%;
|
|
||||||
--typography-Footnote-Bold-textDecoration: "none";
|
|
||||||
--typography-Footnote-Labels-fontFamily: "brandon text";
|
|
||||||
--typography-Footnote-Labels-fontSize: 12px;
|
|
||||||
--typography-Footnote-Labels-fontWeight: "bold";
|
|
||||||
--typography-Footnote-Labels-letterSpacing: 1.399999976158142%;
|
|
||||||
--typography-Footnote-Labels-lineHeight: 150%;
|
|
||||||
--typography-Footnote-Labels-textDecoration: "none";
|
|
||||||
--typography-Footnote-Regular-fontFamily: "fira sans";
|
|
||||||
--typography-Footnote-Regular-fontSize: 12px;
|
|
||||||
--typography-Footnote-Regular-fontWeight: "regular";
|
|
||||||
--typography-Footnote-Regular-letterSpacing: 1.399999976158142%;
|
|
||||||
--typography-Footnote-Regular-lineHeight: 150%;
|
|
||||||
--typography-Footnote-Regular-textDecoration: "none";
|
|
||||||
--typography-Preamble-Desktop-fontSize: 20px;
|
|
||||||
--typography-Preamble-fontFamily: "fira sans";
|
|
||||||
--typography-Preamble-fontWeight: "regular";
|
|
||||||
--typography-Preamble-letterSpacing: 1%;
|
|
||||||
--typography-Preamble-lineHeight: 139.9999976158142%;
|
|
||||||
--typography-Preamble-Mobile-fontSize: 18px;
|
|
||||||
--typography-Preamble-textDecoration: "none";
|
|
||||||
--typography-Script-1-Desktop-fontSize: 32px;
|
|
||||||
--typography-Script-1-fontFamily: "biro script plus";
|
|
||||||
--typography-Script-1-fontWeight: "regular";
|
|
||||||
--typography-Script-1-letterSpacing: 2%;
|
|
||||||
--typography-Script-1-lineHeight: 110.00000238418579%;
|
|
||||||
--typography-Script-1-Mobile-fontSize: 24px;
|
|
||||||
--typography-Script-2-Desktop-fontSize: 24px;
|
|
||||||
--typography-Script-2-fontWeight: "regular";
|
|
||||||
--typography-Script-2-letterSpacing: 2%;
|
|
||||||
--typography-Script-2-lineHeight: 110.00000238418579%;
|
|
||||||
--typography-Script-2-Mobile-fontSize: 20px;
|
|
||||||
--typography-Subtitle-1-Desktop-fontSize: 24px;
|
|
||||||
--typography-Subtitle-1-fontFamily: "fira sans";
|
|
||||||
--typography-Subtitle-1-letterSpacing: 1%;
|
|
||||||
--typography-Subtitle-1-lineHeight: 120.00000476837158%;
|
|
||||||
--typography-Subtitle-1-Mobile-fontSize: 20px;
|
|
||||||
--typography-Subtitle-2-Desktop-fontSize: 20px;
|
|
||||||
--typography-Subtitle-2-fontFamily: "fira sans";
|
|
||||||
--typography-Subtitle-2-fontSize: 20px;
|
|
||||||
--typography-Subtitle-2-fontWeight: "medium";
|
|
||||||
--typography-Subtitle-2-letterSpacing: 1%;
|
|
||||||
--typography-Subtitle-2-lineHeight: 120.00000476837158%;
|
|
||||||
--typography-Subtitle-2-Mobile-fontSize: 18px;
|
|
||||||
--typography-Title-1-Desktop-fontSize: 64px;
|
|
||||||
--typography-Title-1-fontFamily: "brandon text";
|
|
||||||
--typography-Title-1-fontSize: 64px;
|
|
||||||
--typography-Title-1-fontWeight: "black";
|
|
||||||
--typography-Title-1-letterSpacing: 0.25%;
|
|
||||||
--typography-Title-1-lineHeight: 110.00000238418579%;
|
|
||||||
--typography-Title-1-Mobile-fontSize: 48px;
|
|
||||||
--typography-Title-1-textDecoration: "none";
|
|
||||||
--typography-Title-2-Desktop-fontSize: 48px;
|
|
||||||
--typography-Title-2-fontFamily: "brandon text";
|
|
||||||
--typography-Title-2-fontWeight: "black";
|
|
||||||
--typography-Title-2-letterSpacing: 0.25%;
|
|
||||||
--typography-Title-2-lineHeight: 110.00000238418579%;
|
|
||||||
--typography-Title-2-Mobile-fontSize: 36px;
|
|
||||||
--typography-Title-2-textDecoration: "none";
|
|
||||||
--typography-Title-3-Desktop-fontSize: 36px;
|
|
||||||
--typography-Title-3-fontFamily: "brandon text";
|
|
||||||
--typography-Title-3-fontSize: 36px;
|
|
||||||
--typography-Title-3-fontWeight: "black";
|
|
||||||
--typography-Title-3-letterSpacing: 0.25%;
|
|
||||||
--typography-Title-3-lineHeight: 110.00000238418579%;
|
|
||||||
--typography-Title-3-Mobile-fontSize: 30px;
|
|
||||||
--typography-Title-3-textDecoration: "none";
|
|
||||||
--typography-Title-4-Desktop-fontSize: 28px;
|
|
||||||
--typography-Title-4-fontFamily: "brandon text";
|
|
||||||
--typography-Title-4-fontWeight: "bold";
|
|
||||||
--typography-Title-4-letterSpacing: 0.25%;
|
|
||||||
--typography-Title-4-lineHeight: 110.00000238418579%;
|
|
||||||
--typography-Title-4-Mobile-fontSize: 24px;
|
|
||||||
--typography-Title-4-textDecoration: "none";
|
|
||||||
--typography-Title-5-Desktop-fontSize: 24px;
|
|
||||||
--typography-Title-5-fontFamily: "brandon text";
|
|
||||||
--typography-Title-5-fontWeight: "black";
|
|
||||||
--typography-Title-5-letterSpacing: 0.25%;
|
|
||||||
--typography-Title-5-lineHeight: 110.00000238418579%;
|
|
||||||
--typography-Title-5-Mobile-fontSize: 20px;
|
|
||||||
--typography-Title-5-textDecoration: "none";
|
|
||||||
}
|
|
||||||
:root {
|
:root {
|
||||||
--Base-Border-Hover: var(--Scandic-Peach-80);
|
--Base-Border-Hover: var(--Scandic-Peach-80);
|
||||||
--Base-Border-Inverted: var(--UI-Opacity-White-100);
|
--Base-Border-Inverted: var(--UI-Opacity-White-100);
|
||||||
|
|||||||
@@ -26,7 +26,6 @@
|
|||||||
"./Divider": "./lib/components/Divider/index.tsx",
|
"./Divider": "./lib/components/Divider/index.tsx",
|
||||||
"./FacilityToIcon": "./lib/components/FacilityToIcon/index.tsx",
|
"./FacilityToIcon": "./lib/components/FacilityToIcon/index.tsx",
|
||||||
"./FakeButton": "./lib/components/FakeButton/index.tsx",
|
"./FakeButton": "./lib/components/FakeButton/index.tsx",
|
||||||
"./Footnote": "./lib/components/Footnote/index.tsx",
|
|
||||||
"./Form/Checkbox": "./lib/components/Form/Checkbox/index.tsx",
|
"./Form/Checkbox": "./lib/components/Form/Checkbox/index.tsx",
|
||||||
"./Form/Country": "./lib/components/Form/Country/index.tsx",
|
"./Form/Country": "./lib/components/Form/Country/index.tsx",
|
||||||
"./Form/Date": "./lib/components/Form/Date/index.tsx",
|
"./Form/Date": "./lib/components/Form/Date/index.tsx",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { gql } from "graphql-tag"
|
import { gql } from "graphql-tag"
|
||||||
|
|
||||||
import { Alert } from "../Alert.graphql"
|
|
||||||
import { ImageContainer } from "../ImageContainer.graphql"
|
import { ImageContainer } from "../ImageContainer.graphql"
|
||||||
import { AccountPageLink } from "../PageLink/AccountPageLink.graphql"
|
import { AccountPageLink } from "../PageLink/AccountPageLink.graphql"
|
||||||
import { CampaignOverviewPageLink } from "../PageLink/CampaignOverviewPageLink.graphql"
|
import { CampaignOverviewPageLink } from "../PageLink/CampaignOverviewPageLink.graphql"
|
||||||
@@ -25,7 +24,6 @@ export const Content_ContentPage = gql`
|
|||||||
node {
|
node {
|
||||||
__typename
|
__typename
|
||||||
...SysAsset
|
...SysAsset
|
||||||
...Alert
|
|
||||||
...ImageContainer
|
...ImageContainer
|
||||||
...AccountPageLink
|
...AccountPageLink
|
||||||
...CampaignOverviewPageLink
|
...CampaignOverviewPageLink
|
||||||
@@ -47,7 +45,6 @@ export const Content_ContentPage = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
${SysAsset}
|
${SysAsset}
|
||||||
${Alert}
|
|
||||||
${ImageContainer}
|
${ImageContainer}
|
||||||
${AccountPageLink}
|
${AccountPageLink}
|
||||||
${CampaignOverviewPageLink}
|
${CampaignOverviewPageLink}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { router } from "../.."
|
import { router } from "../.."
|
||||||
|
import { findBookingForCurrentUserRoute } from "./query/findBookingForCurrentUserRoute"
|
||||||
import { findBookingRoute } from "./query/findBookingRoute"
|
import { findBookingRoute } from "./query/findBookingRoute"
|
||||||
import { getBookingRoute } from "./query/getBookingRoute"
|
import { getBookingRoute } from "./query/getBookingRoute"
|
||||||
import { getBookingStatusRoute } from "./query/getBookingStatusRoute"
|
import { getBookingStatusRoute } from "./query/getBookingStatusRoute"
|
||||||
@@ -7,6 +8,7 @@ import { getLinkedReservationsRoute } from "./query/getLinkedReservationsRoute"
|
|||||||
export const bookingQueryRouter = router({
|
export const bookingQueryRouter = router({
|
||||||
get: getBookingRoute,
|
get: getBookingRoute,
|
||||||
findBooking: findBookingRoute,
|
findBooking: findBookingRoute,
|
||||||
|
findBookingForCurrentUser: findBookingForCurrentUserRoute,
|
||||||
linkedReservations: getLinkedReservationsRoute,
|
linkedReservations: getLinkedReservationsRoute,
|
||||||
status: getBookingStatusRoute,
|
status: getBookingStatusRoute,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { createCounter } from "@scandic-hotels/common/telemetry"
|
||||||
|
|
||||||
|
import { notFoundError } from "../../../errors"
|
||||||
|
import { safeProtectedServiceProcedure } from "../../../procedures"
|
||||||
|
import { findBooking } from "../../../services/booking/findBooking"
|
||||||
|
import { getHotel } from "../../hotels/services/getHotel"
|
||||||
|
import { findBookingInput } from "../input"
|
||||||
|
|
||||||
|
export const findBookingForCurrentUserRoute = safeProtectedServiceProcedure
|
||||||
|
.input(
|
||||||
|
findBookingInput.omit({ lastName: true, firstName: true, email: true })
|
||||||
|
)
|
||||||
|
.query(async function ({ ctx, input }) {
|
||||||
|
const lang = input.lang ?? ctx.lang
|
||||||
|
const { confirmationNumber } = input
|
||||||
|
const user = await ctx.getScandicUser()
|
||||||
|
const token = await ctx.getScandicUserToken()
|
||||||
|
|
||||||
|
const findBookingCounter = createCounter(
|
||||||
|
"trpc.booking.findBookingForCurrentUser"
|
||||||
|
)
|
||||||
|
const metricsFindBooking = findBookingCounter.init({
|
||||||
|
confirmationNumber,
|
||||||
|
})
|
||||||
|
metricsFindBooking.start()
|
||||||
|
|
||||||
|
if (!user || !token) {
|
||||||
|
metricsFindBooking.dataError(
|
||||||
|
`Fail to find user when finding booking for ${confirmationNumber}`,
|
||||||
|
{ confirmationNumber }
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const booking = await findBooking(
|
||||||
|
{
|
||||||
|
confirmationNumber,
|
||||||
|
lang,
|
||||||
|
lastName: user.lastName,
|
||||||
|
firstName: user.firstName,
|
||||||
|
email: user.email,
|
||||||
|
},
|
||||||
|
token
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!booking) {
|
||||||
|
metricsFindBooking.dataError(
|
||||||
|
`Fail to find booking data for ${confirmationNumber}`,
|
||||||
|
{ confirmationNumber }
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const hotelData = await getHotel(
|
||||||
|
{
|
||||||
|
hotelId: booking.hotelId,
|
||||||
|
isCardOnlyPayment: false,
|
||||||
|
language: lang,
|
||||||
|
},
|
||||||
|
ctx.serviceToken
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!hotelData) {
|
||||||
|
metricsFindBooking.dataError(
|
||||||
|
`Failed to find hotel data for ${booking.hotelId}`,
|
||||||
|
{
|
||||||
|
hotelId: booking.hotelId,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
throw notFoundError({
|
||||||
|
message: "Hotel data not found",
|
||||||
|
errorDetails: { hotelId: booking.hotelId },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
metricsFindBooking.success()
|
||||||
|
|
||||||
|
return {
|
||||||
|
hotel: {
|
||||||
|
name: hotelData.hotel.name,
|
||||||
|
cityName: hotelData.hotel.cityName,
|
||||||
|
},
|
||||||
|
booking,
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -46,7 +46,7 @@ const getContactConfig = cache(async (lang: Lang) => {
|
|||||||
GetContactConfig,
|
GetContactConfig,
|
||||||
variables,
|
variables,
|
||||||
{
|
{
|
||||||
key: `${lang}:contact`,
|
key: `${lang}:contact_config`,
|
||||||
ttl: "max",
|
ttl: "max",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
import { BlocksEnums } from "../../../../types/blocksEnum"
|
import { BlocksEnums } from "../../../../types/blocksEnum"
|
||||||
import { ContentEnum } from "../../../../types/content"
|
|
||||||
import { alertSchema, transformAlertSchema } from "../alert"
|
|
||||||
import { rawLinkUnionSchema, transformPageLink } from "../pageLinks"
|
import { rawLinkUnionSchema, transformPageLink } from "../pageLinks"
|
||||||
import { imageContainerSchema } from "./imageContainer"
|
import { imageContainerSchema } from "./imageContainer"
|
||||||
import { sysAssetSchema } from "./sysAsset"
|
import { sysAssetSchema } from "./sysAsset"
|
||||||
@@ -24,11 +22,7 @@ export const contentSchema = z.object({
|
|||||||
.discriminatedUnion("__typename", [
|
.discriminatedUnion("__typename", [
|
||||||
imageContainerSchema,
|
imageContainerSchema,
|
||||||
sysAssetSchema,
|
sysAssetSchema,
|
||||||
alertSchema.merge(
|
|
||||||
z.object({
|
|
||||||
__typename: z.literal(ContentEnum.blocks.Alert),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
...rawLinkUnionSchema.options,
|
...rawLinkUnionSchema.options,
|
||||||
])
|
])
|
||||||
.transform((data) => {
|
.transform((data) => {
|
||||||
@@ -36,12 +30,6 @@ export const contentSchema = z.object({
|
|||||||
if (link) {
|
if (link) {
|
||||||
return link
|
return link
|
||||||
}
|
}
|
||||||
if (data.__typename === ContentEnum.blocks.Alert) {
|
|
||||||
return {
|
|
||||||
__typename: data.__typename,
|
|
||||||
...transformAlertSchema(data),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return data
|
return data
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import type { FilterType, HotelFilter } from "./output"
|
|||||||
|
|
||||||
export async function getHotelFilters(lang: Lang) {
|
export async function getHotelFilters(lang: Lang) {
|
||||||
const cacheClient = await getCacheClient()
|
const cacheClient = await getCacheClient()
|
||||||
const cacheKey = `${lang}:getHotelFilters`
|
const cacheKey = `${lang}:hotel_filter:outer`
|
||||||
|
|
||||||
return await cacheClient.cacheOrGet(
|
return await cacheClient.cacheOrGet(
|
||||||
cacheKey,
|
cacheKey,
|
||||||
@@ -27,7 +27,7 @@ export async function getHotelFilters(lang: Lang) {
|
|||||||
const response = await request<unknown>(
|
const response = await request<unknown>(
|
||||||
GetHotelFilters,
|
GetHotelFilters,
|
||||||
{ locale: lang },
|
{ locale: lang },
|
||||||
{ key: `${lang}:hotel_filters`, ttl: "1d" }
|
{ key: `${lang}:hotel_filter`, ttl: "1d" }
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!response.data) {
|
if (!response.data) {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import z from "zod"
|
||||||
|
|
||||||
import { signupVerify } from "@scandic-hotels/common/constants/routes/signup"
|
import { signupVerify } from "@scandic-hotels/common/constants/routes/signup"
|
||||||
import { createLogger } from "@scandic-hotels/common/logger/createLogger"
|
import { createLogger } from "@scandic-hotels/common/logger/createLogger"
|
||||||
import { createCounter } from "@scandic-hotels/common/telemetry"
|
import { createCounter } from "@scandic-hotels/common/telemetry"
|
||||||
@@ -318,4 +320,33 @@ export const userMutationRouter = router({
|
|||||||
return true
|
return true
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
claimPoints: protectedProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
from: z.string(),
|
||||||
|
to: z.string(),
|
||||||
|
city: z.string(),
|
||||||
|
hotel: z.string(),
|
||||||
|
firstName: z.string(),
|
||||||
|
lastName: z.string(),
|
||||||
|
email: z.string().email(),
|
||||||
|
phone: z.string(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.mutation(async function ({ input, ctx }) {
|
||||||
|
userMutationLogger.info("api.user.claimPoints start")
|
||||||
|
const user = await ctx.getScandicUser()
|
||||||
|
if (!user) {
|
||||||
|
throw "error"
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log("Claiming points", input, user.membershipNumber)
|
||||||
|
|
||||||
|
// TODO Waiting for API endpoint, simulating delay
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 2_000))
|
||||||
|
|
||||||
|
userMutationLogger.info("api.user.claimPoints success")
|
||||||
|
return true
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ const ICONS = [
|
|||||||
"download",
|
"download",
|
||||||
"dresser",
|
"dresser",
|
||||||
"edit_calendar",
|
"edit_calendar",
|
||||||
|
"edit_document",
|
||||||
"edit_square",
|
"edit_square",
|
||||||
"edit",
|
"edit",
|
||||||
"electric_bike",
|
"electric_bike",
|
||||||
|
|||||||
Reference in New Issue
Block a user