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

@@ -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