Merged in fix/remove-old-select-rate (pull request #2647)

Fix/remove old select rate

* remove old select-rate

* Fix imports

* renamed SelectRate2 -> SelectRate
This commit is contained in:
Joakim Jäderberg
2025-08-13 13:43:48 +00:00
parent 51f53a717d
commit e3067331c6
127 changed files with 1859 additions and 8448 deletions

View File

@@ -1,9 +1,7 @@
"use client"
import { useSession } from "next-auth/react"
import { useIntl } from "react-intl"
import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import { dt } from "@scandic-hotels/common/dt"
import { logger } from "@scandic-hotels/common/logger"
import Body from "@scandic-hotels/design-system/Body"
import Caption from "@scandic-hotels/design-system/Caption"
@@ -13,123 +11,40 @@ import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton
import Subtitle from "@scandic-hotels/design-system/Subtitle"
import { RateEnum } from "@scandic-hotels/trpc/enums/rate"
import { useRatesStore } from "@/stores/select-rate"
import Chip from "@/components/TempDesignSystem/Chip"
import { useRoomContext } from "@/contexts/SelectRate/Room"
import { isValidClientSession } from "@/utils/clientSession"
import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext"
import { useIsUserLoggedIn } from "@/hooks/useIsUserLoggedIn"
import styles from "./selectedRoomPanel.module.css"
export default function SelectedRoomPanel() {
export function SelectedRoomPanel({ roomIndex }: { roomIndex: number }) {
const intl = useIntl()
const { dates, roomCategories, rooms } = useRatesStore((state) => ({
dates: {
from: state.booking.fromDate,
to: state.booking.toDate,
},
roomCategories: state.roomCategories,
rooms: state.rooms,
}))
const { data: session } = useSession()
const isUserLoggedIn = isValidClientSession(session)
const isMainRoom = roomIndex === 0
const roomNr = roomIndex + 1
const {
actions: { modifyRate },
isMainRoom,
roomNr,
selectedPackages,
selectedRate,
} = useRoomContext()
const nights = dt(dates.to).diff(dt(dates.from), "days")
selectedRates,
actions: { setActiveRoom },
} = useSelectRateContext()
const selectedRate = selectedRates.forRoom(roomIndex)
const images = selectedRate?.roomInfo?.roomInfo?.images
const images = roomCategories.find((roomCategory) =>
roomCategory.roomTypes.some(
(roomType) => roomType.code === selectedRate?.roomTypeCode
)
)?.images
const rateTitle = useRateTitle(selectedRate?.rate)
const freeCancelation = intl.formatMessage({
defaultMessage: "Free cancellation",
})
const nonRefundable = intl.formatMessage({
defaultMessage: "Non-refundable",
})
const freeBooking = intl.formatMessage({
defaultMessage: "Free rebooking",
})
const payLater = intl.formatMessage({
defaultMessage: "Pay later",
})
const payNow = intl.formatMessage({
defaultMessage: "Pay now",
})
function getRateTitle(rate: RateEnum) {
switch (rate) {
case RateEnum.change:
return `${freeBooking}, ${payNow}`
case RateEnum.flex:
return `${freeCancelation}, ${payLater}`
case RateEnum.save:
default:
return `${nonRefundable}, ${payNow}`
}
}
const selectedProductTitle = useSelectedProductTitle({ roomIndex })
if (!selectedRate) {
return null
}
const selectedPackagesCurrency = selectedPackages.find(
(pkg) => pkg.localPrice.currency
)
const selectedPackagesPrice = selectedPackages.reduce(
(total, pkg) => total + pkg.localPrice.totalPrice,
0
)
const selectedPackagesPricePerNight = Math.ceil(
selectedPackagesPrice / nights
)
const night = intl.formatMessage({
defaultMessage: "night",
})
let selectedProduct
if (
isUserLoggedIn &&
isMainRoom &&
"member" in selectedRate.product &&
selectedRate.product.member
) {
const { localPrice } = selectedRate.product.member
selectedProduct = `${localPrice.pricePerNight + selectedPackagesPricePerNight} ${localPrice.currency} / ${night}`
} else if ("public" in selectedRate.product && selectedRate.product.public) {
const { localPrice } = selectedRate.product.public
selectedProduct = `${localPrice.pricePerNight + selectedPackagesPricePerNight} ${localPrice.currency} / ${night}`
} else if ("corporateCheque" in selectedRate.product) {
const { localPrice } = selectedRate.product.corporateCheque
selectedProduct = `${localPrice.numberOfCheques} ${CurrencyEnum.CC}`
if (
(localPrice.additionalPricePerStay || selectedPackagesPrice) &&
localPrice.currency
) {
selectedProduct = `${selectedProduct} + ${localPrice.additionalPricePerStay + selectedPackagesPrice} ${localPrice.currency}`
}
} else if ("voucher" in selectedRate.product) {
selectedProduct = `${selectedRate.product.voucher.numberOfVouchers} ${CurrencyEnum.Voucher}`
if (selectedPackagesPrice && selectedPackagesCurrency) {
selectedProduct = `${selectedProduct} + ${selectedPackagesPrice} ${selectedPackagesCurrency}`
}
}
if (!selectedProduct) {
if (!selectedProductTitle) {
logger.error("Selected product is unknown")
return null
}
const showModifyButton =
isMainRoom ||
(!isMainRoom && rooms.slice(0, roomNr).every((room) => room.selectedRate))
(!isMainRoom && selectedRates.rates.slice(0, roomNr).every((room) => room))
return (
<div className={styles.selectedRoomPanel}>
@@ -143,17 +58,19 @@ export default function SelectedRoomPanel() {
)}
</Caption>
<Subtitle className={styles.subtitle} color="uiTextHighContrast">
{selectedRate.roomType}
{selectedRate.roomInfo.roomType}
</Subtitle>
<Body color="uiTextMediumContrast">
{getRateTitle(selectedRate.product.rate)}
</Body>
<Body color="uiTextHighContrast">{selectedProduct}</Body>
<Body color="uiTextMediumContrast">{rateTitle}</Body>
<Body color="uiTextHighContrast">{selectedProductTitle}</Body>
</div>
<div className={styles.imageContainer}>
{images?.[0]?.imageSizes?.tiny ? (
<Image
alt={selectedRate.roomType ?? images[0].metaData?.altText ?? ""}
alt={
selectedRate.roomInfo.roomType ??
images[0].metaData?.altText ??
""
}
className={styles.img}
height={300}
src={images[0].imageSizes.tiny}
@@ -162,7 +79,7 @@ export default function SelectedRoomPanel() {
) : null}
{showModifyButton && (
<div className={styles.modifyButtonContainer}>
<Button clean onClick={modifyRate}>
<Button clean onClick={() => setActiveRoom(roomIndex)}>
<Chip size="small" variant="uiTextHighContrast">
<MaterialIcon
size={16}
@@ -180,3 +97,99 @@ export default function SelectedRoomPanel() {
</div>
)
}
function useSelectedProductTitle({ roomIndex }: { roomIndex: number }) {
const intl = useIntl()
const isUserLoggedIn = useIsUserLoggedIn()
const {
selectedRates,
input: { nights },
} = useSelectRateContext()
const selectedRate = selectedRates.forRoom(roomIndex)
const night = intl.formatMessage({
defaultMessage: "night",
})
const isMainRoom = roomIndex === 0
if (!selectedRate) {
return null
}
const selectedPackagesCurrency = selectedRate.roomInfo.selectedPackages.find(
(pkg) => pkg.localPrice.currency
)
const selectedPackagesPrice = selectedRate.roomInfo.selectedPackages.reduce(
(total, pkg) => total + pkg.localPrice.totalPrice,
0
)
const selectedPackagesPricePerNight = Math.ceil(
selectedPackagesPrice / nights
)
if (
isUserLoggedIn &&
isMainRoom &&
"member" in selectedRate &&
selectedRate.member
) {
const { localPrice } = selectedRate.member
return `${localPrice.pricePerNight + selectedPackagesPricePerNight} ${localPrice.currency} / ${night}`
}
if ("public" in selectedRate && selectedRate.public) {
const { localPrice } = selectedRate.public
return `${localPrice.pricePerNight + selectedPackagesPricePerNight} ${localPrice.currency} / ${night}`
}
if ("corporateCheque" in selectedRate) {
const { localPrice } = selectedRate.corporateCheque
const mainProductTitle = `${localPrice.numberOfCheques} ${CurrencyEnum.CC}`
if (
(localPrice.additionalPricePerStay || selectedPackagesPrice) &&
localPrice.currency
) {
const packagesText = `${localPrice.additionalPricePerStay + selectedPackagesPrice} ${localPrice.currency}`
return `${mainProductTitle} + ${packagesText}`
}
}
if ("voucher" in selectedRate) {
const mainProductText = `${selectedRate.voucher.numberOfVouchers} ${CurrencyEnum.Voucher}`
if (selectedPackagesPrice && selectedPackagesCurrency) {
const packagesText = `${selectedPackagesPrice} ${selectedPackagesCurrency}`
return `${mainProductText} + ${packagesText}`
}
}
}
function useRateTitle(rate: RateEnum | undefined) {
const intl = useIntl()
const freeCancelation = intl.formatMessage({
defaultMessage: "Free cancellation",
})
const nonRefundable = intl.formatMessage({
defaultMessage: "Non-refundable",
})
const freeBooking = intl.formatMessage({
defaultMessage: "Free rebooking",
})
const payLater = intl.formatMessage({
defaultMessage: "Pay later",
})
const payNow = intl.formatMessage({
defaultMessage: "Pay now",
})
switch (rate) {
case RateEnum.change:
return `${freeBooking}, ${payNow}`
case RateEnum.flex:
return `${freeCancelation}, ${payLater}`
case RateEnum.save:
default:
return `${nonRefundable}, ${payNow}`
}
}

View File

@@ -6,29 +6,32 @@ import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton"
import Subtitle from "@scandic-hotels/design-system/Subtitle"
import { useRatesStore } from "@/stores/select-rate"
import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext"
import { useRoomContext } from "@/contexts/SelectRate/Room"
import SelectedRoomPanel from "./SelectedRoomPanel"
import { SelectedRoomPanel } from "./SelectedRoomPanel"
import { roomSelectionPanelVariants } from "./variants"
import styles from "./multiRoomWrapper.module.css"
export default function MultiRoomWrapper({
children,
isMultiRoom,
}: React.PropsWithChildren<{ isMultiRoom: boolean }>) {
type Props = {
children: React.ReactNode
isMultiRoom: boolean
roomIndex: number
}
export function MultiRoomWrapper({ children, isMultiRoom, roomIndex }: Props) {
const intl = useIntl()
const activeRoom = useRatesStore((state) => state.activeRoom)
const {
actions: { closeSection },
bookingRoom,
isActiveRoom,
roomNr,
selectedRate,
} = useRoomContext()
const { getTopOffset } = useStickyPosition()
const {
activeRoomIndex,
selectedRates,
actions: { setActiveRoom },
input: { data },
} = useSelectRateContext()
const roomNr = roomIndex + 1
const adultCount = data?.booking.rooms[roomIndex]?.adults || 0
const childCount = data?.booking.rooms[roomIndex]?.childrenInRoom?.length || 0
const isActiveRoom = activeRoomIndex === roomIndex
const roomMsg = intl.formatMessage(
{
@@ -41,7 +44,7 @@ export default function MultiRoomWrapper({
{
defaultMessage: "{adults, plural, one {# adult} other {# adults}}",
},
{ adults: bookingRoom.adults }
{ adults: adultCount }
)
const childrenMsg = intl.formatMessage(
@@ -49,15 +52,13 @@ export default function MultiRoomWrapper({
defaultMessage: "{children, plural, one {# child} other {# children}}",
},
{
children: bookingRoom.childrenInRoom?.length,
children: childCount,
}
)
const onlyAdultsMsg = adultsMsg
const adultsAndChildrenMsg = [adultsMsg, childrenMsg].join(", ")
const guestsMsg = bookingRoom.childrenInRoom?.length
? adultsAndChildrenMsg
: onlyAdultsMsg
const guestsMsg = childCount ? adultsAndChildrenMsg : onlyAdultsMsg
const title = [roomMsg, guestsMsg].join(", ")
@@ -69,7 +70,7 @@ export default function MultiRoomWrapper({
// If no room is active we will show all rooms collapsed, hence we want
// to scroll to the first room.
const selectedRoom =
activeRoom === -1 ? roomElements[0] : roomElements[activeRoom]
activeRoomIndex === -1 ? roomElements[0] : roomElements[activeRoomIndex]
if (selectedRoom) {
const elementPosition = selectedRoom.getBoundingClientRect().top
@@ -86,7 +87,9 @@ export default function MultiRoomWrapper({
}
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [activeRoom])
}, [activeRoomIndex])
const selectedRate = selectedRates.rateSelectedForRoom(roomIndex)
if (isMultiRoom) {
const classNames = roomSelectionPanelVariants({
@@ -102,7 +105,9 @@ export default function MultiRoomWrapper({
{selectedRate && isActiveRoom ? (
<Button
intent="text"
onClick={closeSection}
onClick={() => {
setActiveRoom("deselect")
}}
size="medium"
theme="base"
variant="icon"
@@ -120,7 +125,7 @@ export default function MultiRoomWrapper({
</div>
<div className={classNames}>
<div className={styles.roomPanel}>
<SelectedRoomPanel />
<SelectedRoomPanel roomIndex={roomIndex} />
</div>
<div className={styles.roomSelectionPanel}>{children}</div>
</div>