Merged in feat/SW-2078-update-confirmation-page-vouchers (pull request #1731)

Feat/SW-2078 update confirmation page vouchers and Corp Cheques rate

* feat: SW-2078 Tablet bookingCode ref forward issue fix

(cherry picked from commit 16a6a00fd99b6b6220a98ad74de062d67d35e1c0)

* feat: SW-2078 Display Vouchers and Cheques prices on confirmation page

(cherry picked from commit a76494de497a7d5e7641cb0036bd7055acf875c1)

* feat: SW-2078 Rebase issue fix

* feat: SW-2079 Updated rate title in terms modal

* feat: SW-2078 Optimized code

* feat: SW-2078 Removed extra tags


Approved-by: Christian Andolf
This commit is contained in:
Hrishikesh Vaipurkar
2025-04-08 07:27:40 +00:00
parent c56a0b8ce9
commit 73cb423c95
26 changed files with 300 additions and 143 deletions

View File

@@ -322,6 +322,15 @@ function TabletBookingCode({
const codeVoucher = intl.formatMessage({ id: "Code / Voucher" }) const codeVoucher = intl.formatMessage({ id: "Code / Voucher" })
function toggleModal(isOpen: boolean) { function toggleModal(isOpen: boolean) {
if (document.body) {
if (isOpen) {
document.body.style.overflow = "visible"
} else {
// !important needed to override 'overflow: hidden' set by react-aria.
// 'overflow: hidden' does not work in combination with other sticky positioned elements, which clip does.
document.body.style.overflow = "clip !important"
}
}
if (!isOpen && !bookingCode?.value) { if (!isOpen && !bookingCode?.value) {
setValue("bookingCode.flag", false) setValue("bookingCode.flag", false)
setIsOpen(isOpen) setIsOpen(isOpen)

View File

@@ -2,6 +2,7 @@
import React from "react" import React from "react"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
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 { Typography } from "@scandic-hotels/design-system/Typography" import { Typography } from "@scandic-hotels/design-system/Typography"
@@ -10,7 +11,7 @@ import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
import Modal from "@/components/Modal" import Modal from "@/components/Modal"
import Button from "@/components/TempDesignSystem/Button" import Button from "@/components/TempDesignSystem/Button"
import Body from "@/components/TempDesignSystem/Text/Body" import IconChip from "@/components/TempDesignSystem/IconChip"
import Caption from "@/components/TempDesignSystem/Text/Caption" import Caption from "@/components/TempDesignSystem/Text/Caption"
import useLang from "@/hooks/useLang" import useLang from "@/hooks/useLang"
import { formatPrice } from "@/utils/numberFormatting" import { formatPrice } from "@/utils/numberFormatting"
@@ -45,15 +46,26 @@ function TableSection({ children }: React.PropsWithChildren) {
function TableSectionHeader({ function TableSectionHeader({
title, title,
subtitle, subtitle,
bold,
}: { }: {
title: string title: string
subtitle?: string subtitle?: string
bold?: boolean
}) { }) {
const typographyVariant = bold
? "Body/Paragraph/mdBold"
: "Body/Paragraph/mdRegular"
return ( return (
<tr> <tr>
<th colSpan={2}> <th colSpan={2} align="left">
<Body>{title}</Body> <Typography variant={typographyVariant}>
{subtitle ? <Body>{subtitle}</Body> : null} <p>{title}</p>
</Typography>
{subtitle ? (
<Typography variant="Body/Paragraph/mdRegular">
<p>{subtitle}</p>
</Typography>
) : null}
</th> </th>
</tr> </tr>
) )
@@ -130,12 +142,13 @@ export default function PriceDetailsModal() {
<React.Fragment key={idx}> <React.Fragment key={idx}>
<TableSection> <TableSection>
{rooms.length > 1 && ( {rooms.length > 1 && (
<Body textTransform="bold"> <TableSectionHeader
{intl.formatMessage( title={intl.formatMessage(
{ id: "Room {roomIndex}" }, { id: "Room {roomIndex}" },
{ roomIndex: idx + 1 } { roomIndex: idx + 1 }
)} )}
</Body> bold
/>
)} )}
<TableSectionHeader title={room.name} subtitle={duration} /> <TableSectionHeader title={room.name} subtitle={duration} />
{room.roomFeatures {room.roomFeatures
@@ -179,14 +192,14 @@ export default function PriceDetailsModal() {
currencyCode currencyCode
)} )}
/> />
{room.children ? ( {room.childrenAges?.length ? (
<Row <Row
label={intl.formatMessage( label={intl.formatMessage(
{ {
id: "Breakfast ({totalChildren, plural, one {# child} other {# children}}) x {totalBreakfasts}", id: "Breakfast ({totalChildren, plural, one {# child} other {# children}}) x {totalBreakfasts}",
}, },
{ {
totalChildren: room.children, totalChildren: room.childrenAges.length,
totalBreakfasts: diff, totalBreakfasts: diff,
} }
)} )}
@@ -237,11 +250,26 @@ export default function PriceDetailsModal() {
</tr> </tr>
{bookingCode && ( {bookingCode && (
<tr className={styles.row}> <tr className={styles.row}>
<td> <td colSpan={2} align="left">
<MaterialIcon icon="sell" /> <Typography variant="Body/Supporting text (caption)/smRegular">
{bookingCode} <IconChip
color="blue"
icon={<DiscountIcon color="Icon/Feedback/Information" />}
>
{intl.formatMessage(
{ id: "<strong>Booking code</strong>: {value}" },
{
value: bookingCode,
strong: (text) => (
<Typography variant="Body/Supporting text (caption)/smBold">
<strong>{text}</strong>
</Typography>
),
}
)}
</IconChip>
</Typography>
</td> </td>
<td></td>
</tr> </tr>
)} )}
</TableSection> </TableSection>

View File

@@ -23,10 +23,13 @@ export default function ReceiptRoom({
roomIndex, roomIndex,
}: BookingConfirmationReceiptRoomProps) { }: BookingConfirmationReceiptRoomProps) {
const intl = useIntl() const intl = useIntl()
const { room, currencyCode } = useBookingConfirmationStore((state) => ({ const { room, currencyCode, isVatCurrency } = useBookingConfirmationStore(
room: state.rooms[roomIndex], (state) => ({
currencyCode: state.currencyCode, room: state.rooms[roomIndex],
})) currencyCode: state.currencyCode,
isVatCurrency: state.isVatCurrency,
})
)
if (!room) { if (!room) {
return <RoomSkeletonLoader /> return <RoomSkeletonLoader />
@@ -56,7 +59,7 @@ export default function ReceiptRoom({
) : ( ) : (
<Typography variant="Body/Paragraph/mdRegular"> <Typography variant="Body/Paragraph/mdRegular">
<p className={styles.uiTextHighContrast}> <p className={styles.uiTextHighContrast}>
{formatPrice(intl, room.roomPrice, currencyCode)} {room.formattedTotalCost}
</p> </p>
</Typography> </Typography>
)} )}
@@ -91,9 +94,9 @@ export default function ReceiptRoom({
</Button> </Button>
} }
title={ title={
(room.roomPoints (isVatCurrency
? room.rateDefinition.title ? room.rateDefinition.cancellationText
: room.rateDefinition.cancellationText) || "" : room.rateDefinition.title) || ""
} }
subtitle={ subtitle={
room.rateDefinition.cancellationRule === room.rateDefinition.cancellationRule ===

View File

@@ -15,6 +15,7 @@ import { LinkedReservationCardSkeleton } from "./LinkedReservationCardSkeleton"
import Retry from "./Retry" import Retry from "./Retry"
import type { LinkedReservationProps } from "@/types/components/hotelReservation/bookingConfirmation/rooms/linkedReservation" import type { LinkedReservationProps } from "@/types/components/hotelReservation/bookingConfirmation/rooms/linkedReservation"
import { CurrencyEnum } from "@/types/enums/currency"
export function LinkedReservation({ export function LinkedReservation({
checkInTime, checkInTime,
@@ -27,13 +28,19 @@ export function LinkedReservation({
confirmationNumber, confirmationNumber,
lang, lang,
}) })
const { setRoom, setFormattedTotalCost, currencyCode, totalBookingPrice } = const {
useBookingConfirmationStore((state) => ({ setRoom,
setRoom: state.actions.setRoom, setFormattedTotalCost,
setFormattedTotalCost: state.actions.setFormattedTotalCost, currencyCode,
currencyCode: state.currencyCode, totalBookingPrice,
totalBookingPrice: state.totalBookingPrice, totalBookingCheques,
})) } = useBookingConfirmationStore((state) => ({
setRoom: state.actions.setRoom,
setFormattedTotalCost: state.actions.setFormattedTotalCost,
currencyCode: state.currencyCode,
totalBookingPrice: state.totalBookingPrice,
totalBookingCheques: state.totalBookingCheques,
}))
const intl = useIntl() const intl = useIntl()
useEffect(() => { useEffect(() => {
@@ -41,18 +48,24 @@ export function LinkedReservation({
const roomData = mapRoomState(data.booking, data.room, intl) const roomData = mapRoomState(data.booking, data.room, intl)
setRoom(roomData, roomIndex) setRoom(roomData, roomIndex)
const formattedTotalCost = formatPrice( const formattedTotalCost = totalBookingCheques
intl, ? formatPrice(
totalBookingPrice, intl,
currencyCode totalBookingCheques,
) CurrencyEnum.CC,
totalBookingPrice,
currencyCode
)
: formatPrice(intl, totalBookingPrice, currencyCode)
setFormattedTotalCost(formattedTotalCost) setFormattedTotalCost(formattedTotalCost)
} }
}, [ }, [
data, data,
roomIndex, roomIndex,
setRoom,
intl, intl,
setRoom,
totalBookingCheques,
totalBookingPrice, totalBookingPrice,
currencyCode, currencyCode,
setFormattedTotalCost, setFormattedTotalCost,

View File

@@ -75,7 +75,7 @@ export function getTracking(
} }
const noOfAdults = rooms.map((r) => r.adults).join(",") const noOfAdults = rooms.map((r) => r.adults).join(",")
const noOfChildren = rooms.map((r) => r.children ?? 0).join(",") const noOfChildren = rooms.map((r) => r.childrenAges?.length ?? 0).join(",")
const noOfRooms = rooms.length const noOfRooms = rooms.length
const hotelsTrackingData: TrackingSDKHotelInfo = { const hotelsTrackingData: TrackingSDKHotelInfo = {

View File

@@ -32,6 +32,20 @@ export function mapRoomState(
booking.totalPrice, booking.totalPrice,
booking.currencyCode booking.currencyCode
) )
} else if (booking.cheques) {
formattedTotalCost = formatPrice(
intl,
booking.cheques,
CurrencyEnum.CC,
booking.totalPrice,
booking.currencyCode
)
} else if (booking.vouchers) {
formattedTotalCost = formatPrice(
intl,
booking.vouchers,
CurrencyEnum.Voucher
)
} }
return { return {
@@ -39,7 +53,7 @@ export function mapRoomState(
bedDescription: room.bedType.description, bedDescription: room.bedType.description,
breakfast, breakfast,
breakfastIncluded, breakfastIncluded,
children: booking.childrenAges.length, cheques: booking.cheques,
childrenAges: booking.childrenAges, childrenAges: booking.childrenAges,
childBedPreferences: booking.childBedPreferences, childBedPreferences: booking.childBedPreferences,
confirmationNumber: booking.confirmationNumber, confirmationNumber: booking.confirmationNumber,
@@ -57,5 +71,6 @@ export function mapRoomState(
totalPrice: booking.totalPrice, totalPrice: booking.totalPrice,
totalPriceExVat: booking.totalPriceExVat, totalPriceExVat: booking.totalPriceExVat,
vatAmount: booking.vatAmount, vatAmount: booking.vatAmount,
vouchers: booking.vouchers,
} }
} }

View File

@@ -3,10 +3,12 @@
import { Fragment } from "react" import { Fragment } from "react"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import DiscountIcon from "@scandic-hotels/design-system/Icons/DiscountIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { dt } from "@/lib/dt" import { dt } from "@/lib/dt"
import IconChip from "@/components/TempDesignSystem/IconChip"
import Body from "@/components/TempDesignSystem/Text/Body" import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption" import Caption from "@/components/TempDesignSystem/Text/Caption"
import useLang from "@/hooks/useLang" import useLang from "@/hooks/useLang"
@@ -328,13 +330,28 @@ export default function PriceDetailsTable({
</td> </td>
</tr> </tr>
) : null} ) : null}
{bookingCode && totalPrice.local.regularPrice && ( {bookingCode && (
<tr className={styles.row}> <tr className={styles.row}>
<td> <td colSpan={2} align="left">
<MaterialIcon icon="sell" /> <Typography variant="Body/Supporting text (caption)/smRegular">
{bookingCode} <IconChip
color="blue"
icon={<DiscountIcon color="Icon/Feedback/Information" />}
>
{intl.formatMessage(
{ id: "<strong>Booking code</strong>: {value}" },
{
value: bookingCode,
strong: (text) => (
<Typography variant="Body/Supporting text (caption)/smBold">
<strong>{text}</strong>
</Typography>
),
}
)}
</IconChip>
</Typography>
</td> </td>
<td></td>
</tr> </tr>
)} )}
</TableSection> </TableSection>

View File

@@ -3,7 +3,9 @@
import { Fragment } from "react" import { Fragment } from "react"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
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 { Typography } from "@scandic-hotels/design-system/Typography"
import { dt } from "@/lib/dt" import { dt } from "@/lib/dt"
@@ -12,6 +14,7 @@ import SignupPromoDesktop from "@/components/HotelReservation/SignupPromo/Deskto
import Modal from "@/components/Modal" import Modal from "@/components/Modal"
import Button from "@/components/TempDesignSystem/Button" import Button from "@/components/TempDesignSystem/Button"
import Divider from "@/components/TempDesignSystem/Divider" import Divider from "@/components/TempDesignSystem/Divider"
import IconChip from "@/components/TempDesignSystem/IconChip"
import Body from "@/components/TempDesignSystem/Text/Body" import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption" import Caption from "@/components/TempDesignSystem/Text/Caption"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
@@ -450,10 +453,24 @@ export default function SummaryUI({
</div> </div>
</div> </div>
{booking.bookingCode && ( {booking.bookingCode && (
<div> <Typography variant="Body/Supporting text (caption)/smRegular">
<MaterialIcon icon="sell" /> <IconChip
{booking.bookingCode} color="blue"
</div> icon={<DiscountIcon color="Icon/Feedback/Information" />}
>
{intl.formatMessage(
{ id: "<strong>Booking code</strong>: {value}" },
{
value: booking.bookingCode,
strong: (text) => (
<Typography variant="Body/Supporting text (caption)/smBold">
<strong>{text}</strong>
</Typography>
),
}
)}
</IconChip>
</Typography>
)} )}
<Divider className={styles.bottomDivider} color="primaryLightSubtle" /> <Divider className={styles.bottomDivider} color="primaryLightSubtle" />
</div> </div>

View File

@@ -5,8 +5,8 @@ import { useRouter, useSearchParams } from "next/navigation"
import { memo, useCallback } from "react" import { memo, useCallback } from "react"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import DiscountIcon from "@scandic-hotels/design-system/Icons/DiscountIcon"
import HotelLogoIcon from "@scandic-hotels/design-system/Icons/HotelLogoIcon" import HotelLogoIcon from "@scandic-hotels/design-system/Icons/HotelLogoIcon"
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 { selectHotelMap, selectRate } from "@/constants/routes/hotelReservation" import { selectHotelMap, selectRate } from "@/constants/routes/hotelReservation"
@@ -185,14 +185,26 @@ function HotelCard({
) : ( ) : (
<> <>
{bookingCode && ( {bookingCode && (
<span className={`${fullPrice ? styles.strikedText : ""}`}> <div className={`${fullPrice ? styles.strikedText : ""}`}>
<IconChip <Typography variant="Body/Supporting text (caption)/smRegular">
color="blue" <IconChip
icon={<MaterialIcon icon="sell" size={20} />} color="blue"
> icon={<DiscountIcon color="Icon/Feedback/Information" />}
{bookingCode} >
</IconChip> {intl.formatMessage(
</span> { id: "<strong>Booking code</strong>: {value}" },
{
value: bookingCode,
strong: (text) => (
<Typography variant="Body/Supporting text (caption)/smBold">
<strong>{text}</strong>
</Typography>
),
}
)}
</IconChip>
</Typography>
</div>
)} )}
{(!isUserLoggedIn || {(!isUserLoggedIn ||
!price?.member || !price?.member ||

View File

@@ -273,17 +273,24 @@ export function ReferenceCard({
<Typography variant="Title/Overline/sm"> <Typography variant="Title/Overline/sm">
<p>{intl.formatMessage({ id: "Booking code" })}</p> <p>{intl.formatMessage({ id: "Booking code" })}</p>
</Typography> </Typography>
<IconChip <Typography variant="Body/Supporting text (caption)/smBold">
color="blue" <IconChip
icon={<DiscountIcon color="Icon/Feedback/Information" />} color="blue"
> icon={<DiscountIcon color="Icon/Feedback/Information" />}
<Typography variant="Body/Supporting text (caption)/smBold"> >
<p className={styles.bookingCode}> {intl.formatMessage(
<strong>{intl.formatMessage({ id: "Booking code" })}</strong>:{" "} { id: "<strong>Booking code</strong>: {value}" },
{bookingCode} {
</p> value: bookingCode,
</Typography> strong: (text) => (
</IconChip> <Typography variant="Body/Supporting text (caption)/smBold">
<strong>{text}</strong>
</Typography>
),
}
)}
</IconChip>
</Typography>
</div> </div>
)} )}
<div className={styles.actionArea}> <div className={styles.actionArea}>

View File

@@ -43,10 +43,6 @@
display: none; display: none;
} }
.bookingCode {
color: var(--UI-Semantic-Information);
}
.guaranteed { .guaranteed {
align-items: flex-start; align-items: flex-start;
border-radius: var(--Corner-radius-Medium); border-radius: var(--Corner-radius-Medium);

View File

@@ -343,19 +343,24 @@ export function SingleRoom({ bedType, image, hotel, user }: RoomProps) {
</div> </div>
<div className={styles.bookingInformation}> <div className={styles.bookingInformation}>
{bookingCode && ( {bookingCode && (
<IconChip <Typography variant="Body/Supporting text (caption)/smBold">
color="blue" <IconChip
icon={<DiscountIcon color="Icon/Feedback/Information" />} color="blue"
> icon={<DiscountIcon color="Icon/Feedback/Information" />}
<Typography variant="Body/Supporting text (caption)/smBold"> >
<p className={styles.bookingCode}> {intl.formatMessage(
<strong> { id: "<strong>Booking code</strong>: {value}" },
{intl.formatMessage({ id: "Booking code" })} {
</strong> value: bookingCode,
: {bookingCode} strong: (text) => (
</p> <Typography variant="Body/Supporting text (caption)/smBold">
</Typography> <strong>{text}</strong>
</IconChip> </Typography>
),
}
)}
</IconChip>
</Typography>
)} )}
<div className={styles.priceDetails}> <div className={styles.priceDetails}>
<div className={styles.price}> <div className={styles.price}>

View File

@@ -6,10 +6,6 @@
padding: var(--Spacing-x3) 0; padding: var(--Spacing-x3) 0;
} }
.bookingCode {
color: var(--UI-Semantic-Information);
}
.roomName { .roomName {
color: var(--Scandic-Brand-Burgundy); color: var(--Scandic-Brand-Burgundy);
padding: 0 var(--Spacing-x2); padding: 0 var(--Spacing-x2);

View File

@@ -291,17 +291,24 @@ export default function BookedRoomSidePeek({
</div> </div>
</div> </div>
{bookingCode && ( {bookingCode && (
<IconChip <Typography variant="Body/Supporting text (caption)/smBold">
color="blue" <IconChip
icon={<DiscountIcon color="Icon/Feedback/Information" />} color="blue"
> icon={<DiscountIcon color="Icon/Feedback/Information" />}
<Typography variant="Body/Supporting text (caption)/smBold"> >
<p className={styles.bookingCode}> {intl.formatMessage(
<strong>{intl.formatMessage({ id: "Booking code" })}</strong> { id: "<strong>Booking code</strong>: {value}" },
{bookingCode} {
</p> value: bookingCode,
</Typography> strong: (text) => (
</IconChip> <Typography variant="Body/Supporting text (caption)/smBold">
<strong>{text}</strong>
</Typography>
),
}
)}
</IconChip>
</Typography>
)} )}
<GuestDetails <GuestDetails

View File

@@ -1,5 +1,6 @@
"use client" "use client"
import { forwardRef } from "react"
import { Checkbox as AriaCheckbox } from "react-aria-components" import { Checkbox as AriaCheckbox } from "react-aria-components"
import { useController, useFormContext } from "react-hook-form" import { useController, useFormContext } from "react-hook-form"
@@ -11,14 +12,13 @@ import styles from "./checkbox.module.css"
import type { CheckboxProps } from "@/types/components/checkbox" import type { CheckboxProps } from "@/types/components/checkbox"
export default function Checkbox({ const Checkbox = forwardRef<
className, HTMLInputElement,
name, React.PropsWithChildren<CheckboxProps>
children, >(function Checkbox(
registerOptions, { className, name, children, registerOptions, hideError, topAlign = false },
hideError, ref
topAlign = false, ) {
}: React.PropsWithChildren<CheckboxProps>) {
const { control } = useFormContext() const { control } = useFormContext()
const { field, fieldState } = useController({ const { field, fieldState } = useController({
control, control,
@@ -43,6 +43,7 @@ export default function Checkbox({
<span <span
className={styles.checkbox} className={styles.checkbox}
tabIndex={registerOptions?.disabled ? undefined : 0} tabIndex={registerOptions?.disabled ? undefined : 0}
ref={ref}
> >
{isSelected && ( {isSelected && (
<MaterialIcon icon="check" color="Icon/Inverted" /> <MaterialIcon icon="check" color="Icon/Inverted" />
@@ -60,4 +61,6 @@ export default function Checkbox({
)} )}
</AriaCheckbox> </AriaCheckbox>
) )
} })
export default Checkbox

View File

@@ -1,4 +1,5 @@
"use client" "use client"
import { forwardRef, type HTMLAttributes, type WheelEvent } from "react"
import { Text, TextField } from "react-aria-components" import { Text, TextField } from "react-aria-components"
import { Controller, useFormContext } from "react-hook-form" import { Controller, useFormContext } from "react-hook-form"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
@@ -10,24 +11,25 @@ import Caption from "@/components/TempDesignSystem/Text/Caption"
import styles from "./input.module.css" import styles from "./input.module.css"
import type { HTMLAttributes, WheelEvent } from "react"
import type { InputProps } from "./input" import type { InputProps } from "./input"
export default function Input({ const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
"aria-label": ariaLabel, {
className = "", "aria-label": ariaLabel,
disabled = false, className = "",
helpText = "", disabled = false,
label, helpText = "",
maxLength, label,
name, maxLength,
placeholder = "", name,
readOnly = false, placeholder = "",
registerOptions = {}, readOnly = false,
type = "text", registerOptions = {},
hideError, type = "text",
}: InputProps) { hideError,
},
ref
) {
const intl = useIntl() const intl = useIntl()
const { control } = useFormContext() const { control } = useFormContext()
let numberAttributes: HTMLAttributes<HTMLInputElement> = {} let numberAttributes: HTMLAttributes<HTMLInputElement> = {}
@@ -58,6 +60,7 @@ export default function Input({
> >
<AriaInputWithLabel <AriaInputWithLabel
{...field} {...field}
ref={ref}
aria-labelledby={field.name} aria-labelledby={field.name}
id={field.name} id={field.name}
label={label} label={label}
@@ -85,4 +88,5 @@ export default function Input({
)} )}
/> />
) )
} })
export default Input

View File

@@ -9,6 +9,7 @@
"<sasMark>EuroBonus {sasLevelName}</sasMark> and <scandicMark>{scandicLevelName}</scandicMark> are equally matched tiers. Level up in one of your memberships to qualify for an upgrade!": "<sasMark>EuroBonus {sasLevelName}</sasMark> and <scandicMark>{scandicLevelName}</scandicMark> are equally matched tiers. Level up in one of your memberships to qualify for an upgrade!", "<sasMark>EuroBonus {sasLevelName}</sasMark> and <scandicMark>{scandicLevelName}</scandicMark> are equally matched tiers. Level up in one of your memberships to qualify for an upgrade!": "<sasMark>EuroBonus {sasLevelName}</sasMark> and <scandicMark>{scandicLevelName}</scandicMark> are equally matched tiers. Level up in one of your memberships to qualify for an upgrade!",
"<sasMark>EuroBonus {sasLevelName}</sasMark> has upgraded your Scandic Friends level to <scandicMark>{scandicLevelName}</scandicMark>.": "<sasMark>EuroBonus {sasLevelName}</sasMark> has upgraded your Scandic Friends level to <scandicMark>{scandicLevelName}</scandicMark>.", "<sasMark>EuroBonus {sasLevelName}</sasMark> has upgraded your Scandic Friends level to <scandicMark>{scandicLevelName}</scandicMark>.": "<sasMark>EuroBonus {sasLevelName}</sasMark> has upgraded your Scandic Friends level to <scandicMark>{scandicLevelName}</scandicMark>.",
"<strikethrough>{amount}</strikethrough> <free>0 {currency}</free>/night per adult": "<strikethrough>{amount}</strikethrough> <free>0 {currency}</free>/nat per voksen", "<strikethrough>{amount}</strikethrough> <free>0 {currency}</free>/night per adult": "<strikethrough>{amount}</strikethrough> <free>0 {currency}</free>/nat per voksen",
"<strong>Booking code</strong>: {value}": "<strong>Bookingkode</strong>: {value}",
"<strong>Status</strong> Paid": "<strong>Status</strong> Betalt", "<strong>Status</strong> Paid": "<strong>Status</strong> Betalt",
"<strong>Status</strong> Unpaid": "<strong>Status</strong> Ikke betalt", "<strong>Status</strong> Unpaid": "<strong>Status</strong> Ikke betalt",
"A destination or hotel name is needed to be able to search for a hotel room.": "Et destinations- eller hotelnavn er nødvendigt for at kunne søge efter et hotelværelse.", "A destination or hotel name is needed to be able to search for a hotel room.": "Et destinations- eller hotelnavn er nødvendigt for at kunne søge efter et hotelværelse.",

View File

@@ -9,6 +9,7 @@
"<sasMark>EuroBonus {sasLevelName}</sasMark> and <scandicMark>{scandicLevelName}</scandicMark> are equally matched tiers. Level up in one of your memberships to qualify for an upgrade!": "<sasMark>EuroBonus {sasLevelName}</sasMark> and <scandicMark>{scandicLevelName}</scandicMark> are equally matched tiers. Level up in one of your memberships to qualify for an upgrade!", "<sasMark>EuroBonus {sasLevelName}</sasMark> and <scandicMark>{scandicLevelName}</scandicMark> are equally matched tiers. Level up in one of your memberships to qualify for an upgrade!": "<sasMark>EuroBonus {sasLevelName}</sasMark> and <scandicMark>{scandicLevelName}</scandicMark> are equally matched tiers. Level up in one of your memberships to qualify for an upgrade!",
"<sasMark>EuroBonus {sasLevelName}</sasMark> has upgraded your Scandic Friends level to <scandicMark>{scandicLevelName}</scandicMark>.": "<sasMark>EuroBonus {sasLevelName}</sasMark> has upgraded your Scandic Friends level to <scandicMark>{scandicLevelName}</scandicMark>.", "<sasMark>EuroBonus {sasLevelName}</sasMark> has upgraded your Scandic Friends level to <scandicMark>{scandicLevelName}</scandicMark>.": "<sasMark>EuroBonus {sasLevelName}</sasMark> has upgraded your Scandic Friends level to <scandicMark>{scandicLevelName}</scandicMark>.",
"<strikethrough>{amount}</strikethrough> <free>0 {currency}</free>/night per adult": "<strikethrough>{amount}</strikethrough> <free>0 {currency}</free>/Nacht pro Erwachsenem", "<strikethrough>{amount}</strikethrough> <free>0 {currency}</free>/night per adult": "<strikethrough>{amount}</strikethrough> <free>0 {currency}</free>/Nacht pro Erwachsenem",
"<strong>Booking code</strong>: {value}": "<strong>Buchungscode</strong>: {value}",
"<strong>Status</strong> Paid": "<strong>Status</strong> Bezahlt", "<strong>Status</strong> Paid": "<strong>Status</strong> Bezahlt",
"<strong>Status</strong> Unpaid": "<strong>Status</strong> Unbezahlt", "<strong>Status</strong> Unpaid": "<strong>Status</strong> Unbezahlt",
"A destination or hotel name is needed to be able to search for a hotel room.": "Ein Reiseziel oder Hotelname wird benötigt, um nach einem Hotelzimmer suchen zu können.", "A destination or hotel name is needed to be able to search for a hotel room.": "Ein Reiseziel oder Hotelname wird benötigt, um nach einem Hotelzimmer suchen zu können.",

View File

@@ -9,6 +9,7 @@
"<sasMark>EuroBonus {sasLevelName}</sasMark> and <scandicMark>{scandicLevelName}</scandicMark> are equally matched tiers. Level up in one of your memberships to qualify for an upgrade!": "<sasMark>EuroBonus {sasLevelName}</sasMark> and <scandicMark>{scandicLevelName}</scandicMark> are equally matched tiers. Level up in one of your memberships to qualify for an upgrade!", "<sasMark>EuroBonus {sasLevelName}</sasMark> and <scandicMark>{scandicLevelName}</scandicMark> are equally matched tiers. Level up in one of your memberships to qualify for an upgrade!": "<sasMark>EuroBonus {sasLevelName}</sasMark> and <scandicMark>{scandicLevelName}</scandicMark> are equally matched tiers. Level up in one of your memberships to qualify for an upgrade!",
"<sasMark>EuroBonus {sasLevelName}</sasMark> has upgraded your Scandic Friends level to <scandicMark>{scandicLevelName}</scandicMark>.": "<sasMark>EuroBonus {sasLevelName}</sasMark> has upgraded your Scandic Friends level to <scandicMark>{scandicLevelName}</scandicMark>.", "<sasMark>EuroBonus {sasLevelName}</sasMark> has upgraded your Scandic Friends level to <scandicMark>{scandicLevelName}</scandicMark>.": "<sasMark>EuroBonus {sasLevelName}</sasMark> has upgraded your Scandic Friends level to <scandicMark>{scandicLevelName}</scandicMark>.",
"<strikethrough>{amount}</strikethrough> <free>0 {currency}</free>/night per adult": "<strikethrough>{amount}</strikethrough> <free>0 {currency}</free>/night per adult", "<strikethrough>{amount}</strikethrough> <free>0 {currency}</free>/night per adult": "<strikethrough>{amount}</strikethrough> <free>0 {currency}</free>/night per adult",
"<strong>Booking code</strong>: {value}": "<strong>Booking code</strong>: {value}",
"<strong>Status</strong> Paid": "<strong>Status</strong> Paid", "<strong>Status</strong> Paid": "<strong>Status</strong> Paid",
"<strong>Status</strong> Unpaid": "<strong>Status</strong> Unpaid", "<strong>Status</strong> Unpaid": "<strong>Status</strong> Unpaid",
"A destination or hotel name is needed to be able to search for a hotel room.": "A destination or hotel name is needed to be able to search for a hotel room.", "A destination or hotel name is needed to be able to search for a hotel room.": "A destination or hotel name is needed to be able to search for a hotel room.",

View File

@@ -9,6 +9,7 @@
"<sasMark>EuroBonus {sasLevelName}</sasMark> and <scandicMark>{scandicLevelName}</scandicMark> are equally matched tiers. Level up in one of your memberships to qualify for an upgrade!": "<sasMark>EuroBonus {sasLevelName}</sasMark> and <scandicMark>{scandicLevelName}</scandicMark> are equally matched tiers. Level up in one of your memberships to qualify for an upgrade!", "<sasMark>EuroBonus {sasLevelName}</sasMark> and <scandicMark>{scandicLevelName}</scandicMark> are equally matched tiers. Level up in one of your memberships to qualify for an upgrade!": "<sasMark>EuroBonus {sasLevelName}</sasMark> and <scandicMark>{scandicLevelName}</scandicMark> are equally matched tiers. Level up in one of your memberships to qualify for an upgrade!",
"<sasMark>EuroBonus {sasLevelName}</sasMark> has upgraded your Scandic Friends level to <scandicMark>{scandicLevelName}</scandicMark>.": "<sasMark>EuroBonus {sasLevelName}</sasMark> has upgraded your Scandic Friends level to <scandicMark>{scandicLevelName}</scandicMark>.", "<sasMark>EuroBonus {sasLevelName}</sasMark> has upgraded your Scandic Friends level to <scandicMark>{scandicLevelName}</scandicMark>.": "<sasMark>EuroBonus {sasLevelName}</sasMark> has upgraded your Scandic Friends level to <scandicMark>{scandicLevelName}</scandicMark>.",
"<strikethrough>{amount}</strikethrough> <free>0 {currency}</free>/night per adult": "<strikethrough>{amount}</strikethrough> <free>0 {currency}</free>/yötä aikuista kohti", "<strikethrough>{amount}</strikethrough> <free>0 {currency}</free>/night per adult": "<strikethrough>{amount}</strikethrough> <free>0 {currency}</free>/yötä aikuista kohti",
"<strong>Booking code</strong>: {value}": "<strong>Varauskoodi</strong>: {value}",
"<strong>Status</strong> Paid": "<strong>Status</strong> Maksettu", "<strong>Status</strong> Paid": "<strong>Status</strong> Maksettu",
"<strong>Status</strong> Unpaid": "<strong>Status</strong> Ei maksettu", "<strong>Status</strong> Unpaid": "<strong>Status</strong> Ei maksettu",
"A destination or hotel name is needed to be able to search for a hotel room.": "Kohteen tai hotellin nimi tarvitaan, jotta hotellihuonetta voidaan hakea.", "A destination or hotel name is needed to be able to search for a hotel room.": "Kohteen tai hotellin nimi tarvitaan, jotta hotellihuonetta voidaan hakea.",

View File

@@ -9,6 +9,7 @@
"<sasMark>EuroBonus {sasLevelName}</sasMark> and <scandicMark>{scandicLevelName}</scandicMark> are equally matched tiers. Level up in one of your memberships to qualify for an upgrade!": "<sasMark>EuroBonus {sasLevelName}</sasMark> and <scandicMark>{scandicLevelName}</scandicMark> are equally matched tiers. Level up in one of your memberships to qualify for an upgrade!", "<sasMark>EuroBonus {sasLevelName}</sasMark> and <scandicMark>{scandicLevelName}</scandicMark> are equally matched tiers. Level up in one of your memberships to qualify for an upgrade!": "<sasMark>EuroBonus {sasLevelName}</sasMark> and <scandicMark>{scandicLevelName}</scandicMark> are equally matched tiers. Level up in one of your memberships to qualify for an upgrade!",
"<sasMark>EuroBonus {sasLevelName}</sasMark> has upgraded your Scandic Friends level to <scandicMark>{scandicLevelName}</scandicMark>.": "<sasMark>EuroBonus {sasLevelName}</sasMark> has upgraded your Scandic Friends level to <scandicMark>{scandicLevelName}</scandicMark>.", "<sasMark>EuroBonus {sasLevelName}</sasMark> has upgraded your Scandic Friends level to <scandicMark>{scandicLevelName}</scandicMark>.": "<sasMark>EuroBonus {sasLevelName}</sasMark> has upgraded your Scandic Friends level to <scandicMark>{scandicLevelName}</scandicMark>.",
"<strikethrough>{amount}</strikethrough> <free>0 {currency}</free>/night per adult": "<strikethrough>{amount}</strikethrough> <free>0 {currency}</free>/natt per voksen", "<strikethrough>{amount}</strikethrough> <free>0 {currency}</free>/night per adult": "<strikethrough>{amount}</strikethrough> <free>0 {currency}</free>/natt per voksen",
"<strong>Booking code</strong>: {value}": "<strong>Bestillingskode</strong>: {value}",
"<strong>Status</strong> Paid": "<strong>Status</strong> Betalt", "<strong>Status</strong> Paid": "<strong>Status</strong> Betalt",
"<strong>Status</strong> Unpaid": "<strong>Status</strong> Ikke betalt", "<strong>Status</strong> Unpaid": "<strong>Status</strong> Ikke betalt",
"A destination or hotel name is needed to be able to search for a hotel room.": "Et reisemål eller hotellnavn er nødvendig for å kunne søke etter et hotellrom.", "A destination or hotel name is needed to be able to search for a hotel room.": "Et reisemål eller hotellnavn er nødvendig for å kunne søke etter et hotellrom.",

View File

@@ -9,6 +9,7 @@
"<sasMark>EuroBonus {sasLevelName}</sasMark> and <scandicMark>{scandicLevelName}</scandicMark> are equally matched tiers. Level up in one of your memberships to qualify for an upgrade!": "<sasMark>EuroBonus {sasLevelName}</sasMark> and <scandicMark>{scandicLevelName}</scandicMark> are equally matched tiers. Level up in one of your memberships to qualify for an upgrade!", "<sasMark>EuroBonus {sasLevelName}</sasMark> and <scandicMark>{scandicLevelName}</scandicMark> are equally matched tiers. Level up in one of your memberships to qualify for an upgrade!": "<sasMark>EuroBonus {sasLevelName}</sasMark> and <scandicMark>{scandicLevelName}</scandicMark> are equally matched tiers. Level up in one of your memberships to qualify for an upgrade!",
"<sasMark>EuroBonus {sasLevelName}</sasMark> has upgraded your Scandic Friends level to <scandicMark>{scandicLevelName}</scandicMark>.": "<sasMark>EuroBonus {sasLevelName}</sasMark> has upgraded your Scandic Friends level to <scandicMark>{scandicLevelName}</scandicMark>.", "<sasMark>EuroBonus {sasLevelName}</sasMark> has upgraded your Scandic Friends level to <scandicMark>{scandicLevelName}</scandicMark>.": "<sasMark>EuroBonus {sasLevelName}</sasMark> has upgraded your Scandic Friends level to <scandicMark>{scandicLevelName}</scandicMark>.",
"<strikethrough>{amount}</strikethrough> <free>0 {currency}</free>/night per adult": "<strikethrough>{amount}</strikethrough> <free>0 {currency}</free>/natt per vuxen", "<strikethrough>{amount}</strikethrough> <free>0 {currency}</free>/night per adult": "<strikethrough>{amount}</strikethrough> <free>0 {currency}</free>/natt per vuxen",
"<strong>Booking code</strong>: {value}": "<strong>Bokningskod</strong>: {value}",
"<strong>Status</strong> Paid": "<strong>Status</strong> Betalat", "<strong>Status</strong> Paid": "<strong>Status</strong> Betalat",
"<strong>Status</strong> Unpaid": "<strong>Status</strong> Ej betalat", "<strong>Status</strong> Unpaid": "<strong>Status</strong> Ej betalat",
"A destination or hotel name is needed to be able to search for a hotel room.": "Ett destinations- eller hotellnamn behövs för att kunna söka efter ett hotellrum.", "A destination or hotel name is needed to be able to search for a hotel room.": "Ett destinations- eller hotellnamn behövs för att kunna söka efter ett hotellrum.",

View File

@@ -33,6 +33,13 @@ export default function BookingConfirmationProvider({
const totalBookingPoints = rooms.reduce((acc, room) => { const totalBookingPoints = rooms.reduce((acc, room) => {
return acc + (room?.roomPoints ?? 0) return acc + (room?.roomPoints ?? 0)
}, 0) }, 0)
const totalBookingCheques = rooms.reduce((acc, room) => {
return acc + (room?.cheques ?? 0)
}, 0)
const totalBookingVouchers = rooms.reduce((acc, room) => {
return acc + (room?.vouchers ?? 0)
}, 0)
let isVatCurrency = true let isVatCurrency = true
if (totalBookingPoints) { if (totalBookingPoints) {
isVatCurrency = false isVatCurrency = false
@@ -43,6 +50,22 @@ export default function BookingConfirmationProvider({
totalBookingPrice, totalBookingPrice,
currencyCode currencyCode
) )
} else if (totalBookingCheques) {
isVatCurrency = false
formattedTotalCost = formatPrice(
intl,
totalBookingCheques,
CurrencyEnum.CC,
totalBookingPrice,
currencyCode
)
} else if (totalBookingVouchers) {
isVatCurrency = false
formattedTotalCost = formatPrice(
intl,
totalBookingVouchers,
CurrencyEnum.Voucher
)
} }
const initialData = { const initialData = {
bookingCode, bookingCode,
@@ -54,6 +77,7 @@ export default function BookingConfirmationProvider({
isVatCurrency, isVatCurrency,
formattedTotalCost, formattedTotalCost,
totalBookingPrice, totalBookingPrice,
totalBookingCheques,
} }
storeRef.current = createBookingConfirmationStore(initialData) storeRef.current = createBookingConfirmationStore(initialData)

View File

@@ -19,22 +19,24 @@ export function createBookingConfirmationStore(initialState: InitialState) {
formattedTotalCost: initialState.formattedTotalCost, formattedTotalCost: initialState.formattedTotalCost,
isVatCurrency: initialState.isVatCurrency, isVatCurrency: initialState.isVatCurrency,
totalBookingPrice: initialState.totalBookingPrice, totalBookingPrice: initialState.totalBookingPrice,
totalBookingCheques: initialState.totalBookingCheques,
actions: { actions: {
setRoom: (room, idx) => { setRoom: (room, idx) => {
set((state) => { set((state) => {
const rooms = [...state.rooms] const rooms = [...state.rooms]
rooms[idx] = room rooms[idx] = room
const totalBookingPrice = rooms.reduce((acc, room) => { const totalBookingPrice = rooms.reduce((acc, room) => {
return acc + (room?.totalPrice ?? 0) return acc + (room?.totalPrice ?? 0)
}, 0) }, 0)
const totalBookingCheques = rooms.reduce((acc, room) => {
return { rooms, totalBookingPrice } return acc + (room?.cheques ?? 0)
}, 0)
return { rooms, totalBookingPrice, totalBookingCheques }
}) })
}, },
setFormattedTotalCost: (updatedFormattedTotalCost: string) => { setFormattedTotalCost: (formattedTotalCost) => {
set((state) => { set(() => ({ formattedTotalCost }))
return { ...state, formattedTotalCost: updatedFormattedTotalCost }
})
}, },
}, },
})) }))

View File

@@ -14,7 +14,7 @@ export interface Room {
bedDescription: string bedDescription: string
breakfast?: PackageSchema breakfast?: PackageSchema
breakfastIncluded: boolean breakfastIncluded: boolean
children?: number cheques: number
childBedPreferences: ChildBedPreference[] childBedPreferences: ChildBedPreference[]
childrenAges?: number[] childrenAges?: number[]
confirmationNumber: string confirmationNumber: string
@@ -32,6 +32,7 @@ export interface Room {
totalPrice: number totalPrice: number
totalPriceExVat: number totalPriceExVat: number
vatAmount: number vatAmount: number
vouchers: number
} }
export interface InitialState { export interface InitialState {
@@ -44,20 +45,12 @@ export interface InitialState {
isVatCurrency: boolean isVatCurrency: boolean
formattedTotalCost: string formattedTotalCost: string
totalBookingPrice: number totalBookingPrice: number
totalBookingCheques: number
} }
export interface BookingConfirmationState { export interface BookingConfirmationState extends InitialState {
bookingCode: string | null
isVatCurrency: boolean
rooms: (Room | null)[]
currencyCode: string
vat: number
fromDate: Date
toDate: Date
formattedTotalCost: string | null
totalBookingPrice: number
actions: { actions: {
setRoom: (room: Room, idx: number) => void setRoom: (room: Room, idx: number) => void
setFormattedTotalCost: (updatedFormattedTotalCost: string) => void setFormattedTotalCost: (formattedTotalCost: string) => void
} }
} }

View File

@@ -34,7 +34,7 @@ export function formatPrice(
const localizedAdditionalPrice = intl.formatNumber(additionalPrice, { const localizedAdditionalPrice = intl.formatNumber(additionalPrice, {
minimumFractionDigits: 0, minimumFractionDigits: 0,
}) })
formattedAdditionalPrice = ` ${localizedAdditionalPrice} ${additionalPriceCurrency}` formattedAdditionalPrice = ` + ${localizedAdditionalPrice} ${additionalPriceCurrency}`
} }
return `${localizedPrice} ${currency}${formattedAdditionalPrice}` return `${localizedPrice} ${currency}${formattedAdditionalPrice}`