Merged in feat/SW-3477-hide-voucher-booking-code-sas- (pull request #2836)
feat(SW-3477) Updated booking widget for SAS white label Approved-by: Anton Gunnarsson
This commit is contained in:
@@ -15,6 +15,7 @@ import { bookingTermsAndConditionsRoutes } from "@scandic-hotels/common/constant
|
|||||||
import { customerService } from "@scandic-hotels/common/constants/routes/customerService"
|
import { customerService } from "@scandic-hotels/common/constants/routes/customerService"
|
||||||
import { myStay } from "@scandic-hotels/common/constants/routes/myStay"
|
import { myStay } from "@scandic-hotels/common/constants/routes/myStay"
|
||||||
import { privacyPolicyRoutes } from "@scandic-hotels/common/constants/routes/privacyPolicyRoutes"
|
import { privacyPolicyRoutes } from "@scandic-hotels/common/constants/routes/privacyPolicyRoutes"
|
||||||
|
import { ScandicPartnersEnum } from "@scandic-hotels/common/constants/scandicPartners"
|
||||||
import { ToastHandler } from "@scandic-hotels/design-system/ToastHandler"
|
import { ToastHandler } from "@scandic-hotels/design-system/ToastHandler"
|
||||||
|
|
||||||
import AdobeSDKScript from "@/components/AdobeSDKScript"
|
import AdobeSDKScript from "@/components/AdobeSDKScript"
|
||||||
@@ -61,6 +62,7 @@ export default async function RootLayout(props: RootLayoutProps) {
|
|||||||
|
|
||||||
const bookingFlowConfig: BookingFlowConfig = {
|
const bookingFlowConfig: BookingFlowConfig = {
|
||||||
bookingCodeEnabled: false,
|
bookingCodeEnabled: false,
|
||||||
|
partner: ScandicPartnersEnum.sas,
|
||||||
routes: {
|
routes: {
|
||||||
myStay: routeToScandicWeb(myStay),
|
myStay: routeToScandicWeb(myStay),
|
||||||
bookingTermsAndConditions: routeToScandicWeb(
|
bookingTermsAndConditions: routeToScandicWeb(
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import { cache } from "react"
|
|||||||
import { BookingFlowConfigContextProvider } from "./bookingFlowConfigContext"
|
import { BookingFlowConfigContextProvider } from "./bookingFlowConfigContext"
|
||||||
|
|
||||||
import type { LangRoute } from "@scandic-hotels/common/constants/routes/langRoute"
|
import type { LangRoute } from "@scandic-hotels/common/constants/routes/langRoute"
|
||||||
|
import type { ScandicPartnersEnum } from "@scandic-hotels/common/constants/scandicPartners"
|
||||||
|
|
||||||
export type BookingFlowConfig = {
|
export type BookingFlowConfig = {
|
||||||
bookingCodeEnabled: boolean
|
bookingCodeEnabled: boolean
|
||||||
|
partner?: ScandicPartnersEnum
|
||||||
routes: {
|
routes: {
|
||||||
myStay: LangRoute
|
myStay: LangRoute
|
||||||
bookingTermsAndConditions: LangRoute
|
bookingTermsAndConditions: LangRoute
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
import { createContext, useContext } from "react"
|
import { createContext, useContext } from "react"
|
||||||
|
|
||||||
|
import type { ScandicPartnersEnum } from "@scandic-hotels/common/constants/scandicPartners"
|
||||||
|
|
||||||
import type { BookingFlowConfig } from "./bookingFlowConfig"
|
import type { BookingFlowConfig } from "./bookingFlowConfig"
|
||||||
|
|
||||||
type BookingFlowConfigContextData = BookingFlowConfig
|
type BookingFlowConfigContextData = BookingFlowConfig
|
||||||
@@ -10,6 +12,18 @@ const BookingFlowConfigContext = createContext<
|
|||||||
BookingFlowConfigContextData | undefined
|
BookingFlowConfigContextData | undefined
|
||||||
>(undefined)
|
>(undefined)
|
||||||
|
|
||||||
|
export const useIsPartner = (partner: ScandicPartnersEnum) => {
|
||||||
|
const context = useContext(BookingFlowConfigContext)
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
throw new Error(
|
||||||
|
"useBookingFlowConfig must be used within a BookingFlowConfigContextProvider. Did you forget to use BookingFlowConfig in the consuming app?"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return context.partner === partner
|
||||||
|
}
|
||||||
|
|
||||||
export const useBookingFlowConfig = (): BookingFlowConfigContextData => {
|
export const useBookingFlowConfig = (): BookingFlowConfigContextData => {
|
||||||
const context = useContext(BookingFlowConfigContext)
|
const context = useContext(BookingFlowConfigContext)
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { ScandicPartnersEnum } from "@scandic-hotels/common/constants/scandicPartners"
|
||||||
import { logger } from "@scandic-hotels/common/logger"
|
import { logger } from "@scandic-hotels/common/logger"
|
||||||
import { phoneErrors } from "@scandic-hotels/common/utils/zod/phoneValidator"
|
import { phoneErrors } from "@scandic-hotels/common/utils/zod/phoneValidator"
|
||||||
|
|
||||||
@@ -9,7 +10,11 @@ import {
|
|||||||
|
|
||||||
import type { IntlShape } from "react-intl"
|
import type { IntlShape } from "react-intl"
|
||||||
|
|
||||||
export function getErrorMessage(intl: IntlShape, errorCode?: string) {
|
export function getErrorMessage(
|
||||||
|
intl: IntlShape,
|
||||||
|
errorCode?: string,
|
||||||
|
partner?: ScandicPartnersEnum
|
||||||
|
) {
|
||||||
switch (errorCode) {
|
switch (errorCode) {
|
||||||
case bookingWidgetErrors.BOOKING_CODE_INVALID:
|
case bookingWidgetErrors.BOOKING_CODE_INVALID:
|
||||||
return intl.formatMessage({
|
return intl.formatMessage({
|
||||||
@@ -42,10 +47,15 @@ export function getErrorMessage(intl: IntlShape, errorCode?: string) {
|
|||||||
"Multi-room booking is not available with this booking code.",
|
"Multi-room booking is not available with this booking code.",
|
||||||
})
|
})
|
||||||
case bookingWidgetErrors.MULTIROOM_REWARD_NIGHT_UNAVAILABLE:
|
case bookingWidgetErrors.MULTIROOM_REWARD_NIGHT_UNAVAILABLE:
|
||||||
return intl.formatMessage({
|
return partner === ScandicPartnersEnum.sas
|
||||||
defaultMessage:
|
? intl.formatMessage({
|
||||||
"Multi-room booking is not available with reward night.",
|
defaultMessage:
|
||||||
})
|
"Multi-room booking is not available with euro bonus points.",
|
||||||
|
})
|
||||||
|
: intl.formatMessage({
|
||||||
|
defaultMessage:
|
||||||
|
"Multi-room booking is not available with reward night.",
|
||||||
|
})
|
||||||
case bookingWidgetErrors.CODE_VOUCHER_REWARD_NIGHT_UNAVAILABLE:
|
case bookingWidgetErrors.CODE_VOUCHER_REWARD_NIGHT_UNAVAILABLE:
|
||||||
return intl.formatMessage({
|
return intl.formatMessage({
|
||||||
defaultMessage:
|
defaultMessage:
|
||||||
|
|||||||
@@ -19,6 +19,10 @@
|
|||||||
color: var(--Text-Secondary);
|
color: var(--Text-Secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.colorSecondary {
|
||||||
|
color: var(--Text-Secondary);
|
||||||
|
}
|
||||||
|
|
||||||
.errorContainer {
|
.errorContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -46,6 +50,7 @@
|
|||||||
.bookingCodeTooltip {
|
.bookingCodeTooltip {
|
||||||
max-width: 560px;
|
max-width: 560px;
|
||||||
margin-top: var(--Spacing-x2);
|
margin-top: var(--Spacing-x2);
|
||||||
|
color: var(--Text-Secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bookingCodeRememberVisible label {
|
.bookingCodeRememberVisible label {
|
||||||
|
|||||||
@@ -4,15 +4,11 @@ import { type FieldError, useFormContext } from "react-hook-form"
|
|||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
import { useMediaQuery } from "usehooks-ts"
|
import { useMediaQuery } from "usehooks-ts"
|
||||||
|
|
||||||
import Body from "@scandic-hotels/design-system/Body"
|
import { Button } from "@scandic-hotels/design-system/Button"
|
||||||
import Caption from "@scandic-hotels/design-system/Caption"
|
|
||||||
import Checkbox from "@scandic-hotels/design-system/Form/Checkbox"
|
import Checkbox from "@scandic-hotels/design-system/Form/Checkbox"
|
||||||
|
import { IconButton } from "@scandic-hotels/design-system/IconButton"
|
||||||
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 Modal from "@scandic-hotels/design-system/Modal"
|
||||||
import {
|
|
||||||
type ButtonProps,
|
|
||||||
OldDSButton as Button,
|
|
||||||
} from "@scandic-hotels/design-system/OldDSButton"
|
|
||||||
import Switch from "@scandic-hotels/design-system/Switch"
|
import Switch from "@scandic-hotels/design-system/Switch"
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking"
|
import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking"
|
||||||
@@ -20,6 +16,7 @@ import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking"
|
|||||||
import BookingFlowInput from "../../../../BookingFlowInput"
|
import BookingFlowInput from "../../../../BookingFlowInput"
|
||||||
import { getErrorMessage } from "../../../../BookingFlowInput/errors"
|
import { getErrorMessage } from "../../../../BookingFlowInput/errors"
|
||||||
import { Input as BookingWidgetInput } from "../Input"
|
import { Input as BookingWidgetInput } from "../Input"
|
||||||
|
import { RemoveExtraRooms } from "../RemoveExtraRooms/RemoveExtraRooms"
|
||||||
import { isMultiRoomError } from "../utils"
|
import { isMultiRoomError } from "../utils"
|
||||||
|
|
||||||
import styles from "./booking-code.module.css"
|
import styles from "./booking-code.module.css"
|
||||||
@@ -192,13 +189,15 @@ export default function BookingCode() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Switch name="bookingCode.remember" className="mobile-switch">
|
<Switch name="bookingCode.remember" className="mobile-switch">
|
||||||
<Caption asChild>
|
<Typography
|
||||||
|
variant={"Body/Supporting text (caption)/smRegular"}
|
||||||
|
>
|
||||||
<span>
|
<span>
|
||||||
{intl.formatMessage({
|
{intl.formatMessage({
|
||||||
defaultMessage: "Remember code",
|
defaultMessage: "Remember code",
|
||||||
})}
|
})}
|
||||||
</span>
|
</span>
|
||||||
</Caption>
|
</Typography>
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -226,19 +225,19 @@ function CodeRulesModal() {
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
trigger={
|
trigger={
|
||||||
<Button intent="text">
|
<IconButton theme={"Black"} wrapping>
|
||||||
<MaterialIcon
|
<MaterialIcon
|
||||||
icon="info"
|
icon="info"
|
||||||
color="Icon/Interactive/Placeholder"
|
color="Icon/Interactive/Placeholder"
|
||||||
size={20}
|
size={20}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</IconButton>
|
||||||
}
|
}
|
||||||
title={codeVoucher}
|
title={codeVoucher}
|
||||||
>
|
>
|
||||||
<Body color="uiTextHighContrast" className={styles.bookingCodeTooltip}>
|
<Typography variant={"Body/Paragraph/mdRegular"}>
|
||||||
{bookingCodeTooltipText}
|
<p className={styles.bookingCodeTooltip}>{bookingCodeTooltipText}</p>
|
||||||
</Body>
|
</Typography>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -249,20 +248,19 @@ function CodeRemember({ bookingCodeValue, onApplyClick }: CodeRememberProps) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Checkbox name="bookingCode.remember">
|
<Checkbox name="bookingCode.remember">
|
||||||
<Caption asChild>
|
<Typography variant={"Body/Supporting text (caption)/smRegular"}>
|
||||||
<span>
|
<span>
|
||||||
{intl.formatMessage({
|
{intl.formatMessage({
|
||||||
defaultMessage: "Remember code",
|
defaultMessage: "Remember code",
|
||||||
})}
|
})}
|
||||||
</span>
|
</span>
|
||||||
</Caption>
|
</Typography>
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
{bookingCodeValue ? (
|
{bookingCodeValue ? (
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="Small"
|
||||||
className={styles.hideOnMobile}
|
className={styles.hideOnMobile}
|
||||||
intent="tertiary"
|
variant="Tertiary"
|
||||||
theme="base"
|
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onApplyClick}
|
onClick={onApplyClick}
|
||||||
>
|
>
|
||||||
@@ -303,41 +301,13 @@ function BookingCodeError({
|
|||||||
</Typography>
|
</Typography>
|
||||||
{isMultiroomError ? (
|
{isMultiroomError ? (
|
||||||
<div className={styles.removeButton}>
|
<div className={styles.removeButton}>
|
||||||
<RemoveExtraRooms fullWidth />
|
<RemoveExtraRooms />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RemoveExtraRooms({ ...props }: ButtonProps) {
|
|
||||||
const intl = useIntl()
|
|
||||||
const { getValues, setValue, trigger } = useFormContext<BookingWidgetSchema>()
|
|
||||||
function removeExtraRooms() {
|
|
||||||
// Timeout to delay the event scheduling issue with touch events on mobile
|
|
||||||
window.setTimeout(() => {
|
|
||||||
const rooms = getValues("rooms")[0]
|
|
||||||
setValue("rooms", [rooms], { shouldValidate: true, shouldDirty: true })
|
|
||||||
trigger("bookingCode.value")
|
|
||||||
trigger(SEARCH_TYPE_REDEMPTION)
|
|
||||||
}, 300)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
onClick={removeExtraRooms}
|
|
||||||
size="small"
|
|
||||||
intent="secondary"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{intl.formatMessage({
|
|
||||||
defaultMessage: "Remove extra rooms",
|
|
||||||
})}
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function TabletBookingCode({
|
function TabletBookingCode({
|
||||||
bookingCode,
|
bookingCode,
|
||||||
updateValue,
|
updateValue,
|
||||||
@@ -381,7 +351,7 @@ function TabletBookingCode({
|
|||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<DialogTrigger isOpen={isOpen} onOpenChange={toggleModal}>
|
<DialogTrigger isOpen={isOpen} onOpenChange={toggleModal}>
|
||||||
<Button type="button" intent="text">
|
<Button type="button" variant={"Text"}>
|
||||||
{/* For some reason Checkbox click triggers twice modal state change, which returns the modal back to old state. So we are using overlay as trigger for modal */}
|
{/* For some reason Checkbox click triggers twice modal state change, which returns the modal back to old state. So we are using overlay as trigger for modal */}
|
||||||
<div className={styles.overlayTrigger}></div>
|
<div className={styles.overlayTrigger}></div>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@@ -394,9 +364,9 @@ function TabletBookingCode({
|
|||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Caption color="uiTextMediumContrast" type="bold" asChild>
|
<Typography variant={"Body/Supporting text (caption)/smBold"}>
|
||||||
<span>{codeVoucher}</span>
|
<span className={styles.colorSecondary}>{codeVoucher}</span>
|
||||||
</Caption>
|
</Typography>
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</Button>
|
</Button>
|
||||||
<Popover
|
<Popover
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { useFormContext } from "react-hook-form"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { Button, type ButtonProps } from "@scandic-hotels/design-system/Button"
|
||||||
|
import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking"
|
||||||
|
|
||||||
|
import type { BookingWidgetSchema } from "../../../Client"
|
||||||
|
|
||||||
|
export function RemoveExtraRooms({ ...props }: ButtonProps) {
|
||||||
|
const intl = useIntl()
|
||||||
|
const { getValues, setValue, trigger } = useFormContext<BookingWidgetSchema>()
|
||||||
|
function removeExtraRooms() {
|
||||||
|
// Timeout to delay the event scheduling issue with touch events on mobile
|
||||||
|
window.setTimeout(() => {
|
||||||
|
const rooms = getValues("rooms")[0]
|
||||||
|
setValue("rooms", [rooms], { shouldValidate: true, shouldDirty: true })
|
||||||
|
trigger("bookingCode.value")
|
||||||
|
trigger(SEARCH_TYPE_REDEMPTION)
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
type="button"
|
||||||
|
onClick={removeExtraRooms}
|
||||||
|
size="Small"
|
||||||
|
variant={"Secondary"}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{intl.formatMessage({
|
||||||
|
defaultMessage: "Remove extra rooms",
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -5,16 +5,20 @@ import { useFormContext } from "react-hook-form"
|
|||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
import { useMediaQuery } from "usehooks-ts"
|
import { useMediaQuery } from "usehooks-ts"
|
||||||
|
|
||||||
import Caption from "@scandic-hotels/design-system/Caption"
|
import { ScandicPartnersEnum } from "@scandic-hotels/common/constants/scandicPartners"
|
||||||
import Checkbox from "@scandic-hotels/design-system/Form/Checkbox"
|
import Checkbox from "@scandic-hotels/design-system/Form/Checkbox"
|
||||||
|
import { IconButton } from "@scandic-hotels/design-system/IconButton"
|
||||||
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 Modal from "@scandic-hotels/design-system/Modal"
|
||||||
import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton"
|
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking"
|
import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking"
|
||||||
|
|
||||||
|
import {
|
||||||
|
useBookingFlowConfig,
|
||||||
|
useIsPartner,
|
||||||
|
} from "../../../../../bookingFlowConfig/bookingFlowConfigContext"
|
||||||
import { getErrorMessage } from "../../../../BookingFlowInput/errors"
|
import { getErrorMessage } from "../../../../BookingFlowInput/errors"
|
||||||
import { RemoveExtraRooms } from "../BookingCode"
|
import { RemoveExtraRooms } from "../RemoveExtraRooms/RemoveExtraRooms"
|
||||||
import { isMultiRoomError } from "../utils"
|
import { isMultiRoomError } from "../utils"
|
||||||
|
|
||||||
import styles from "./reward-night.module.css"
|
import styles from "./reward-night.module.css"
|
||||||
@@ -23,6 +27,8 @@ import type { BookingWidgetSchema } from "../../../Client"
|
|||||||
|
|
||||||
export default function RewardNight() {
|
export default function RewardNight() {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
const config = useBookingFlowConfig()
|
||||||
|
const isPartnerSas = useIsPartner(ScandicPartnersEnum.sas)
|
||||||
const {
|
const {
|
||||||
setValue,
|
setValue,
|
||||||
getValues,
|
getValues,
|
||||||
@@ -30,13 +36,22 @@ export default function RewardNight() {
|
|||||||
trigger,
|
trigger,
|
||||||
} = useFormContext<BookingWidgetSchema>()
|
} = useFormContext<BookingWidgetSchema>()
|
||||||
const ref = useRef<HTMLDivElement | null>(null)
|
const ref = useRef<HTMLDivElement | null>(null)
|
||||||
const reward = intl.formatMessage({
|
const reward = isPartnerSas
|
||||||
defaultMessage: "Reward Night",
|
? intl.formatMessage({
|
||||||
})
|
defaultMessage: "EuroBonus Points",
|
||||||
const rewardNightTooltip = intl.formatMessage({
|
})
|
||||||
defaultMessage:
|
: intl.formatMessage({
|
||||||
"To book a reward night, make sure you're logged in to your Scandic Friends account.",
|
defaultMessage: "Reward Night",
|
||||||
})
|
})
|
||||||
|
const rewardNightTooltip = isPartnerSas
|
||||||
|
? intl.formatMessage({
|
||||||
|
defaultMessage:
|
||||||
|
"To book with EuroBonus points, make sure you're logged into your SAS EuroBonus account.",
|
||||||
|
})
|
||||||
|
: intl.formatMessage({
|
||||||
|
defaultMessage:
|
||||||
|
"To book a reward night, make sure you're logged in to your Scandic Friends account.",
|
||||||
|
})
|
||||||
const redemptionErr = errors[SEARCH_TYPE_REDEMPTION]
|
const redemptionErr = errors[SEARCH_TYPE_REDEMPTION]
|
||||||
const isDesktop = useMediaQuery("(min-width: 767px)")
|
const isDesktop = useMediaQuery("(min-width: 767px)")
|
||||||
|
|
||||||
@@ -92,19 +107,22 @@ export default function RewardNight() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className={styles.rewardNightLabel}>
|
<div className={styles.rewardNightLabel}>
|
||||||
<Caption color="uiTextMediumContrast" asChild>
|
<Typography
|
||||||
|
variant={"Body/Supporting text (caption)/smRegular"}
|
||||||
|
className={styles.label}
|
||||||
|
>
|
||||||
<span>{reward}</span>
|
<span>{reward}</span>
|
||||||
</Caption>
|
</Typography>
|
||||||
<Modal
|
<Modal
|
||||||
trigger={
|
trigger={
|
||||||
<Button intent="text">
|
<IconButton theme={"Black"} wrapping>
|
||||||
<MaterialIcon
|
<MaterialIcon
|
||||||
icon="info"
|
icon="info"
|
||||||
size={20}
|
size={20}
|
||||||
color="Icon/Interactive/Placeholder"
|
color="Icon/Interactive/Placeholder"
|
||||||
className={styles.errorIcon}
|
className={styles.errorIcon}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</IconButton>
|
||||||
}
|
}
|
||||||
title={reward}
|
title={reward}
|
||||||
>
|
>
|
||||||
@@ -131,12 +149,12 @@ export default function RewardNight() {
|
|||||||
className={styles.errorIcon}
|
className={styles.errorIcon}
|
||||||
isFilled={!isDesktop}
|
isFilled={!isDesktop}
|
||||||
/>
|
/>
|
||||||
{getErrorMessage(intl, redemptionErr.message)}
|
{getErrorMessage(intl, redemptionErr.message, config.partner)}
|
||||||
</span>
|
</span>
|
||||||
</Typography>
|
</Typography>
|
||||||
{isMultiRoomError(redemptionErr.message) ? (
|
{isMultiRoomError(redemptionErr.message) ? (
|
||||||
<div className={styles.hideOnMobile}>
|
<div className={styles.hideOnMobile}>
|
||||||
<RemoveExtraRooms fullWidth />
|
<RemoveExtraRooms />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,7 +17,9 @@
|
|||||||
.rewardNightLabel {
|
.rewardNightLabel {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--Spacing-x1);
|
color: var(--Text-Secondary);
|
||||||
|
min-width: 160px;
|
||||||
|
gap: var(--Space-x1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.rewardNightTooltip {
|
.rewardNightTooltip {
|
||||||
|
|||||||
@@ -185,4 +185,8 @@
|
|||||||
.input {
|
.input {
|
||||||
gap: var(--Spacing-x2);
|
gap: var(--Spacing-x2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bookingCodeDisabled {
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,10 +13,11 @@ import SkeletonShimmer from "@scandic-hotels/design-system/SkeletonShimmer"
|
|||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking"
|
import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking"
|
||||||
|
|
||||||
|
import { useBookingFlowConfig } from "../../../../bookingFlowConfig/bookingFlowConfigContext"
|
||||||
import useLang from "../../../../hooks/useLang"
|
import useLang from "../../../../hooks/useLang"
|
||||||
import GuestsRoomsPickerForm from "../../../BookingWidget/GuestsRoomsPicker"
|
import GuestsRoomsPickerForm from "../../../BookingWidget/GuestsRoomsPicker"
|
||||||
import DatePicker from "../../DatePicker"
|
import DatePicker from "../../DatePicker"
|
||||||
import { RemoveExtraRooms } from "./BookingCode"
|
import { RemoveExtraRooms } from "./RemoveExtraRooms/RemoveExtraRooms"
|
||||||
import { Search, SearchSkeleton } from "./Search"
|
import { Search, SearchSkeleton } from "./Search"
|
||||||
import { isMultiRoomError } from "./utils"
|
import { isMultiRoomError } from "./utils"
|
||||||
import ValidationError from "./ValidationError"
|
import ValidationError from "./ValidationError"
|
||||||
@@ -40,6 +41,7 @@ export default function FormContent({
|
|||||||
const {
|
const {
|
||||||
formState: { errors, isDirty },
|
formState: { errors, isDirty },
|
||||||
} = useFormContext<BookingWidgetSchema>()
|
} = useFormContext<BookingWidgetSchema>()
|
||||||
|
const { bookingCodeEnabled } = useBookingFlowConfig()
|
||||||
|
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
const pathName = usePathname()
|
const pathName = usePathname()
|
||||||
@@ -109,14 +111,20 @@ export default function FormContent({
|
|||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className={cx(styles.voucherContainer, styles.voucherRow)}>
|
<div
|
||||||
|
className={cx(
|
||||||
|
styles.voucherContainer,
|
||||||
|
styles.voucherRow,
|
||||||
|
bookingCodeEnabled ? null : styles.bookingCodeDisabled
|
||||||
|
)}
|
||||||
|
>
|
||||||
<Voucher />
|
<Voucher />
|
||||||
</div>
|
</div>
|
||||||
<div className={cx(styles.buttonContainer, styles.hideOnTablet)}>
|
<div className={cx(styles.buttonContainer, styles.hideOnTablet)}>
|
||||||
{isMultiRoomError(errors.bookingCode?.value?.message) ||
|
{isMultiRoomError(errors.bookingCode?.value?.message) ||
|
||||||
isMultiRoomError(errors[SEARCH_TYPE_REDEMPTION]?.message) ? (
|
isMultiRoomError(errors[SEARCH_TYPE_REDEMPTION]?.message) ? (
|
||||||
<div className={styles.showOnMobile}>
|
<div className={styles.showOnMobile}>
|
||||||
<RemoveExtraRooms size="medium" fullWidth />
|
<RemoveExtraRooms />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
|||||||
import { trpc } from "@scandic-hotels/trpc/client"
|
import { trpc } from "@scandic-hotels/trpc/client"
|
||||||
import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking"
|
import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking"
|
||||||
|
|
||||||
|
import { useBookingFlowConfig } from "../../bookingFlowConfig/bookingFlowConfigContext"
|
||||||
import useLang from "../../hooks/useLang"
|
import useLang from "../../hooks/useLang"
|
||||||
import {
|
import {
|
||||||
type bookingCodeSchema,
|
type bookingCodeSchema,
|
||||||
@@ -52,6 +53,7 @@ export default function BookingWidgetClient({
|
|||||||
const [originalOverflowY, setOriginalOverflowY] = useState<string | null>(
|
const [originalOverflowY, setOriginalOverflowY] = useState<string | null>(
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
|
const { bookingCodeEnabled } = useBookingFlowConfig()
|
||||||
|
|
||||||
const shouldFetchAutoComplete = !!data.hotelId || !!data.city
|
const shouldFetchAutoComplete = !!data.hotelId || !!data.city
|
||||||
|
|
||||||
@@ -124,7 +126,7 @@ export default function BookingWidgetClient({
|
|||||||
toDate: toDate.format("YYYY-MM-DD"),
|
toDate: toDate.format("YYYY-MM-DD"),
|
||||||
},
|
},
|
||||||
bookingCode: {
|
bookingCode: {
|
||||||
value: selectedBookingCode,
|
value: bookingCodeEnabled ? selectedBookingCode : "",
|
||||||
remember: false,
|
remember: false,
|
||||||
},
|
},
|
||||||
redemption: data.searchType === SEARCH_TYPE_REDEMPTION,
|
redemption: data.searchType === SEARCH_TYPE_REDEMPTION,
|
||||||
|
|||||||
3
packages/common/constants/scandicPartners.ts
Normal file
3
packages/common/constants/scandicPartners.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export enum ScandicPartnersEnum {
|
||||||
|
sas = "sas",
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
"./constants/routes/*": "./constants/routes/*.ts",
|
"./constants/routes/*": "./constants/routes/*.ts",
|
||||||
"./constants/signatureHotels": "./constants/signatureHotels.ts",
|
"./constants/signatureHotels": "./constants/signatureHotels.ts",
|
||||||
"./constants/paymentCallbackStatus": "./constants/paymentCallbackStatus.ts",
|
"./constants/paymentCallbackStatus": "./constants/paymentCallbackStatus.ts",
|
||||||
|
"./constants/scandicPartners": "./constants/scandicPartners.ts",
|
||||||
"./dataCache": "./dataCache/index.ts",
|
"./dataCache": "./dataCache/index.ts",
|
||||||
"./dt": "./dt/dt.ts",
|
"./dt": "./dt/dt.ts",
|
||||||
"./dt/utils/hasOverlappingDates": "./dt/utils/hasOverlappingDates.ts",
|
"./dt/utils/hasOverlappingDates": "./dt/utils/hasOverlappingDates.ts",
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
export { Button } from './Button'
|
export { Button } from './Button'
|
||||||
// eslint-disable-next-line react-refresh/only-export-components
|
// eslint-disable-next-line react-refresh/only-export-components
|
||||||
export { withButton } from './variants'
|
export { withButton } from './variants'
|
||||||
|
|
||||||
|
export { type ButtonProps } from './types'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
color: var(--text-color);
|
color: var(--Text-Default);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ const meta: Meta<typeof IconButton> = {
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
description: `The style variant is only applied on certain variants. The examples below shows the possible combinations of variants and style variants.`,
|
description: `The style variant is only applied on certain variants. The examples below shows the possible combinations of variants and style variants.`,
|
||||||
},
|
},
|
||||||
|
wrapping: {
|
||||||
|
control: 'select',
|
||||||
|
options: Object.keys(config.variants.wrapping),
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ export function IconButton({
|
|||||||
theme,
|
theme,
|
||||||
style,
|
style,
|
||||||
className,
|
className,
|
||||||
|
wrapping,
|
||||||
...props
|
...props
|
||||||
}: IconButtonProps) {
|
}: IconButtonProps) {
|
||||||
const classNames = variants({
|
const classNames = variants({
|
||||||
theme,
|
theme,
|
||||||
style,
|
style,
|
||||||
|
wrapping,
|
||||||
className,
|
className,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -100,3 +100,7 @@
|
|||||||
background-color: var(--Component-Button-Muted-Fill-Disabled-inverted);
|
background-color: var(--Component-Button-Muted-Fill-Disabled-inverted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.no-wrapping {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ export const config = {
|
|||||||
[variantKeys.style.Elevated]: '',
|
[variantKeys.style.Elevated]: '',
|
||||||
[variantKeys.style.Faded]: '',
|
[variantKeys.style.Faded]: '',
|
||||||
},
|
},
|
||||||
|
wrapping: {
|
||||||
|
true: styles['no-wrapping'],
|
||||||
|
false: undefined,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
compoundVariants: [
|
compoundVariants: [
|
||||||
// Primary should only use Normal
|
// Primary should only use Normal
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
color: var(--text-color);
|
color: var(--Text-Default);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
Reference in New Issue
Block a user