chore: cleaning up select-rate

This commit is contained in:
Simon Emanuelsson
2025-02-05 20:18:03 +01:00
parent 3044bc87d1
commit 051bc54e6c
95 changed files with 3269 additions and 3527 deletions

View File

@@ -0,0 +1,118 @@
"use client"
import { useSession } from "next-auth/react"
import { useIntl } from "react-intl"
import { useRatesStore } from "@/stores/select-rate"
import { getRates } from "@/components/HotelReservation/SelectRate/utils"
import { EditIcon } from "@/components/Icons"
import Image from "@/components/Image"
import Button from "@/components/TempDesignSystem/Button"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import { useRoomContext } from "@/contexts/Room"
import { isValidClientSession } from "@/utils/clientSession"
import styles from "./selectedRoomPanel.module.css"
import type { Room as SelectedRateRoom } from "@/types/components/hotelReservation/selectRate/selectRate"
interface SelectedRoomPanelProps {
room: SelectedRateRoom
}
export default function SelectedRoomPanel({ room }: SelectedRoomPanelProps) {
const intl = useIntl()
const { data: session } = useSession()
const isUserLoggedIn = isValidClientSession(session)
const { rateDefinitions, roomCategories } = useRatesStore((state) => ({
rateDefinitions: state.roomsAvailability?.rateDefinitions,
roomCategories: state.roomCategories,
}))
const {
actions: { modifyRate },
roomNr,
selectedRate,
} = useRoomContext()
const images = roomCategories.find((roomCategory) =>
roomCategory.roomTypes.some(
(roomType) => roomType.code === selectedRate?.roomTypeCode
)
)?.images
if (!rateDefinitions) {
return null
}
const rates = getRates(rateDefinitions)
const freeCancelation = intl.formatMessage({ id: "Free cancellation" })
const nonRefundable = intl.formatMessage({ id: "Non-refundable" })
const freeBooking = intl.formatMessage({ id: "Free rebooking" })
const payLater = intl.formatMessage({ id: "Pay later" })
const payNow = intl.formatMessage({ id: "Pay now" })
function getRateDetails(rateCode: string) {
const rate = Object.keys(rates).find((k) =>
rates[k as keyof typeof rates].find((a) => a.rateCode === rateCode)
)
switch (rate) {
case "change":
return `${freeBooking}, ${payNow}`
case "flex":
return `${freeCancelation}, ${payLater}`
case "save":
default:
return `${nonRefundable}, ${payNow}`
}
}
const rateCode =
isUserLoggedIn && selectedRate?.product.productType.member
? selectedRate?.product.productType.member?.rateCode
: selectedRate?.product.productType.public.rateCode
return (
<div className={styles.selectedRoomPanel}>
<div>
<Caption color="uiTextHighContrast">
{intl.formatMessage(
{ id: "Room {roomIndex}" },
{ roomIndex: roomNr }
)}
</Caption>
<Subtitle className={styles.subtitle} color="uiTextHighContrast">
{selectedRate?.roomType}
</Subtitle>
<Body color="uiTextMediumContrast">
{rateCode ? getRateDetails(rateCode) : null}
</Body>
<Body color="uiTextHighContrast">
{selectedRate?.product.productType.public.localPrice.pricePerNight}{" "}
{selectedRate?.product.productType.public.localPrice.currency}/
{intl.formatMessage({ id: "night" })}
</Body>
</div>
<div className={styles.imageAndModifyButtonContainer}>
{images?.[0]?.imageSizes?.tiny && (
<div className={styles.imageContainer}>
<Image
alt={selectedRate?.roomType ?? images[0].metaData?.altText ?? ""}
fill
src={images[0].imageSizes.tiny}
/>
</div>
)}
<div className={styles.modifyButtonContainer}>
<Button variant="icon" size="small" onClick={modifyRate}>
<EditIcon />
{intl.formatMessage({ id: "Modify" })}
</Button>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,50 @@
.selectedRoomPanel {
display: flex;
flex-direction: row;
justify-content: space-between;
position: relative;
}
.modifyButtonContainer {
position: absolute;
right: var(--Spacing-x2);
bottom: var(--Spacing-x2);
}
.imageContainer {
width: 187px;
height: 105px;
position: relative;
border-radius: var(--Corner-radius-Small);
overflow: hidden;
}
.titleContainer {
display: flex;
flex-direction: column;
gap: var(--Spacing-x1);
}
div.selectedRoomPanel p.subtitle {
padding-bottom: var(--Spacing-x1);
}
@media (max-width: 768px) {
.imageContainer {
width: 120px;
height: 80px;
}
.imageAndModifyButtonContainer {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: var(--Spacing-x1);
}
.modifyButtonContainer {
position: relative;
bottom: 0;
right: 0;
}
}

View File

@@ -0,0 +1,83 @@
import { useEffect } from "react"
import { useIntl } from "react-intl"
import { useRatesStore } from "@/stores/select-rate"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import { useRoomContext } from "@/contexts/Room"
import SelectedRoomPanel from "./SelectedRoomPanel"
import { roomSelectionPanelVariants } from "./variants"
import styles from "./multiRoomWrapper.module.css"
export default function MultiRoomWrapper({
children,
isMultiRoom,
}: React.PropsWithChildren<{ isMultiRoom: boolean }>) {
const intl = useIntl()
const activeRoom = useRatesStore((state) => state.activeRoom)
const { bookingRoom, isActiveRoom, roomNr, selectedRate } = useRoomContext()
const onlyAdultsMsg = intl.formatMessage(
{ id: "{adults} adults" },
{ adults: bookingRoom.adults }
)
const adultsAndChildrenMsg = intl.formatMessage(
{ id: "{adults} adults, {children} children" },
{
adults: bookingRoom.adults,
children: bookingRoom.childrenInRoom?.length,
}
)
useEffect(() => {
requestAnimationFrame(() => {
const SCROLL_OFFSET = 100
const roomElements = document.querySelectorAll(`.${styles.roomContainer}`)
const selectedRoom = roomElements[activeRoom]
if (selectedRoom) {
const elementPosition = selectedRoom.getBoundingClientRect().top
const offsetPosition = elementPosition + window.scrollY - SCROLL_OFFSET
window.scrollTo({
top: offsetPosition,
behavior: "smooth",
})
}
})
}, [activeRoom])
if (isMultiRoom) {
const classNames = roomSelectionPanelVariants({
active: isActiveRoom,
selected: !!selectedRate && !isActiveRoom,
})
return (
<div className={styles.roomContainer}>
{selectedRate && !isActiveRoom ? null : (
<Subtitle className={styles.subtitle} color="uiTextHighContrast">
{intl.formatMessage(
{ id: "Room {roomIndex}" },
{ roomIndex: roomNr }
)}
,{" "}
{bookingRoom.childrenInRoom?.length
? adultsAndChildrenMsg
: onlyAdultsMsg}
</Subtitle>
)}
<div className={classNames}>
<div className={styles.roomPanel}>
<SelectedRoomPanel room={bookingRoom} />
</div>
<div className={styles.roomSelectionPanel}>{children}</div>
</div>
</div>
)
}
return children
}

View File

@@ -0,0 +1,55 @@
.roomContainer {
background: var(--Base-Surface-Primary-light-Normal);
border: 1px solid var(--Base-Border-Subtle);
border-radius: var(--Corner-radius-Large);
display: flex;
flex-direction: column;
padding: var(--Spacing-x2);
}
.roomPanel,
.roomSelectionPanel {
display: grid;
grid-template-rows: 0fr;
opacity: 0;
height: 0;
transition:
opacity 0.3s ease,
grid-template-rows 0.3s ease;
transform-origin: bottom;
}
.roomPanel>* {
overflow: hidden;
}
.roomSelectionPanel {
gap: var(--Spacing-x2);
}
.roomSelectionPanelContainer.selected .roomPanel {
grid-template-rows: 1fr;
opacity: 1;
height: auto;
}
.roomSelectionPanelContainer.selected .roomSelectionPanel {
display: none;
}
.roomSelectionPanelContainer.active .roomSelectionPanel {
grid-template-rows: 1fr;
opacity: 1;
height: auto;
padding-top: var(--Spacing-x1);
}
div.roomContainer p.subtitle {
padding-bottom: var(--Spacing-x1);
}
@media (max-width: 768px) {
.roomContainer {
padding: var(--Spacing-x2);
}
}

View File

@@ -0,0 +1,21 @@
import { cva } from "class-variance-authority"
import styles from "./multiRoomWrapper.module.css"
export const roomSelectionPanelVariants = cva(
styles.roomSelectionPanelContainer,
{
variants: {
active: {
true: styles.active,
},
selected: {
true: styles.selected,
},
},
defaultVariants: {
active: false,
selected: false,
},
}
)