Merged in feat/BOOK-485-campaign-rate-my-stay (pull request #3120)
feat(BOOK-485): add campaign tag on my stay and update design * feat(BOOK-485): add campaign tag on my stay and update design * feat(BOOK-485): update rightAligned Approved-by: Erik Tiekstra
This commit is contained in:
@@ -37,6 +37,7 @@ export default function PriceDetails() {
|
|||||||
totalPrice={totalPrice}
|
totalPrice={totalPrice}
|
||||||
vat={bookedRoom.vatPercentage}
|
vat={bookedRoom.vatPercentage}
|
||||||
defaultCurrency={bookedRoom.currencyCode}
|
defaultCurrency={bookedRoom.currencyCode}
|
||||||
|
isCampaignRate={bookedRoom.isCampaignRate}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
.row {
|
.row {
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
|
&.rightAligned {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
import { cx } from "class-variance-authority"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import IconChip from "@scandic-hotels/design-system/IconChip"
|
import { BookingCodeChip } from "@scandic-hotels/design-system/BookingCodeChip"
|
||||||
import DiscountIcon from "@scandic-hotels/design-system/Icons/DiscountIcon"
|
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import { useMyStayStore } from "@/stores/my-stay"
|
import { useMyStayStore } from "@/stores/my-stay"
|
||||||
@@ -11,31 +11,40 @@ import styles from "./bookingCode.module.css"
|
|||||||
|
|
||||||
export default function BookingCode() {
|
export default function BookingCode() {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const bookingCode = useMyStayStore((state) => state.bookedRoom.bookingCode)
|
const { bookingCode, isCampaignRate } = useMyStayStore((state) => ({
|
||||||
|
bookingCode: state.bookedRoom.bookingCode,
|
||||||
|
isCampaignRate: state.bookedRoom.isCampaignRate,
|
||||||
|
}))
|
||||||
|
|
||||||
if (!bookingCode) {
|
if (!bookingCode && !isCampaignRate) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const codeType = isCampaignRate
|
||||||
<div className={styles.row}>
|
? intl.formatMessage({
|
||||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
id: "booking.campaignCode",
|
||||||
<p>
|
defaultMessage: "Campaign code",
|
||||||
{intl.formatMessage({
|
})
|
||||||
id: "booking.bookingCode",
|
: intl.formatMessage({
|
||||||
defaultMessage: "Booking code",
|
id: "booking.bookingCode",
|
||||||
})}
|
defaultMessage: "Booking code",
|
||||||
</p>
|
})
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<IconChip
|
const showCodeType = bookingCode || !isCampaignRate
|
||||||
color="blue"
|
|
||||||
icon={<DiscountIcon color="Icon/Feedback/Information" />}
|
return (
|
||||||
>
|
<div className={cx(styles.row, { [styles.rightAligned]: !showCodeType })}>
|
||||||
|
{showCodeType && (
|
||||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||||
<span>{bookingCode}</span>
|
<p>{codeType}</p>
|
||||||
</Typography>
|
</Typography>
|
||||||
</IconChip>
|
)}
|
||||||
|
|
||||||
|
<BookingCodeChip
|
||||||
|
bookingCode={bookingCode}
|
||||||
|
isCampaign={isCampaignRate}
|
||||||
|
withText={!showCodeType}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
"use client"
|
|
||||||
import { useIntl } from "react-intl"
|
|
||||||
|
|
||||||
import IconChip from "@scandic-hotels/design-system/IconChip"
|
|
||||||
import DiscountIcon from "@scandic-hotels/design-system/Icons/DiscountIcon"
|
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
|
||||||
|
|
||||||
import { useMyStayStore } from "@/stores/my-stay"
|
|
||||||
|
|
||||||
export default function BookingCode() {
|
|
||||||
const intl = useIntl()
|
|
||||||
|
|
||||||
const bookingCode = useMyStayStore((state) => state.bookedRoom.bookingCode)
|
|
||||||
|
|
||||||
if (!bookingCode) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
|
||||||
<IconChip
|
|
||||||
color="blue"
|
|
||||||
icon={<DiscountIcon color="Icon/Feedback/Information" />}
|
|
||||||
>
|
|
||||||
{intl.formatMessage(
|
|
||||||
{
|
|
||||||
id: "booking.bookingCodeWithValue",
|
|
||||||
defaultMessage: "<strong>Booking code</strong>: {value}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: bookingCode,
|
|
||||||
strong: (text) => (
|
|
||||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
|
||||||
<strong>{text}</strong>
|
|
||||||
</Typography>
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</IconChip>
|
|
||||||
</Typography>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,19 @@
|
|||||||
import BookingCode from "./BookingCode"
|
import { BookingCodeChip } from "@scandic-hotels/design-system/BookingCodeChip"
|
||||||
|
|
||||||
|
import { useMyStayStore } from "@/stores/my-stay"
|
||||||
|
|
||||||
import PriceDetails from "./PriceDetails"
|
import PriceDetails from "./PriceDetails"
|
||||||
|
|
||||||
import styles from "./information.module.css"
|
import styles from "./information.module.css"
|
||||||
|
|
||||||
export default function BookingInformation() {
|
export default function BookingInformation() {
|
||||||
|
const { bookingCode, isCampaignRate } = useMyStayStore((state) => ({
|
||||||
|
bookingCode: state.bookedRoom.bookingCode,
|
||||||
|
isCampaignRate: state.bookedRoom.isCampaignRate,
|
||||||
|
}))
|
||||||
return (
|
return (
|
||||||
<div className={styles.bookingInformation}>
|
<div className={styles.bookingInformation}>
|
||||||
<BookingCode />
|
<BookingCodeChip bookingCode={bookingCode} isCampaign={isCampaignRate} />
|
||||||
<PriceDetails />
|
<PriceDetails />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -138,6 +138,7 @@ export function mapRoomDetails({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...booking,
|
...booking,
|
||||||
|
isCampaignRate: booking.rateDefinition.isCampaignRate,
|
||||||
bedType: {
|
bedType: {
|
||||||
description: room?.bedType.mainBed.description ?? "",
|
description: room?.bedType.mainBed.description ?? "",
|
||||||
roomTypeCode: room?.bedType.code ?? "",
|
roomTypeCode: room?.bedType.code ?? "",
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import { dt } from "@scandic-hotels/common/dt"
|
|||||||
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
|
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
|
||||||
import Accordion from "@scandic-hotels/design-system/Accordion"
|
import Accordion from "@scandic-hotels/design-system/Accordion"
|
||||||
import AccordionItem from "@scandic-hotels/design-system/Accordion/AccordionItem"
|
import AccordionItem from "@scandic-hotels/design-system/Accordion/AccordionItem"
|
||||||
|
import { BookingCodeChip } from "@scandic-hotels/design-system/BookingCodeChip"
|
||||||
import IconChip from "@scandic-hotels/design-system/IconChip"
|
import IconChip from "@scandic-hotels/design-system/IconChip"
|
||||||
import DiscountIcon from "@scandic-hotels/design-system/Icons/DiscountIcon"
|
|
||||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||||
import ImageGallery from "@scandic-hotels/design-system/ImageGallery"
|
import ImageGallery from "@scandic-hotels/design-system/ImageGallery"
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
@@ -422,29 +422,10 @@ export default function BookedRoomSidePeekContent({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{bookingCode && (
|
<BookingCodeChip
|
||||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
bookingCode={bookingCode}
|
||||||
<IconChip
|
isCampaign={rateDefinition.isCampaignRate}
|
||||||
color="blue"
|
/>
|
||||||
icon={<DiscountIcon color="Icon/Feedback/Information" />}
|
|
||||||
>
|
|
||||||
{intl.formatMessage(
|
|
||||||
{
|
|
||||||
id: "booking.bookingCodeWithValue",
|
|
||||||
defaultMessage: "<strong>Booking code</strong>: {value}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: bookingCode,
|
|
||||||
strong: (text) => (
|
|
||||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
|
||||||
<strong>{text}</strong>
|
|
||||||
</Typography>
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</IconChip>
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<GuestDetails
|
<GuestDetails
|
||||||
refId={refId}
|
refId={refId}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export type Room = Omit<
|
|||||||
BookingConfirmation["booking"],
|
BookingConfirmation["booking"],
|
||||||
"packages" | "roomPrice"
|
"packages" | "roomPrice"
|
||||||
> & {
|
> & {
|
||||||
|
isCampaignRate: boolean
|
||||||
bedType: BedTypeSchema
|
bedType: BedTypeSchema
|
||||||
breakfast: Omit<BreakfastPackage, "requestedPrice"> | undefined | false
|
breakfast: Omit<BreakfastPackage, "requestedPrice"> | undefined | false
|
||||||
breakfastChildren: Omit<BreakfastPackage, "requestedPrice"> | null
|
breakfastChildren: Omit<BreakfastPackage, "requestedPrice"> | null
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ export function RemoveBookingCodeButton() {
|
|||||||
return (
|
return (
|
||||||
<BookingCodeChip
|
<BookingCodeChip
|
||||||
bookingCode={bookingCode}
|
bookingCode={bookingCode}
|
||||||
filledIcon
|
|
||||||
isCampaign={hasCampaignRates}
|
isCampaign={hasCampaignRates}
|
||||||
withCloseButton={true}
|
withCloseButton={true}
|
||||||
withText={false}
|
withText={false}
|
||||||
|
|||||||
@@ -23,11 +23,6 @@ export const WithoutText: Story = {
|
|||||||
render: () => <BookingCodeChip bookingCode="ABC123" withText={false} />,
|
render: () => <BookingCodeChip bookingCode="ABC123" withText={false} />,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FilledIcon: Story = {
|
|
||||||
args: {},
|
|
||||||
render: () => <BookingCodeChip bookingCode="ABC123" filledIcon />,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Unavailable: Story = {
|
export const Unavailable: Story = {
|
||||||
args: {},
|
args: {},
|
||||||
render: () => <BookingCodeChip bookingCode="ABC123" isUnavailable />,
|
render: () => <BookingCodeChip bookingCode="ABC123" isUnavailable />,
|
||||||
@@ -50,9 +45,7 @@ export const CampaignWithoutBookingCode: Story = {
|
|||||||
render: () => <BookingCodeChip isCampaign />,
|
render: () => <BookingCodeChip isCampaign />,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CampaignFilledIcon: Story = {
|
export const CampaignWithBookingCode: Story = {
|
||||||
args: {},
|
args: {},
|
||||||
render: () => (
|
render: () => <BookingCodeChip isCampaign bookingCode="SUMMER25" />,
|
||||||
<BookingCodeChip isCampaign bookingCode="SUMMER25" filledIcon />
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,12 @@
|
|||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: var(--Space-x05);
|
||||||
|
}
|
||||||
|
|
||||||
.center {
|
.center {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useIntl } from 'react-intl'
|
import { useIntl } from 'react-intl'
|
||||||
|
|
||||||
import IconChip from '../IconChip'
|
import IconChip from '../IconChip'
|
||||||
import DiscountIcon from '../Icons/Nucleo/Benefits/discount-2-2'
|
|
||||||
import FilledDiscountIcon from '../Icons/Nucleo/Benefits/FilledDiscount'
|
import FilledDiscountIcon from '../Icons/Nucleo/Benefits/FilledDiscount'
|
||||||
import { MaterialIcon } from '../Icons/MaterialIcon'
|
import { MaterialIcon } from '../Icons/MaterialIcon'
|
||||||
import { Typography } from '../Typography'
|
import { Typography } from '../Typography'
|
||||||
@@ -17,7 +16,6 @@ type BaseBookingCodeChipProps = {
|
|||||||
isUnavailable?: boolean
|
isUnavailable?: boolean
|
||||||
isCampaignUnavailable?: boolean
|
isCampaignUnavailable?: boolean
|
||||||
withText?: boolean
|
withText?: boolean
|
||||||
filledIcon?: boolean
|
|
||||||
}
|
}
|
||||||
type BookingCodeChipWithoutCloseButtonProps = BaseBookingCodeChipProps & {
|
type BookingCodeChipWithoutCloseButtonProps = BaseBookingCodeChipProps & {
|
||||||
withCloseButton?: false
|
withCloseButton?: false
|
||||||
@@ -39,106 +37,68 @@ export function BookingCodeChip({
|
|||||||
isCampaignUnavailable,
|
isCampaignUnavailable,
|
||||||
isUnavailable,
|
isUnavailable,
|
||||||
withText = true,
|
withText = true,
|
||||||
filledIcon = false,
|
|
||||||
withCloseButton,
|
withCloseButton,
|
||||||
onClose,
|
onClose,
|
||||||
}: BookingCodeChipProps) {
|
}: BookingCodeChipProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
if (isCampaign || isCampaignUnavailable) {
|
const isCampaignRate = isCampaign || isCampaignUnavailable
|
||||||
return (
|
if (!isCampaignRate && !bookingCode) {
|
||||||
<IconChip
|
|
||||||
color="green"
|
|
||||||
icon={
|
|
||||||
filledIcon ? (
|
|
||||||
<MaterialIcon
|
|
||||||
icon="sell"
|
|
||||||
color="Icon/Feedback/Success"
|
|
||||||
isFilled={!!filledIcon}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<MaterialIcon icon="sell" color="Icon/Feedback/Success" />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
className={alignCenter ? styles.center : undefined}
|
|
||||||
>
|
|
||||||
<p
|
|
||||||
className={cx(styles.bookingCodeChip, {
|
|
||||||
[styles.unavailable]: isCampaignUnavailable,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
|
||||||
<strong>
|
|
||||||
{intl.formatMessage({
|
|
||||||
id: 'booking.campaign',
|
|
||||||
defaultMessage: 'Campaign',
|
|
||||||
})}
|
|
||||||
</strong>
|
|
||||||
</Typography>
|
|
||||||
{bookingCode && (
|
|
||||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
|
||||||
{/*eslint-disable-next-line formatjs/no-literal-string-in-jsx*/}
|
|
||||||
<span>∙ {bookingCode}</span>
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
{withCloseButton && (
|
|
||||||
<IconButton
|
|
||||||
style="Muted"
|
|
||||||
theme="Inverted"
|
|
||||||
wrapping
|
|
||||||
className={styles.removeButton}
|
|
||||||
onPress={onClose}
|
|
||||||
aria-label={intl.formatMessage({
|
|
||||||
id: 'booking.removeBookingCode',
|
|
||||||
defaultMessage: 'Remove booking code',
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<MaterialIcon
|
|
||||||
icon="close"
|
|
||||||
size={16}
|
|
||||||
color="Icon/Feedback/Success"
|
|
||||||
/>
|
|
||||||
</IconButton>
|
|
||||||
)}
|
|
||||||
</IconChip>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!bookingCode) {
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const color = isCampaignRate ? 'green' : 'blue'
|
||||||
|
|
||||||
|
const iconColor = isCampaignRate
|
||||||
|
? 'Icon/Feedback/Success'
|
||||||
|
: 'Icon/Feedback/Information'
|
||||||
|
|
||||||
|
const isUnavailableRate = isCampaignRate
|
||||||
|
? isCampaignUnavailable
|
||||||
|
: isUnavailable
|
||||||
|
|
||||||
|
const label = isCampaignRate
|
||||||
|
? intl.formatMessage({
|
||||||
|
id: 'booking.campaign',
|
||||||
|
defaultMessage: 'Campaign',
|
||||||
|
})
|
||||||
|
: intl.formatMessage({
|
||||||
|
id: 'booking.bookingCode',
|
||||||
|
defaultMessage: 'Booking code',
|
||||||
|
})
|
||||||
|
|
||||||
|
const icon = isCampaignRate ? (
|
||||||
|
<MaterialIcon icon="sell" color={iconColor} isFilled={false} size={20} />
|
||||||
|
) : (
|
||||||
|
<FilledDiscountIcon fill={iconColor} size={20} />
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IconChip
|
<IconChip
|
||||||
color="blue"
|
color={color}
|
||||||
icon={
|
icon={icon}
|
||||||
filledIcon ? (
|
|
||||||
<FilledDiscountIcon fill="Icon/Feedback/Information" />
|
|
||||||
) : (
|
|
||||||
<DiscountIcon color="Icon/Feedback/Information" />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
className={alignCenter ? styles.center : undefined}
|
className={alignCenter ? styles.center : undefined}
|
||||||
>
|
>
|
||||||
<p
|
<p
|
||||||
className={cx(styles.bookingCodeChip, {
|
className={cx(styles.bookingCodeChip, {
|
||||||
[styles.unavailable]: isUnavailable,
|
[styles.unavailable]: isUnavailableRate,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{withText && (
|
{withText && (
|
||||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||||
<strong>
|
<strong>{label}</strong>
|
||||||
{intl.formatMessage({
|
</Typography>
|
||||||
id: 'booking.bookingCode',
|
)}
|
||||||
defaultMessage: 'Booking code',
|
|
||||||
})}
|
{bookingCode && (
|
||||||
</strong>
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
||||||
|
<span>
|
||||||
|
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||||
|
{withText && <span className={styles.separator}>∙</span>}
|
||||||
|
{bookingCode}
|
||||||
|
</span>
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
|
||||||
<span>{bookingCode}</span>
|
|
||||||
</Typography>
|
|
||||||
</p>
|
</p>
|
||||||
{withCloseButton && (
|
{withCloseButton && (
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -152,11 +112,7 @@ export function BookingCodeChip({
|
|||||||
defaultMessage: 'Remove booking code',
|
defaultMessage: 'Remove booking code',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<MaterialIcon
|
<MaterialIcon icon="close" size={16} color={iconColor} />
|
||||||
icon="close"
|
|
||||||
size={16}
|
|
||||||
color="Icon/Feedback/Information"
|
|
||||||
/>
|
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
</IconChip>
|
</IconChip>
|
||||||
|
|||||||
Reference in New Issue
Block a user