Merged in chore/cleanup-booking-flow (pull request #2824)

chore: Cleanup booking-flow after migration

* Remove unused types

* Clean up exports, types, unused files etc in booking-flow


Approved-by: Joakim Jäderberg
This commit is contained in:
Anton Gunnarsson
2025-09-18 07:28:05 +00:00
parent 9620bfe76d
commit b0f3e4afbd
44 changed files with 63 additions and 1483 deletions

View File

@@ -5,7 +5,17 @@ import { Typography } from "@scandic-hotels/design-system/Typography"
import styles from "./promo.module.css"
import type { PromoProps } from "@scandic-hotels/booking-flow/types/components/promo/promoProps"
type PromoProps = {
buttonText: string
href: string
text: string
title: string
image?: {
src: string
altText: string
altText_En: string
}
}
export default function Promo({
buttonText,

View File

@@ -1,5 +0,0 @@
import { createContext } from "react"
import type { RatesStore } from "@/types/contexts/rates"
export const RatesContext = createContext<RatesStore | null>(null)

View File

@@ -1,62 +0,0 @@
"use client"
import { usePathname, useSearchParams } from "next/navigation"
import { useMemo } from "react"
import { useIntl } from "react-intl"
import { createRatesStore } from "@/stores/select-rate"
import { RatesContext } from "@/contexts/Rates"
import type { RatesProviderProps } from "@/types/providers/rates"
export default function RatesProvider({
booking,
children,
hotelType,
roomCategories,
roomsAvailability,
vat,
}: RatesProviderProps) {
const pathname = usePathname()
const searchParams = useSearchParams()
const intl = useIntl()
const modifyRateIndex = searchParams.has("activeRoomIndex")
? Number(searchParams.get("activeRoomIndex"))
: undefined
const store = useMemo(() => {
return createRatesStore({
booking,
hotelType,
labels: {
accessibilityRoom: intl.formatMessage({
defaultMessage: "Accessible room",
}),
allergyRoom: intl.formatMessage({
defaultMessage: "Allergy-friendly room",
}),
petRoom: intl.formatMessage({
defaultMessage: "Pet-friendly room",
}),
},
pathname,
roomCategories,
roomsAvailability,
vat,
initialActiveRoom: modifyRateIndex,
})
}, [
booking,
hotelType,
intl,
pathname,
roomCategories,
roomsAvailability,
modifyRateIndex,
vat,
])
return <RatesContext.Provider value={store}>{children}</RatesContext.Provider>
}

View File

@@ -1,537 +0,0 @@
import { produce } from "immer"
import { useContext } from "react"
import { create, useStore } from "zustand"
import { BookingCodeFilterEnum } from "@scandic-hotels/booking-flow/stores/bookingCode-filter"
import { serializeBookingSearchParams } from "@scandic-hotels/booking-flow/utils/url"
import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking"
import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter"
import { AvailabilityEnum } from "@scandic-hotels/trpc/enums/selectHotel"
import { RatesContext } from "@/contexts/Rates"
import {
findDefaultCurrency,
findProductInRoom,
findSelectedRate,
} from "./helpers"
import type {
InitialState,
RatesState,
} from "@scandic-hotels/booking-flow/types/stores/rates"
import type { Package, Packages } from "@scandic-hotels/trpc/types/packages"
import type { PriceProduct } from "@scandic-hotels/trpc/types/roomAvailability"
export function createRatesStore({
booking,
hotelType,
labels,
pathname,
roomCategories,
roomsAvailability,
initialActiveRoom,
vat,
}: InitialState) {
function updateUrl(booking: RatesState["booking"], activeRoom: number = -1) {
const searchParams = serializeBookingSearchParams(booking, {
initialSearchParams: new URLSearchParams(window.location.search),
})
if (activeRoom >= 0) {
searchParams.set("modifyRateIndex", activeRoom.toString())
} else {
searchParams.delete("modifyRateIndex")
}
window.history.replaceState({}, "", `${pathname}?${searchParams}`)
}
const packageOptions = [
{
code: RoomPackageCodeEnum.ACCESSIBILITY_ROOM,
description: labels.accessibilityRoom,
},
{
code: RoomPackageCodeEnum.ALLERGY_ROOM,
description: labels.allergyRoom,
},
{
code: RoomPackageCodeEnum.PET_ROOM,
description: labels.petRoom,
},
]
const roomsPackages: NonNullable<Packages>[] = []
const roomConfigurations: RatesState["roomConfigurations"] = []
if (roomsAvailability) {
for (const availability of roomsAvailability) {
if ("error" in availability) {
// Availability request failed, default to empty array
roomConfigurations.push([])
roomsPackages.push([])
} else {
roomConfigurations.push(availability.roomConfigurations)
roomsPackages.push(availability.packages)
}
}
}
const rateSummary: RatesState["rateSummary"] = []
for (const [idx, room] of booking.rooms.entries()) {
if (room.rateCode && room.roomTypeCode) {
const roomConfiguration = roomConfigurations?.[idx]
const selectedRoom = findSelectedRate(
room.rateCode,
room.counterRateCode,
room.roomTypeCode,
roomConfiguration
)
if (!selectedRoom) {
booking.rooms[idx] = roomWithoutSelection(room)
updateUrl(booking)
continue
}
const product = findProductInRoom(
room.rateCode,
selectedRoom,
room.counterRateCode
)
if (product) {
const roomPackages = roomsPackages[idx].filter((pkg) =>
room.packages?.includes(pkg.code)
)
rateSummary[idx] = {
features: selectedRoom.features,
product,
packages: roomPackages,
rate: product.rate,
roomType: selectedRoom.roomType,
roomTypeCode: selectedRoom.roomTypeCode,
}
} else {
rateSummary[idx] = null
}
}
}
let activeRoom = rateSummary.length
if (initialActiveRoom !== undefined && initialActiveRoom >= 0) {
activeRoom = initialActiveRoom
} else if (rateSummary.length === booking.rooms.length) {
// Finds the first unselected room and sets that to active
// if no unselected rooms it will return -1 and close all rooms
const unselectedRoomIndex = rateSummary.findIndex((rate) => !rate)
activeRoom = unselectedRoomIndex
}
const isRedemptionBooking = booking.searchType === SEARCH_TYPE_REDEMPTION
const defaultCurrency = findDefaultCurrency(roomsAvailability)
return create<RatesState>()((set) => {
return {
activeRoom,
booking,
packageOptions,
hotelType,
isRedemptionBooking,
rateSummary,
roomConfigurations,
roomCategories,
roomsPackages,
roomsAvailability,
vat,
defaultCurrency,
rooms: booking.rooms.map((room, idx) => {
const roomConfiguration = roomConfigurations[idx]
const roomPackages = roomsPackages[idx]
const selectedPackages =
room.packages
?.map((code) => roomPackages.find((pkg) => pkg.code === code))
.filter((pkg): pkg is Package => Boolean(pkg)) ?? []
const selectedRate =
findSelectedRate(
room.rateCode,
room.counterRateCode,
room.roomTypeCode,
roomConfiguration
) ?? null
let product = null
if (selectedRate) {
product = findProductInRoom(
room.rateCode,
selectedRate,
room.counterRateCode
)
}
const bookingCode = room.rateCode
? room.bookingCode
: booking.bookingCode
const selectedFilter =
bookingCode && !isRedemptionBooking
? BookingCodeFilterEnum.Discounted
: BookingCodeFilterEnum.All
return {
actions: {
appendRegularRates(roomConfigurations) {
return set(
produce((state: RatesState) => {
state.rooms[idx].isFetchingAdditionalRate = false
if (roomConfigurations) {
const rooms = state.rooms[idx].rooms
const updatedRooms = rooms.map((currentRoom) => {
const incomingRoom = roomConfigurations.find(
(room) =>
room.roomType === currentRoom.roomType &&
room.roomTypeCode === currentRoom.roomTypeCode
)
if (incomingRoom) {
let campaign = currentRoom.campaign
if (incomingRoom.campaign.length) {
const newCampaign = [
...campaign,
...incomingRoom.campaign,
].reduce((cpns, cpn) => {
if (cpns.has(cpn.rateDefinition.rateCode)) {
return cpns
}
cpns.set(cpn.rateDefinition.rateCode, cpn)
return cpns
}, new Map<string, PriceProduct>())
campaign = Array.from(newCampaign.values())
}
const currentRoomAvailable =
currentRoom.status === AvailabilityEnum.Available
const incomingRoomAvailable =
incomingRoom.status === AvailabilityEnum.Available
let status = AvailabilityEnum.NotAvailable
if (currentRoomAvailable || incomingRoomAvailable) {
status = AvailabilityEnum.Available
}
return {
...currentRoom,
campaign,
products: [
...currentRoom.products,
...incomingRoom.products,
],
regular: incomingRoom.regular,
status,
}
}
return currentRoom
})
state.rooms[idx].rooms = updatedRooms
} else {
state.rooms[idx].rooms = []
}
})
)
},
closeSection() {
return set(
produce((state: RatesState) => {
if (state.rateSummary.length === state.booking.rooms.length) {
state.activeRoom = -1
} else {
state.activeRoom = idx + 1
}
})
)
},
modifyRate() {
return set(
produce((state: RatesState) => {
state.activeRoom = idx
})
)
},
removeSelectedPackage(code) {
return set(
produce((state: RatesState) => {
state.rooms[idx].isFetchingPackages = true
const filteredSelectedPackages = state.rooms[
idx
].selectedPackages.filter((c) => c.code !== code)
state.rooms[idx].selectedPackages = filteredSelectedPackages
if (
state.rooms[idx].selectedRate?.product.bookingCode ||
state.booking.bookingCode
) {
state.rooms[idx].selectedFilter =
BookingCodeFilterEnum.Discounted
}
if (filteredSelectedPackages.length) {
state.booking.rooms[idx].packages =
filteredSelectedPackages.map((pkg) => pkg.code)
} else {
state.booking.rooms[idx].packages = null
}
updateUrl(state.booking, state.activeRoom)
})
)
},
removeSelectedPackages() {
return set(
produce((state: RatesState) => {
state.rooms[idx].isFetchingPackages = true
state.rooms[idx].selectedPackages = []
if (
state.rooms[idx].selectedRate?.product.bookingCode ||
state.booking.bookingCode
) {
state.rooms[idx].selectedFilter =
BookingCodeFilterEnum.Discounted
}
state.booking.rooms[idx].packages = null
updateUrl(state.booking, state.activeRoom)
})
)
},
selectFilter(filter) {
return set(
produce((state: RatesState) => {
state.rooms[idx].selectedFilter = filter
state.rooms[idx].isFetchingAdditionalRate =
filter === BookingCodeFilterEnum.All
})
)
},
selectRate(selectedRate, isUserLoggedIn) {
return set(
produce((state: RatesState) => {
if (!selectedRate.product) {
return
}
state.rooms[idx].selectedRate = selectedRate
state.rateSummary[idx] = {
features: selectedRate.features,
packages: state.rooms[idx].selectedPackages,
product: selectedRate.product,
rate: selectedRate.product.rate,
roomType: selectedRate.roomType,
roomTypeCode: selectedRate.roomTypeCode,
}
const roomNr = idx + 1
const isMainRoom = roomNr === 1
let productRateCode = ""
if ("corporateCheque" in selectedRate.product) {
productRateCode =
selectedRate.product.corporateCheque.rateCode
}
if ("redemption" in selectedRate.product) {
productRateCode = selectedRate.product.redemption.rateCode
}
if ("voucher" in selectedRate.product) {
productRateCode = selectedRate.product.voucher.rateCode
}
if (
"public" in selectedRate.product &&
selectedRate.product.public
) {
productRateCode = selectedRate.product.public.rateCode
}
let hasMemberRate = false
let memberRateCode = ""
if (
"member" in selectedRate.product &&
selectedRate.product.member
) {
hasMemberRate = true
memberRateCode = selectedRate.product.member.rateCode
}
const isMemberRate =
isUserLoggedIn && isMainRoom && hasMemberRate
state.rooms[idx].bookingRoom.rateCode = isMemberRate
? memberRateCode
: productRateCode
if (!isMemberRate && hasMemberRate) {
state.rooms[idx].bookingRoom.counterRateCode =
memberRateCode
}
state.rooms[idx].bookingRoom.roomTypeCode =
selectedRate.roomTypeCode
state.rooms[idx].bookingRoom.bookingCode =
selectedRate.product.bookingCode
const counterratecode = isMemberRate
? productRateCode
: memberRateCode
if (counterratecode) {
state.booking.rooms[idx].counterRateCode = counterratecode
} else {
state.booking.rooms[idx].counterRateCode = null
}
const rateCode = isMemberRate
? memberRateCode
: productRateCode
if (rateCode) {
state.booking.rooms[idx].rateCode = rateCode
}
if (selectedRate.product.bookingCode) {
state.booking.rooms[idx].bookingCode =
selectedRate.product.bookingCode
} else {
if (state.booking.rooms[idx].bookingCode) {
state.booking.rooms[idx].bookingCode = null
}
}
state.booking.rooms[idx].roomTypeCode =
selectedRate.roomTypeCode
if (state.rateSummary.length === state.booking.rooms.length) {
state.activeRoom = -1
} else {
state.activeRoom = idx + 1
}
updateUrl(state.booking)
})
)
},
selectPackages(selectedPackages) {
return set(
produce((state: RatesState) => {
state.rooms[idx].isFetchingPackages = true
const pkgs = state.roomsPackages[idx].filter((pkg) =>
selectedPackages.includes(pkg.code)
)
state.rooms[idx].selectedPackages = pkgs
if (
state.rooms[idx].selectedRate?.product.bookingCode ||
state.booking.bookingCode
) {
state.rooms[idx].selectedFilter =
BookingCodeFilterEnum.Discounted
}
if (selectedPackages.length) {
state.booking.rooms[idx].packages = selectedPackages
} else {
state.booking.rooms[idx].packages = null
}
updateUrl(state.booking, state.activeRoom)
})
)
},
updateRooms(rooms) {
return set(
produce((state: RatesState) => {
state.rooms[idx].isFetchingPackages = false
if (rooms) {
state.rooms[idx].rooms = rooms
const rateSummaryRoom = state.rateSummary[idx]
if (rateSummaryRoom) {
const room = state.rooms[idx].bookingRoom
const selectedRoom = findSelectedRate(
room.rateCode,
room.counterRateCode,
room.roomTypeCode,
rooms
)
if (selectedRoom) {
rateSummaryRoom.packages =
state.rooms[idx].selectedPackages
} else {
state.booking.rooms[idx] = roomWithoutSelection(
state.booking.rooms[idx]
)
state.rateSummary[idx] = null
state.rooms[idx].selectedRate = null
updateUrl(state.booking)
}
}
} else {
state.rooms[idx].rooms = []
if (state.rateSummary[idx]) {
state.booking.rooms[idx] = roomWithoutSelection(
state.booking.rooms[idx]
)
state.rateSummary[idx] = null
state.rooms[idx].selectedRate = null
updateUrl(state.booking)
}
}
})
)
},
},
bookingRoom: room,
isFetchingAdditionalRate: false,
isFetchingPackages: false,
rooms: roomConfiguration,
selectedFilter,
selectedPackages,
selectedRate:
selectedRate && product
? {
features: selectedRate.features,
packages: selectedPackages,
product,
roomType: selectedRate.roomType,
roomTypeCode: selectedRate.roomTypeCode,
}
: null,
}
}),
}
})
}
export function useRatesStore<T>(selector: (store: RatesState) => T) {
const store = useContext(RatesContext)
if (!store) {
throw new Error("useRatesStore must be used within RatesProvider")
}
return useStore(store, selector)
}
function roomWithoutSelection(
room: RatesState["booking"]["rooms"][number]
): RatesState["booking"]["rooms"][number] {
return {
...room,
rateCode: null,
counterRateCode: null,
roomTypeCode: null,
bookingCode: null,
}
}

View File

@@ -1,6 +1,3 @@
import type { BookingSearchType } from "@scandic-hotels/booking-flow/searchType"
import type { Child } from "@scandic-hotels/trpc/types/child"
import type { PackageEnum } from "@scandic-hotels/trpc/types/packages"
import type { Product } from "@scandic-hotels/trpc/types/roomAvailability"
import type { Price } from "../price"
@@ -11,21 +8,3 @@ export interface RoomPrice {
}
export type RoomRate = Product
export type DetailsBooking = {
hotelId: string
fromDate: string
toDate: string
city?: string
bookingCode?: string
searchType?: BookingSearchType
rooms: {
adults: number
rateCode: string
roomTypeCode: string
bookingCode?: string
childrenInRoom?: Child[]
counterRateCode?: string
packages?: PackageEnum[]
}[]
}

View File

@@ -1,3 +0,0 @@
import type { createRatesStore } from "@/stores/select-rate"
export type RatesStore = ReturnType<typeof createRatesStore>

View File

@@ -1,7 +0,0 @@
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { BreakfastPackages } from "@scandic-hotels/trpc/routers/hotels/output"
import type { RoomCategories } from "@scandic-hotels/trpc/types/hotel"
import type { Room } from "@scandic-hotels/trpc/types/room"
import type { SafeUser } from "@/types/user"
import type { DetailsBooking } from "../components/hotelReservation/enterDetails/details"

View File

@@ -17,7 +17,7 @@ import type { BookingConfirmation } from "@scandic-hotels/trpc/types/bookingConf
import type { EventAttributes } from "ics"
import type { MutableRefObject } from "react"
export interface BookingConfirmationHeaderProps
interface BookingConfirmationHeaderProps
extends Pick<BookingConfirmation, "booking" | "hotel"> {
mainRef: MutableRefObject<HTMLElement | null>
}

View File

@@ -4,7 +4,17 @@ import { Typography } from "@scandic-hotels/design-system/Typography"
import styles from "./promo.module.css"
import type { PromoProps } from "../../../../types/components/promo/promoProps"
type PromoProps = {
buttonText: string
href: string
text: string
title: string
image?: {
src: string
altText: string
altText_En: string
}
}
export function Promo({ buttonText, href, text, title }: PromoProps) {
return (

View File

@@ -13,7 +13,7 @@ import type { BookingConfirmation } from "@scandic-hotels/trpc/types/bookingConf
import type { AdditionalInfoCookieValue } from "../../../types/components/findMyBooking/additionalInfoCookieValue"
export type PromosProps = Pick<BookingConfirmation, "booking">
type PromosProps = Pick<BookingConfirmation, "booking">
export function Promos({ booking }: PromosProps) {
const intl = useIntl()

View File

@@ -7,7 +7,7 @@ import { Typography } from "@scandic-hotels/design-system/Typography"
import styles from "./retry.module.css"
export interface RetryProps {
interface RetryProps {
handleRefetch: () => void
}

View File

@@ -14,7 +14,7 @@ import { Room } from "../Room"
import { LinkedReservationCardSkeleton } from "./LinkedReservationCardSkeleton"
import Retry from "./Retry"
export interface LinkedReservationProps {
interface LinkedReservationProps {
checkInTime: string
checkOutTime: string
refId: string

View File

@@ -22,7 +22,7 @@ import styles from "./room.module.css"
import type { BookingConfirmation } from "@scandic-hotels/trpc/types/bookingConfirmation"
export interface RoomProps {
interface RoomProps {
booking: BookingConfirmation["booking"]
checkInTime: string
checkOutTime: string

View File

@@ -12,7 +12,7 @@ import styles from "./rooms.module.css"
import type { BookingConfirmation } from "@scandic-hotels/trpc/types/bookingConfirmation"
import type { Room as RoomProp } from "@scandic-hotels/trpc/types/hotel"
export interface BookingConfirmationRoomsProps
interface BookingConfirmationRoomsProps
extends Pick<BookingConfirmation, "booking"> {
mainRoom: RoomProp & {
bedType: RoomProp["roomTypes"][number]

View File

@@ -16,7 +16,7 @@ export const bookingWidgetErrors = {
"CODE_VOUCHER_REWARD_NIGHT_UNAVAILABLE",
} as const
export const guestRoomSchema = z
const guestRoomSchema = z
.object({
adults: z.number().default(1),
childrenInRoom: z
@@ -45,7 +45,7 @@ export const guestRoomSchema = z
}
})
export const guestRoomsSchema = z.array(guestRoomSchema)
const guestRoomsSchema = z.array(guestRoomSchema)
export const bookingCodeSchema = z
.object({

View File

@@ -15,7 +15,7 @@ import useLang from "../../../../../hooks/useLang"
import styles from "./joinScandicFriendsCard.module.css"
export type JoinScandicFriendsCardProps = {
type JoinScandicFriendsCardProps = {
name?: string
}
export default function JoinScandicFriendsCard({

View File

@@ -12,7 +12,7 @@ const stringMatcher =
const isValidString = (key: string) => stringMatcher.test(key)
export const baseDetailsSchema = z.object({
const baseDetailsSchema = z.object({
countryCode: z.string().min(1, roomOneErrors.COUNTRY_REQUIRED),
email: z.string().email(roomOneErrors.EMAIL_REQUIRED),
firstName: z
@@ -31,7 +31,7 @@ export const baseDetailsSchema = z.object({
specialRequest: specialRequestSchema,
})
export const notJoinDetailsSchema = baseDetailsSchema.merge(
const notJoinDetailsSchema = baseDetailsSchema.merge(
z.object({
join: z.literal<boolean>(false),
zipCode: z.string().optional(),
@@ -54,7 +54,7 @@ export const notJoinDetailsSchema = baseDetailsSchema.merge(
})
)
export const joinDetailsSchema = baseDetailsSchema.merge(
const joinDetailsSchema = baseDetailsSchema.merge(
z.object({
join: z.literal<boolean>(true),
zipCode: z

View File

@@ -1,11 +1,11 @@
import { z } from "zod"
export enum FloorPreference {
enum FloorPreference {
LOW = "Low floor",
HIGH = "High floor",
}
export enum ElevatorPreference {
enum ElevatorPreference {
AWAY_FROM_ELEVATOR = "Away from elevator",
NEAR_ELEVATOR = "Near elevator",
}

View File

@@ -12,7 +12,7 @@ import styles from "./header.module.css"
import type { HotelData } from "@scandic-hotels/trpc/types/hotel"
export type HotelHeaderProps = {
type HotelHeaderProps = {
hotelData: HotelData & { url: string | null }
}

View File

@@ -63,7 +63,7 @@ import type { PriceChangeData } from "../PriceChangeData"
const maxRetries = 15
const retryInterval = 2000
export type PaymentClientProps = {
type PaymentClientProps = {
otherPaymentOptions: PaymentMethodEnum[]
savedCreditCards: CreditCard[] | null
}

View File

@@ -9,7 +9,7 @@ import { EnterDetailsStepEnum } from "../../../stores/enter-details/enterDetails
import styles from "./section.module.css"
export type SectionProps = {
type SectionProps = {
header: string
label: string
additionalInfo?: string | null

View File

@@ -11,15 +11,3 @@ export function getMemberPrice(roomRate: Product) {
return null
}
export function getPublicPrice(roomRate: Product) {
if ("public" in roomRate && roomRate.public) {
return {
amount: roomRate.public.localPrice.pricePerStay,
currency: roomRate.public.localPrice.currency,
pricePerNight: roomRate.public.localPrice.pricePerNight,
}
}
return null
}

View File

@@ -38,7 +38,7 @@ type RoomPrice =
| RedemptionPriceType
| VoucherPriceType
export interface Room {
interface Room {
adults: number
bedType:
| {

View File

@@ -1,2 +0,0 @@
export { default as FilterAndSortModal } from "./FilterAndSortModal"
export { default as HotelFilter } from "./HotelFilter"

View File

@@ -1,7 +1,7 @@
import type { HotelPin } from "../../../HotelCardDialogListing/utils"
import type { HotelResponse } from "../../helpers"
export function getVisibleHotelPins(
function getVisibleHotelPins(
map: google.maps.Map | null,
filteredHotelPins: HotelPin[]
) {

View File

@@ -23,7 +23,7 @@ import styles from "./summaryContent.module.css"
import type { Price } from "../../../../../../types/price"
export type SelectRateSummaryProps = {
type SelectRateSummaryProps = {
isUserLoggedIn: boolean
bookingCode?: string
toggleSummaryOpen: () => void

View File

@@ -1,115 +0,0 @@
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import type { Packages } from "@scandic-hotels/trpc/types/packages"
import type {
Rate,
Room,
} from "../../../../../types/components/selectRate/selectRate"
import type { Price } from "../../../../../types/price"
export function mapRate(
room: Rate,
index: number,
bookingRooms: Room[],
packages: NonNullable<Packages>
) {
const rate = {
adults: bookingRooms[index].adults,
cancellationText: room.product.rateDefinition?.cancellationText ?? "",
childrenInRoom: bookingRooms[index].childrenInRoom ?? undefined,
rateDetails: room.product.rateDefinition?.generalTerms,
roomPrice: {
currency: CurrencyEnum.Unknown,
perNight: <Price>{
local: {
currency: CurrencyEnum.Unknown,
price: 0,
},
requested: undefined,
},
perStay: <Price>{
local: {
currency: CurrencyEnum.Unknown,
price: 0,
},
requested: undefined,
},
},
roomRate: room.product,
roomType: room.roomType,
packages,
}
if ("corporateCheque" in room.product) {
rate.roomPrice.currency = CurrencyEnum.CC
rate.roomPrice.perNight.local = {
currency: CurrencyEnum.CC,
price: room.product.corporateCheque.localPrice.numberOfCheques,
additionalPrice:
room.product.corporateCheque.localPrice.additionalPricePerStay,
additionalPriceCurrency:
room.product.corporateCheque.localPrice.currency ??
CurrencyEnum.Unknown,
}
rate.roomPrice.perStay.local = {
currency: CurrencyEnum.CC,
price: room.product.corporateCheque.localPrice.numberOfCheques,
additionalPrice:
room.product.corporateCheque.localPrice.additionalPricePerStay,
additionalPriceCurrency:
room.product.corporateCheque.localPrice.currency ??
CurrencyEnum.Unknown,
}
} else if ("redemption" in room.product) {
rate.roomPrice.currency = CurrencyEnum.POINTS
rate.roomPrice.perNight.local = {
currency: CurrencyEnum.POINTS,
price: room.product.redemption.localPrice.pointsPerNight,
additionalPrice:
room.product.redemption.localPrice.additionalPricePerStay,
additionalPriceCurrency:
room.product.redemption.localPrice.currency ?? CurrencyEnum.Unknown,
}
rate.roomPrice.perStay.local = {
currency: CurrencyEnum.POINTS,
price: room.product.redemption.localPrice.pointsPerStay,
additionalPrice:
room.product.redemption.localPrice.additionalPricePerStay,
additionalPriceCurrency:
room.product.redemption.localPrice.currency ?? CurrencyEnum.Unknown,
}
} else if ("voucher" in room.product) {
rate.roomPrice.currency = CurrencyEnum.Voucher
rate.roomPrice.perNight.local = {
currency: CurrencyEnum.Voucher,
price: room.product.voucher.numberOfVouchers,
}
rate.roomPrice.perStay.local = {
currency: CurrencyEnum.Voucher,
price: room.product.voucher.numberOfVouchers,
}
} else {
const currency =
room.product.public?.localPrice.currency ||
room.product.member?.localPrice.currency ||
CurrencyEnum.Unknown
rate.roomPrice.currency = currency
rate.roomPrice.perNight.local = {
currency,
price:
room.product.public?.localPrice.pricePerNight ||
room.product.member?.localPrice.pricePerNight ||
0,
}
rate.roomPrice.perStay.local = {
currency,
price:
room.product.public?.localPrice.pricePerStay ||
room.product.member?.localPrice.pricePerStay ||
0,
}
}
return rate
}

View File

@@ -1,243 +1,6 @@
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import { RateTypeEnum } from "@scandic-hotels/common/constants/rateType"
import { sumPackages } from "../../../../utils/SelectRate"
import type { Packages } from "@scandic-hotels/trpc/types/packages"
import type {
Product,
RedemptionProduct,
} from "@scandic-hotels/trpc/types/roomAvailability"
import type { Rate } from "../../../../types/components/selectRate/selectRate"
import type { Price } from "../../../../types/price"
export function calculateTotalPrice(
selectedRateSummary: Rate[],
isUserLoggedIn: boolean
) {
return selectedRateSummary.reduce<Price>(
(total, room, idx) => {
if (!("member" in room.product) || !("public" in room.product)) {
return total
}
const roomNr = idx + 1
const isMainRoom = roomNr === 1
let rate
if (isUserLoggedIn && isMainRoom && room.product.member) {
rate = room.product.member
} else if (room.product.public) {
rate = room.product.public
}
if (!rate) {
return total
}
const packagesPrice = room.packages.reduce(
(total, pkg) => {
total.local = total.local + pkg.localPrice.totalPrice
if (pkg.requestedPrice.totalPrice) {
total.requested = total.requested + pkg.requestedPrice.totalPrice
}
return total
},
{ local: 0, requested: 0 }
)
total.local.currency = rate.localPrice.currency
total.local.price =
total.local.price + rate.localPrice.pricePerStay + packagesPrice.local
if (rate.localPrice.regularPricePerStay) {
total.local.regularPrice =
(total.local.regularPrice || 0) +
rate.localPrice.regularPricePerStay +
packagesPrice.local
}
if (rate.requestedPrice) {
if (!total.requested) {
total.requested = {
currency: rate.requestedPrice.currency,
price: 0,
}
}
if (!total.requested.currency) {
total.requested.currency = rate.requestedPrice.currency
}
total.requested.price =
total.requested.price +
rate.requestedPrice.pricePerStay +
packagesPrice.requested
if (rate.requestedPrice.regularPricePerStay) {
total.requested.regularPrice =
(total.requested.regularPrice || 0) +
rate.requestedPrice.regularPricePerStay +
packagesPrice.requested
}
}
return total
},
{
local: {
currency: CurrencyEnum.Unknown,
price: 0,
regularPrice: undefined,
},
requested: undefined,
}
)
}
export function calculateRedemptionTotalPrice(
redemption: RedemptionProduct["redemption"],
packages: Packages | null
) {
const pkgsSum = sumPackages(packages)
let additionalPrice
if (redemption.localPrice.additionalPricePerStay) {
additionalPrice =
redemption.localPrice.additionalPricePerStay + pkgsSum.price
} else if (pkgsSum.price) {
additionalPrice = pkgsSum.price
}
let additionalPriceCurrency
if (redemption.localPrice.currency) {
additionalPriceCurrency = redemption.localPrice.currency
} else if (pkgsSum.currency) {
additionalPriceCurrency = pkgsSum.currency
}
return {
local: {
additionalPrice,
additionalPriceCurrency,
currency: CurrencyEnum.POINTS,
price: redemption.localPrice.pointsPerStay,
},
}
}
export function calculateVoucherPrice(selectedRateSummary: Rate[]) {
return selectedRateSummary.reduce<Price>(
(total, room) => {
if (!("voucher" in room.product)) {
return total
}
const rate = room.product.voucher
total.local.price = total.local.price + rate.numberOfVouchers
const pkgsSum = sumPackages(room.packages)
if (pkgsSum.price && pkgsSum.currency) {
total.local.additionalPrice =
(total.local.additionalPrice || 0) + pkgsSum.price
total.local.additionalPriceCurrency = pkgsSum.currency
}
return total
},
{
local: {
currency: CurrencyEnum.Voucher,
price: 0,
},
requested: undefined,
}
)
}
export function calculateCorporateChequePrice(selectedRateSummary: Rate[]) {
return selectedRateSummary.reduce<Price>(
(total, room) => {
if (!("corporateCheque" in room.product)) {
return total
}
const rate = room.product.corporateCheque
const pkgsSum = sumPackages(room.packages)
total.local.price = total.local.price + rate.localPrice.numberOfCheques
if (rate.localPrice.additionalPricePerStay) {
total.local.additionalPrice =
(total.local.additionalPrice || 0) +
rate.localPrice.additionalPricePerStay +
pkgsSum.price
} else if (pkgsSum.price) {
total.local.additionalPrice =
(total.local.additionalPrice || 0) + pkgsSum.price
}
if (rate.localPrice.currency) {
total.local.additionalPriceCurrency = rate.localPrice.currency
}
if (rate.requestedPrice) {
if (!total.requested) {
total.requested = {
currency: CurrencyEnum.CC,
price: 0,
}
}
total.requested.price =
total.requested.price + rate.requestedPrice.numberOfCheques
if (rate.requestedPrice.additionalPricePerStay) {
total.requested.additionalPrice =
(total.requested.additionalPrice || 0) +
rate.requestedPrice.additionalPricePerStay
}
if (rate.requestedPrice.currency) {
total.requested.additionalPriceCurrency = rate.requestedPrice.currency
}
}
return total
},
{
local: {
currency: CurrencyEnum.CC,
price: 0,
},
requested: undefined,
}
)
}
export function getTotalPrice(
mainRoomProduct: Rate | null,
rateSummary: Array<Rate | null>,
isUserLoggedIn: boolean
): Price | null {
const summaryArray = rateSummary.filter((rate): rate is Rate => rate !== null)
if (summaryArray.some((rate) => "corporateCheque" in rate.product)) {
return calculateCorporateChequePrice(summaryArray)
}
if (!mainRoomProduct) {
return calculateTotalPrice(summaryArray, isUserLoggedIn)
}
const { packages, product } = mainRoomProduct
// In case of reward night (redemption) or voucher only single room booking is supported by business rules
if ("redemption" in product) {
return calculateRedemptionTotalPrice(product.redemption, packages)
}
if ("voucher" in product) {
const voucherPrice = calculateVoucherPrice(summaryArray)
return voucherPrice
}
return calculateTotalPrice(summaryArray, isUserLoggedIn)
}
import type { Product } from "@scandic-hotels/trpc/types/roomAvailability"
export function isBookingCodeRate(product: Product | undefined | null) {
if (!product) return false

View File

@@ -1,124 +0,0 @@
.bookingCodeFilter {
display: flex;
justify-content: flex-end;
}
.dialog {
border-radius: var(--Corner-radius-md);
background-color: var(--Surface-Primary-Default);
box-shadow: var(--popup-box-shadow);
max-width: 340px;
}
.radioGroup {
display: grid;
gap: var(--Space-x1);
padding: 0;
}
.radio {
padding: var(--Space-x1);
}
.radio[data-hovered] {
cursor: pointer;
}
.radio[data-focus-visible]::before {
outline: 1px auto var(--Border-Interactive-Focus);
}
.radio {
display: flex;
align-items: center;
}
.radio::before {
flex-shrink: 0;
content: "";
margin-right: var(--Space-x15);
background-color: var(--Surface-UI-Fill-Default);
width: 24px;
height: 24px;
border-radius: 50%;
box-shadow: inset 0 0 0 2px var(--Base-Border-Normal);
}
.radio[data-selected]::before {
box-shadow: inset 0 0 0 8px var(--Surface-UI-Fill-Active);
}
.modalOverlay {
position: fixed;
inset: 0;
background-color: var(--Overlay-40);
&[data-entering] {
animation: overlay-fade 200ms;
}
&[data-exiting] {
animation: overlay-fade 150ms reverse ease-in;
}
}
.modal {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: var(--Space-x2) var(--Space-x05);
border-radius: var(--Corner-radius-md) var(--Corner-radius-md) 0 0;
background-color: var(--Surface-Primary-Default);
box-shadow: 0px 0px 14px 6px rgba(0, 0, 0, 0.1);
&[data-entering] {
animation: modal-anim 200ms;
}
&[data-exiting] {
animation: modal-anim 150ms reverse ease-in;
}
}
.modalDialog {
display: grid;
gap: var(--Space-x2);
padding: 0 var(--Space-x1);
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 var(--Space-x1);
}
@media screen and (min-width: 768px) {
.radioGroup {
padding: var(--Space-x1);
}
.modalOverlay {
display: none;
}
}
@keyframes overlay-fade {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes modal-anim {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
}

View File

@@ -1,199 +0,0 @@
"use client"
import { useState } from "react"
import {
Dialog,
DialogTrigger,
Modal,
ModalOverlay,
Popover,
Radio,
RadioGroup,
} from "react-aria-components"
import { useIntl } from "react-intl"
import { RateTypeEnum } from "@scandic-hotels/common/constants/rateType"
import { ChipButton } from "@scandic-hotels/design-system/ChipButton"
import { IconButton } from "@scandic-hotels/design-system/IconButton"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { useSelectRateContext } from "../../../../../../contexts/SelectRate/SelectRateContext"
import { useBreakpoint } from "../../../../../../hooks/useBreakpoint"
import { BookingCodeFilterEnum } from "../../../../../../stores/bookingCode-filter"
import styles from "./bookingCodeFilter.module.css"
export function BookingCodeFilter({ roomIndex }: { roomIndex: number }) {
const intl = useIntl()
const [isOpen, setIsOpen] = useState(false)
const displayAsModal = useBreakpoint("mobile")
const {
input,
getAvailabilityForRoom,
bookingCodeFilter,
actions: { selectBookingCodeFilter },
} = useSelectRateContext()
const roomAvailability = getAvailabilityForRoom(roomIndex)
const bookingCodeFilterItems = [
{
label: intl.formatMessage({
defaultMessage: "Booking code rates",
}),
value: BookingCodeFilterEnum.Discounted,
},
{
label: intl.formatMessage({
defaultMessage: "All rates",
}),
value: BookingCodeFilterEnum.All,
},
]
async function updateFilterValue(selectedFilter: string) {
selectBookingCodeFilter(selectedFilter as BookingCodeFilterEnum)
}
const hideFilter = (roomAvailability ?? []).some((room) => {
room.products.some((product) => {
const isRedemption = Array.isArray(product)
if (isRedemption) {
return true
}
switch (product.rateDefinition.rateType) {
case RateTypeEnum.Arb:
case RateTypeEnum.CorporateCheque:
case RateTypeEnum.Voucher:
return true
default:
return false
}
})
})
if (hideFilter || !input?.bookingCode) {
return null
}
return (
<>
<div className={styles.bookingCodeFilter}>
<DialogTrigger isOpen={isOpen} onOpenChange={setIsOpen}>
<ChipButton variant="Outlined">
{
bookingCodeFilterItems.find(
(item) => item.value === bookingCodeFilter
)?.label
}
<MaterialIcon
icon="keyboard_arrow_down"
size={20}
color="CurrentColor"
/>
</ChipButton>
{!displayAsModal ? (
<Popover placement="bottom end" isNonModal>
<Dialog className={styles.dialog}>
{({ close }) => {
function handleChangeFilterValue(value: string) {
updateFilterValue(value)
close()
}
return (
<Typography variant="Body/Paragraph/mdRegular">
<RadioGroup
aria-label={intl.formatMessage({
defaultMessage: "Booking Code Filter",
})}
onChange={handleChangeFilterValue}
name="bookingCodeFilter"
value={bookingCodeFilter}
className={styles.radioGroup}
>
{bookingCodeFilterItems.map((item) => (
<Radio
aria-label={item.label}
key={item.value}
value={item.value}
className={styles.radio}
autoFocus={bookingCodeFilter === item.value}
>
{item.label}
</Radio>
))}
</RadioGroup>
</Typography>
)
}}
</Dialog>
</Popover>
) : (
<ModalOverlay className={styles.modalOverlay} isDismissable>
<Modal className={styles.modal}>
<Dialog className={styles.modalDialog}>
{({ close }) => {
function handleChangeFilterValue(value: string) {
updateFilterValue(value)
close()
}
return (
<>
<div className={styles.header}>
<Typography variant="Title/Subtitle/md">
<h3>
{intl.formatMessage({
defaultMessage: "Room rates",
})}
</h3>
</Typography>
<IconButton
theme="Black"
style="Muted"
onPress={() => {
close()
}}
>
<MaterialIcon
icon="close"
size={24}
color="CurrentColor"
/>
</IconButton>
</div>
<Typography variant="Body/Paragraph/mdRegular">
<RadioGroup
aria-label={intl.formatMessage({
defaultMessage: "Booking Code Filter",
})}
onChange={handleChangeFilterValue}
name="bookingCodeFilter"
value={bookingCodeFilter}
className={styles.radioGroup}
>
{bookingCodeFilterItems.map((item) => (
<Radio
aria-label={item.label}
key={item.value}
value={item.value}
className={styles.radio}
>
{item.label}
</Radio>
))}
</RadioGroup>
</Typography>
</>
)
}}
</Dialog>
</Modal>
</ModalOverlay>
)}
</DialogTrigger>
</div>
</>
)
}

View File

@@ -80,21 +80,21 @@ export function PackageCheckboxes({
)
}
export function includesAllergyRoom(codes: PackageEnum[]) {
function includesAllergyRoom(codes: PackageEnum[]) {
return codes.includes(RoomPackageCodeEnum.ALLERGY_ROOM)
}
export function includesPetRoom(codes: PackageEnum[]) {
function includesPetRoom(codes: PackageEnum[]) {
return codes.includes(RoomPackageCodeEnum.PET_ROOM)
}
export function checkIsAllergyRoom(
function checkIsAllergyRoom(
code: PackageEnum
): code is RoomPackageCodeEnum.ALLERGY_ROOM {
return code === RoomPackageCodeEnum.ALLERGY_ROOM
}
export function checkIsPetRoom(
function checkIsPetRoom(
code: PackageEnum
): code is RoomPackageCodeEnum.PET_ROOM {
return code === RoomPackageCodeEnum.PET_ROOM

View File

@@ -14,7 +14,7 @@ import type { Package } from "@scandic-hotels/trpc/types/packages"
import type { AvailabilityWithRoomInfo } from "../../../../../../../contexts/SelectRate/types"
export interface RatesProps {
interface RatesProps {
roomConfiguration: AvailabilityWithRoomInfo
roomIndex: number
selectedPackages: Package[]

View File

@@ -1,68 +0,0 @@
import type {
CorporateChequeProduct,
PriceProduct,
VoucherProduct,
} from "@scandic-hotels/trpc/types/roomAvailability"
import type { SelectedRate } from "../../../../../../../types/stores/rates"
export function isSelectedPriceProduct(
product: PriceProduct,
selectedRate: SelectedRate | null,
roomTypeCode: string
) {
if (!selectedRate || roomTypeCode !== selectedRate.roomTypeCode) {
return false
}
const { member, public: standard } = product
let isSelected = false
if (
"member" in selectedRate.product &&
selectedRate.product.member &&
member
) {
isSelected = selectedRate.product.member.rateCode === member.rateCode
}
if (
"public" in selectedRate.product &&
selectedRate.product.public &&
standard
) {
isSelected = selectedRate.product.public.rateCode === standard.rateCode
}
return isSelected
}
export function isSelectedCorporateCheque(
product: CorporateChequeProduct,
selectedRate: SelectedRate | null,
roomTypeCode: string
) {
if (!selectedRate || !("corporateCheque" in selectedRate.product)) {
return false
}
const isSameRateCode =
product.corporateCheque.rateCode ===
selectedRate.product.corporateCheque.rateCode
const isSameRoomTypeCode = selectedRate.roomTypeCode === roomTypeCode
return isSameRateCode && isSameRoomTypeCode
}
export function isSelectedVoucher(
product: VoucherProduct,
selectedRate: SelectedRate | null,
roomTypeCode: string
) {
if (!selectedRate || !("voucher" in selectedRate.product)) {
return false
}
const isSameRateCode =
product.voucher.rateCode === selectedRate.product.voucher.rateCode
const isSameRoomTypeCode = selectedRate.roomTypeCode === roomTypeCode
return isSameRateCode && isSameRoomTypeCode
}

View File

@@ -17,7 +17,7 @@ import type { ApiImage } from "@scandic-hotels/trpc/types/hotel"
import type { PackageEnum } from "@scandic-hotels/trpc/types/packages"
import type { RoomConfiguration } from "@scandic-hotels/trpc/types/roomAvailability"
export type RoomListItemImageProps = Pick<
type RoomListItemImageProps = Pick<
RoomConfiguration,
"roomType" | "roomTypeCode" | "roomsLeft"
> & {

View File

@@ -12,7 +12,7 @@ import type { Package } from "@scandic-hotels/trpc/types/packages"
import type { AvailabilityWithRoomInfo } from "../../../../../../contexts/SelectRate/types"
export type RoomListItemProps = {
type RoomListItemProps = {
room: AvailabilityWithRoomInfo
selectedPackages: Package[]
roomIndex: number

View File

@@ -32,7 +32,7 @@ import type { DetailsBooking } from "../../utils/url"
export const EnterDetailsContext = createContext<EnterDetailsStore | null>(null)
export type DetailsProviderProps = React.PropsWithChildren & {
type DetailsProviderProps = React.PropsWithChildren & {
booking: DetailsBooking
breakfastPackages: BreakfastPackages
lang: Lang

View File

@@ -7,7 +7,7 @@ import type { Room } from "@scandic-hotels/trpc/types/room"
import type { RoomState } from "../../stores/enter-details/types"
export interface RoomContextValue {
interface RoomContextValue {
actions: RoomState["actions"]
isComplete: RoomState["isComplete"]
idx: number
@@ -16,7 +16,7 @@ export interface RoomContextValue {
steps: RoomState["steps"]
}
export const RoomContext = createContext<RoomContextValue | null>(null)
const RoomContext = createContext<RoomContextValue | null>(null)
export function useRoomContext() {
const ctx = useContext(RoomContext)
@@ -26,7 +26,7 @@ export function useRoomContext() {
return ctx
}
export type RoomProviderProps = {
type RoomProviderProps = {
idx: number
room: Room
children: React.ReactNode

View File

@@ -481,7 +481,6 @@ export function SelectRateProvider({
}
export const useSelectRateContext = () => useContext(SelectRateContext)
export const SelectRateConsumer = SelectRateContext.Consumer
const getDefaultRoomPackages = (intl: IntlShape): DefaultRoomPackage[] =>
[

View File

@@ -224,7 +224,7 @@ type OneLevelNonNullable<T> = {
[K in keyof T]-?: NonNullable<T[K]>
}
export function calculateCorporateChequePrice(
function calculateCorporateChequePrice(
selectedRates: OneLevelNonNullable<SelectedRate>[],
addAdditionalCost?: boolean
) {

View File

@@ -42,7 +42,7 @@ export function extractGuestFromUser(user: User) {
}
}
export function add(...nums: (number | string | undefined)[]) {
function add(...nums: (number | string | undefined)[]) {
return nums.reduce((total: number, num) => {
if (typeof num === "undefined") {
num = 0
@@ -354,7 +354,7 @@ interface TRoomCorporateCheque extends TRoom {
roomRate: CorporateChequeProduct
}
export function getCorporateChequePrice(rooms: TRoom[], nights: number) {
function getCorporateChequePrice(rooms: TRoom[], nights: number) {
return rooms
.filter(
(room): room is TRoomCorporateCheque => "corporateCheque" in room.roomRate
@@ -407,7 +407,7 @@ interface TRoomVoucher extends TRoom {
roomRate: VoucherProduct
}
export function getVoucherPrice(rooms: TRoom[], nights: number) {
function getVoucherPrice(rooms: TRoom[], nights: number) {
return rooms
.filter((room): room is TRoomVoucher => "voucher" in room.roomRate)
.reduce<Price>(
@@ -440,7 +440,7 @@ interface TRoomRedemption extends TRoom {
roomRate: RedemptionProduct
}
export function getRedemptionPrice(rooms: TRoom[], nights: number) {
function getRedemptionPrice(rooms: TRoom[], nights: number) {
return rooms
.filter((room): room is TRoomRedemption => "redemption" in room.roomRate)
.reduce<Price>(
@@ -478,11 +478,7 @@ interface TRoomPriceProduct extends TRoom {
roomRate: PriceProduct
}
export function getRegularPrice(
rooms: TRoom[],
isMember: boolean,
nights: number
) {
function getRegularPrice(rooms: TRoom[], isMember: boolean, nights: number) {
const totalPrice = rooms
.filter(
(room): room is TRoomPriceProduct =>

View File

@@ -5,14 +5,12 @@ import { serverClient } from "../trpc"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { HotelInput } from "@scandic-hotels/trpc/types/hotel"
export const getSiteConfig = cache(async function getMemoizedSiteConfig(
lang: Lang
) {
const getSiteConfig = cache(async function getMemoizedSiteConfig(lang: Lang) {
const caller = await serverClient()
return caller.contentstack.base.siteConfig({ lang })
})
export const getPageSettings = cache(async function getMemoizedPageSettings(
const getPageSettings = cache(async function getMemoizedPageSettings(
lang: Lang
) {
const caller = await serverClient()

View File

@@ -1,11 +0,0 @@
export type PromoProps = {
buttonText: string
href: string
text: string
title: string
image?: {
src: string
altText: string
altText_En: string
}
}

View File

@@ -67,7 +67,7 @@ export function calculateRegularPrice({
}
//copied from enter-details/helpers.ts
export function add(...nums: (number | string | undefined)[]) {
function add(...nums: (number | string | undefined)[]) {
return nums.reduce((total: number, num) => {
if (typeof num === "undefined") {
num = 0

View File

@@ -11,8 +11,6 @@
"test:watch": "vitest"
},
"exports": {
"./bedTypeIcons": "./lib/misc/bedTypeIcons.ts",
"./BookingCodeFilter": "./lib/components/BookingCodeFilter/index.tsx",
"./BookingFlowContextProvider": "./lib/components/BookingFlowContextProvider.tsx",
"./BookingFlowTrackingProvider": "./lib/components/BookingFlowTrackingProvider.tsx",
"./BookingWidget": "./lib/components/BookingWidget/index.tsx",
@@ -21,59 +19,31 @@
"./BookingWidget/Skeleton": "./lib/components/BookingWidget/Skeleton.tsx",
"./components/AdditionalAmenities": "./lib/components/AdditionalAmenities/index.tsx",
"./components/AddToCalendar": "./lib/components/AddToCalendar/index.tsx",
"./components/BookingConfirmation/Confirmation": "./lib/components/BookingConfirmation/Confirmation/index.tsx",
"./components/BookingConfirmation/Header/Actions/downloadInvoice": "./lib/components/BookingConfirmation/Header/Actions/downloadInvoice.ts",
"./components/BookingConfirmation/Header/Actions/helpers": "./lib/components/BookingConfirmation/Header/Actions/helpers.ts",
"./components/Contact": "./lib/components/Contact/index.tsx",
"./components/EnterDetails/enterDetailsErrors": "./lib/components/EnterDetails/enterDetailsErrors.ts",
"./components/EnterDetails/Payment/helpers": "./lib/components/EnterDetails/Payment/helpers.ts",
"./components/EnterDetails/Payment/PaymentCallback/HandleErrorCallback": "./lib/components/EnterDetails/Payment/PaymentCallback/HandleErrorCallback.tsx",
"./components/EnterDetails/Payment/PaymentCallback/HandleSuccessCallback": "./lib/components/EnterDetails/Payment/PaymentCallback/HandleSuccessCallback.tsx",
"./components/EnterDetails/Payment/PaymentCallback/helpers": "./lib/components/EnterDetails/Payment/PaymentCallback/helpers.ts",
"./components/EnterDetails/StorageCleaner": "./lib/components/EnterDetails/StorageCleaner.tsx",
"./components/FnFNotAllowedAlert": "./lib/components/FnFNotAllowedAlert/index.tsx",
"./components/HotelDetailsSidePeek": "./lib/components/HotelDetailsSidePeek/index.tsx",
"./components/HotelReservationSidePeek": "./lib/components/HotelReservationSidePeek/index.tsx",
"./components/OpenSidePeekButton": "./lib/components/OpenSidePeekButton/index.tsx",
"./components/PriceDetailsModal": "./lib/components/PriceDetailsModal/index.tsx",
"./components/RoomCardSkeleton": "./lib/components/RoomCardSkeleton/RoomCardSkeleton.tsx",
"./components/RoomDetailsSidePeek": "./lib/components/RoomDetailsSidePeek/index.tsx",
"./components/RoomSidePeekContent": "./lib/components/RoomSidePeek/RoomSidePeekContent/index.tsx",
"./components/SelectHotel": "./lib/components/SelectHotel/index.tsx",
"./components/SelectHotelMap": "./lib/components/SelectHotel/SelectHotelMap/index.tsx",
"./components/SelectRate": "./lib/components/SelectRate/index.tsx",
"./components/SelectRate/RoomsContainer/RateSummary/utils": "./lib/components/SelectRate/RoomsContainer/RateSummary/utils.ts",
"./components/SidePanel": "./lib/components/SidePanel/index.tsx",
"./components/SidePeekAccordions/BreakfastAccordionItem": "./lib/components/SidePeekAccordions/BreakfastAccordionItem.tsx",
"./components/SidePeekAccordions/CheckInCheckOutAccordionItem": "./lib/components/SidePeekAccordions/CheckInCheckOutAccordionItem.tsx",
"./components/SidePeekAccordions/ParkingAccordionItem": "./lib/components/SidePeekAccordions/ParkingAccordionItem.tsx",
"./components/SignupPromoDesktop": "./lib/components/SignupPromo/Desktop.tsx",
"./components/SignupPromoMobile": "./lib/components/SignupPromo/Mobile.tsx",
"./components/TripAdvisorChip": "./lib/components/TripAdvisorChip/index.tsx",
"./contexts/EnterDetails/RoomContext": "./lib/contexts/EnterDetails/RoomContext.tsx",
"./contexts/SelectRate/getTotalPrice": "./lib/contexts/SelectRate/getTotalPrice.ts",
"./contexts/SelectRate/Room": "./lib/contexts/SelectRate/Room.ts",
"./contexts/SelectRate/SelectRateContext": "./lib/contexts/SelectRate/SelectRateContext.tsx",
"./contexts/SelectRate/types": "./lib/contexts/SelectRate/types.ts",
"./global.d.ts": "./global.d.ts",
"./hooks/useHandleBookingStatus": "./lib/hooks/useHandleBookingStatus.ts",
"./hooks/useSearchHistory": "./lib/hooks/useSearchHistory.ts",
"./pages/*": "./lib/pages/*.tsx",
"./providers/BookingConfirmationProvider": "./lib/providers/BookingConfirmationProvider.tsx",
"./searchType": "./lib/misc/searchType.ts",
"./stores/bookingCode-filter": "./lib/stores/bookingCode-filter.ts",
"./stores/enter-details": "./lib/stores/enter-details/index.ts",
"./stores/enter-details/types": "./lib/stores/enter-details/types.ts",
"./stores/hotels-map": "./lib/stores/hotels-map.ts",
"./stores/booking-confirmation": "./lib/stores/booking-confirmation/index.ts",
"./types/components/bookingConfirmation/bookingConfirmation": "./lib/types/components/bookingConfirmation/bookingConfirmation.ts",
"./types/components/findMyBooking/additionalInfoCookieValue": "./lib/types/components/findMyBooking/additionalInfoCookieValue.ts",
"./types/components/promo/promoProps": "./lib/types/components/promo/promoProps.ts",
"./types/components/selectRate/selectRate": "./lib/types/components/selectRate/selectRate.ts",
"./types/membershipFailedError": "./lib/types/membershipFailedError.ts",
"./types/stores/booking-confirmation": "./lib/types/stores/booking-confirmation.ts",
"./types/stores/rates": "./lib/types/stores/rates.ts",
"./utils/calculateRegularPrice": "./lib/utils/calculateRegularPrice.ts",
"./utils/getRoomFeatureDescription": "./lib/utils/getRoomFeatureDescription.ts",
"./utils/isSameBooking": "./lib/utils/isSameBooking.ts",
"./utils/nuqs": "./lib/utils/nuqs.ts",