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:
@@ -19,6 +19,10 @@
|
||||
color: var(--Text-Secondary);
|
||||
}
|
||||
|
||||
.colorSecondary {
|
||||
color: var(--Text-Secondary);
|
||||
}
|
||||
|
||||
.errorContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -46,6 +50,7 @@
|
||||
.bookingCodeTooltip {
|
||||
max-width: 560px;
|
||||
margin-top: var(--Spacing-x2);
|
||||
color: var(--Text-Secondary);
|
||||
}
|
||||
|
||||
.bookingCodeRememberVisible label {
|
||||
|
||||
@@ -4,15 +4,11 @@ import { type FieldError, useFormContext } from "react-hook-form"
|
||||
import { useIntl } from "react-intl"
|
||||
import { useMediaQuery } from "usehooks-ts"
|
||||
|
||||
import Body from "@scandic-hotels/design-system/Body"
|
||||
import Caption from "@scandic-hotels/design-system/Caption"
|
||||
import { Button } from "@scandic-hotels/design-system/Button"
|
||||
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 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 { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
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 { getErrorMessage } from "../../../../BookingFlowInput/errors"
|
||||
import { Input as BookingWidgetInput } from "../Input"
|
||||
import { RemoveExtraRooms } from "../RemoveExtraRooms/RemoveExtraRooms"
|
||||
import { isMultiRoomError } from "../utils"
|
||||
|
||||
import styles from "./booking-code.module.css"
|
||||
@@ -192,13 +189,15 @@ export default function BookingCode() {
|
||||
}
|
||||
>
|
||||
<Switch name="bookingCode.remember" className="mobile-switch">
|
||||
<Caption asChild>
|
||||
<Typography
|
||||
variant={"Body/Supporting text (caption)/smRegular"}
|
||||
>
|
||||
<span>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Remember code",
|
||||
})}
|
||||
</span>
|
||||
</Caption>
|
||||
</Typography>
|
||||
</Switch>
|
||||
</div>
|
||||
)}
|
||||
@@ -226,19 +225,19 @@ function CodeRulesModal() {
|
||||
return (
|
||||
<Modal
|
||||
trigger={
|
||||
<Button intent="text">
|
||||
<IconButton theme={"Black"} wrapping>
|
||||
<MaterialIcon
|
||||
icon="info"
|
||||
color="Icon/Interactive/Placeholder"
|
||||
size={20}
|
||||
/>
|
||||
</Button>
|
||||
</IconButton>
|
||||
}
|
||||
title={codeVoucher}
|
||||
>
|
||||
<Body color="uiTextHighContrast" className={styles.bookingCodeTooltip}>
|
||||
{bookingCodeTooltipText}
|
||||
</Body>
|
||||
<Typography variant={"Body/Paragraph/mdRegular"}>
|
||||
<p className={styles.bookingCodeTooltip}>{bookingCodeTooltipText}</p>
|
||||
</Typography>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@@ -249,20 +248,19 @@ function CodeRemember({ bookingCodeValue, onApplyClick }: CodeRememberProps) {
|
||||
return (
|
||||
<>
|
||||
<Checkbox name="bookingCode.remember">
|
||||
<Caption asChild>
|
||||
<Typography variant={"Body/Supporting text (caption)/smRegular"}>
|
||||
<span>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Remember code",
|
||||
})}
|
||||
</span>
|
||||
</Caption>
|
||||
</Typography>
|
||||
</Checkbox>
|
||||
{bookingCodeValue ? (
|
||||
<Button
|
||||
size="small"
|
||||
size="Small"
|
||||
className={styles.hideOnMobile}
|
||||
intent="tertiary"
|
||||
theme="base"
|
||||
variant="Tertiary"
|
||||
type="button"
|
||||
onClick={onApplyClick}
|
||||
>
|
||||
@@ -303,41 +301,13 @@ function BookingCodeError({
|
||||
</Typography>
|
||||
{isMultiroomError ? (
|
||||
<div className={styles.removeButton}>
|
||||
<RemoveExtraRooms fullWidth />
|
||||
<RemoveExtraRooms />
|
||||
</div>
|
||||
) : null}
|
||||
</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({
|
||||
bookingCode,
|
||||
updateValue,
|
||||
@@ -381,7 +351,7 @@ function TabletBookingCode({
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<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 */}
|
||||
<div className={styles.overlayTrigger}></div>
|
||||
<Checkbox
|
||||
@@ -394,9 +364,9 @@ function TabletBookingCode({
|
||||
},
|
||||
})}
|
||||
>
|
||||
<Caption color="uiTextMediumContrast" type="bold" asChild>
|
||||
<span>{codeVoucher}</span>
|
||||
</Caption>
|
||||
<Typography variant={"Body/Supporting text (caption)/smBold"}>
|
||||
<span className={styles.colorSecondary}>{codeVoucher}</span>
|
||||
</Typography>
|
||||
</Checkbox>
|
||||
</Button>
|
||||
<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 { 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 { IconButton } from "@scandic-hotels/design-system/IconButton"
|
||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||
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 { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking"
|
||||
|
||||
import {
|
||||
useBookingFlowConfig,
|
||||
useIsPartner,
|
||||
} from "../../../../../bookingFlowConfig/bookingFlowConfigContext"
|
||||
import { getErrorMessage } from "../../../../BookingFlowInput/errors"
|
||||
import { RemoveExtraRooms } from "../BookingCode"
|
||||
import { RemoveExtraRooms } from "../RemoveExtraRooms/RemoveExtraRooms"
|
||||
import { isMultiRoomError } from "../utils"
|
||||
|
||||
import styles from "./reward-night.module.css"
|
||||
@@ -23,6 +27,8 @@ import type { BookingWidgetSchema } from "../../../Client"
|
||||
|
||||
export default function RewardNight() {
|
||||
const intl = useIntl()
|
||||
const config = useBookingFlowConfig()
|
||||
const isPartnerSas = useIsPartner(ScandicPartnersEnum.sas)
|
||||
const {
|
||||
setValue,
|
||||
getValues,
|
||||
@@ -30,13 +36,22 @@ export default function RewardNight() {
|
||||
trigger,
|
||||
} = useFormContext<BookingWidgetSchema>()
|
||||
const ref = useRef<HTMLDivElement | null>(null)
|
||||
const reward = intl.formatMessage({
|
||||
defaultMessage: "Reward Night",
|
||||
})
|
||||
const rewardNightTooltip = intl.formatMessage({
|
||||
defaultMessage:
|
||||
"To book a reward night, make sure you're logged in to your Scandic Friends account.",
|
||||
})
|
||||
const reward = isPartnerSas
|
||||
? intl.formatMessage({
|
||||
defaultMessage: "EuroBonus Points",
|
||||
})
|
||||
: intl.formatMessage({
|
||||
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 isDesktop = useMediaQuery("(min-width: 767px)")
|
||||
|
||||
@@ -92,19 +107,22 @@ export default function RewardNight() {
|
||||
}}
|
||||
>
|
||||
<div className={styles.rewardNightLabel}>
|
||||
<Caption color="uiTextMediumContrast" asChild>
|
||||
<Typography
|
||||
variant={"Body/Supporting text (caption)/smRegular"}
|
||||
className={styles.label}
|
||||
>
|
||||
<span>{reward}</span>
|
||||
</Caption>
|
||||
</Typography>
|
||||
<Modal
|
||||
trigger={
|
||||
<Button intent="text">
|
||||
<IconButton theme={"Black"} wrapping>
|
||||
<MaterialIcon
|
||||
icon="info"
|
||||
size={20}
|
||||
color="Icon/Interactive/Placeholder"
|
||||
className={styles.errorIcon}
|
||||
/>
|
||||
</Button>
|
||||
</IconButton>
|
||||
}
|
||||
title={reward}
|
||||
>
|
||||
@@ -131,12 +149,12 @@ export default function RewardNight() {
|
||||
className={styles.errorIcon}
|
||||
isFilled={!isDesktop}
|
||||
/>
|
||||
{getErrorMessage(intl, redemptionErr.message)}
|
||||
{getErrorMessage(intl, redemptionErr.message, config.partner)}
|
||||
</span>
|
||||
</Typography>
|
||||
{isMultiRoomError(redemptionErr.message) ? (
|
||||
<div className={styles.hideOnMobile}>
|
||||
<RemoveExtraRooms fullWidth />
|
||||
<RemoveExtraRooms />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
.rewardNightLabel {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: var(--Spacing-x1);
|
||||
color: var(--Text-Secondary);
|
||||
min-width: 160px;
|
||||
gap: var(--Space-x1);
|
||||
}
|
||||
|
||||
.rewardNightTooltip {
|
||||
|
||||
@@ -185,4 +185,8 @@
|
||||
.input {
|
||||
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 { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking"
|
||||
|
||||
import { useBookingFlowConfig } from "../../../../bookingFlowConfig/bookingFlowConfigContext"
|
||||
import useLang from "../../../../hooks/useLang"
|
||||
import GuestsRoomsPickerForm from "../../../BookingWidget/GuestsRoomsPicker"
|
||||
import DatePicker from "../../DatePicker"
|
||||
import { RemoveExtraRooms } from "./BookingCode"
|
||||
import { RemoveExtraRooms } from "./RemoveExtraRooms/RemoveExtraRooms"
|
||||
import { Search, SearchSkeleton } from "./Search"
|
||||
import { isMultiRoomError } from "./utils"
|
||||
import ValidationError from "./ValidationError"
|
||||
@@ -40,6 +41,7 @@ export default function FormContent({
|
||||
const {
|
||||
formState: { errors, isDirty },
|
||||
} = useFormContext<BookingWidgetSchema>()
|
||||
const { bookingCodeEnabled } = useBookingFlowConfig()
|
||||
|
||||
const lang = useLang()
|
||||
const pathName = usePathname()
|
||||
@@ -109,14 +111,20 @@ export default function FormContent({
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div className={cx(styles.voucherContainer, styles.voucherRow)}>
|
||||
<div
|
||||
className={cx(
|
||||
styles.voucherContainer,
|
||||
styles.voucherRow,
|
||||
bookingCodeEnabled ? null : styles.bookingCodeDisabled
|
||||
)}
|
||||
>
|
||||
<Voucher />
|
||||
</div>
|
||||
<div className={cx(styles.buttonContainer, styles.hideOnTablet)}>
|
||||
{isMultiRoomError(errors.bookingCode?.value?.message) ||
|
||||
isMultiRoomError(errors[SEARCH_TYPE_REDEMPTION]?.message) ? (
|
||||
<div className={styles.showOnMobile}>
|
||||
<RemoveExtraRooms size="medium" fullWidth />
|
||||
<RemoveExtraRooms />
|
||||
</div>
|
||||
) : null}
|
||||
<Button
|
||||
|
||||
Reference in New Issue
Block a user