Merged in fix/SW-2553-sidepeeks (pull request #1919)

Fix/SW-2553 sidepeeks

* fix(SW-2553): apply sidepeek display logic

* chore: move convertToChildType and getPriceType utils

* fix: apply PR requested changes

* fix(SW-2553): fix roomNumber for multiroom

* fix(SW-2553): fix sidepeek for my-stay page


Approved-by: Michael Zetterberg
Approved-by: Bianca Widstam
Approved-by: Matilda Landström
This commit is contained in:
Arvid Norlin
2025-05-02 15:10:34 +00:00
committed by Bianca Widstam
parent f5f9aba2e5
commit 0c7836fa59
33 changed files with 881 additions and 510 deletions

View File

@@ -1,315 +0,0 @@
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from "next/navigation"
import { useState } from "react"
import { Dialog } from "react-aria-components"
import { FormProvider, useForm } from "react-hook-form"
import { useIntl } from "react-intl"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { trpc } from "@/lib/trpc/client"
import MembershipLevelIcon from "@/components/Levels/Icon"
import Modal from "@/components/Modal"
import { ModalContentWithActions } from "@/components/Modal/ModalContentWithActions"
import Button from "@/components/TempDesignSystem/Button"
import { toast } from "@/components/TempDesignSystem/Toasts"
import useLang from "@/hooks/useLang"
import ModifyContact from "../ModifyContact"
import styles from "./guestDetails.module.css"
import {
type ModifyContactSchema,
modifyContactSchema,
} from "@/types/components/hotelReservation/myStay/modifyContact"
import { MODAL_STEPS } from "@/types/components/hotelReservation/myStay/myStay"
import type { Room } from "@/types/stores/my-stay"
import type { SafeUser } from "@/types/user"
interface DetailsProps {
booking: Room
user: SafeUser
}
export default function Details({ booking, user }: DetailsProps) {
const intl = useIntl()
const lang = useLang()
const router = useRouter()
const utils = trpc.useUtils()
const [currentStep, setCurrentStep] = useState(MODAL_STEPS.INITIAL)
const [isLoading, setIsLoading] = useState(false)
const [isModifyGuestDetailsOpen, setIsModifyGuestDetailsOpen] =
useState(false)
const form = useForm<ModifyContactSchema>({
resolver: zodResolver(modifyContactSchema),
defaultValues: {
firstName: booking.guest.firstName,
lastName: booking.guest.lastName,
email: booking.guest.email,
phoneNumber: booking.guest.phoneNumber,
countryCode: booking.guest.countryCode,
},
})
const isFirstStep = currentStep === MODAL_STEPS.INITIAL
const isMemberBooking =
booking.guest.membershipNumber === user?.membership?.membershipNumber
const updateGuest = trpc.booking.update.useMutation({
onMutate: () => setIsLoading(true),
onSuccess: (data) => {
if (data) {
utils.booking.get.invalidate({
confirmationNumber: data.confirmationNumber,
})
toast.success(
intl.formatMessage({
defaultMessage: "Guest details updated",
})
)
setIsModifyGuestDetailsOpen(false)
setCurrentStep(MODAL_STEPS.INITIAL)
} else {
toast.error(
intl.formatMessage({
defaultMessage: "Failed to update guest details",
})
)
}
},
onError: () => {
toast.error(
intl.formatMessage({
defaultMessage: "Failed to update guest details",
})
)
},
onSettled: () => {
setIsLoading(false)
},
})
async function onSubmit(data: ModifyContactSchema) {
updateGuest.mutate({
confirmationNumber: booking.confirmationNumber,
guest: {
email: data.email,
phoneNumber: data.phoneNumber,
countryCode: data.countryCode,
},
})
}
function handleModifyMemberDetails() {
const expirationTime = Date.now() + 10 * 60 * 1000
sessionStorage.setItem(
"myStayReturnRoute",
JSON.stringify({
path: window.location.href,
expiry: expirationTime,
})
)
router.push(`/${lang}/scandic-friends/my-pages/profile/edit`)
}
return (
<div className={styles.guestDetails}>
{isMemberBooking && user.membership && (
<div className={styles.userDetails}>
<div className={styles.userDetailsTitle}>
<Typography variant="Title/Overline/sm">
<p>
{intl.formatMessage({
defaultMessage: "Your member tier",
})}
</p>
</Typography>
</div>
<div className={styles.memberLevel}>
<MembershipLevelIcon
level={user.membership.membershipLevel}
color="red"
rows={1}
className={styles.memberLevelIcon}
/>
</div>
<div className={styles.totalPoints}>
<div className={styles.totalPointsText}>
<MaterialIcon icon="diamond" color="Icon/Intense" />
<Typography variant="Title/Overline/sm">
<p>
{intl.formatMessage({
defaultMessage: "My total points",
})}
</p>
</Typography>
</div>
<Typography variant="Body/Paragraph/mdRegular">
<p>{user.membership.currentPoints}</p>
</Typography>
</div>
</div>
)}
<div className={styles.guest}>
<Typography variant="Body/Paragraph/mdBold">
<p>
{booking.guest.firstName} {booking.guest.lastName}
</p>
</Typography>
{isMemberBooking && user.membership && (
<Typography variant="Body/Paragraph/mdRegular">
<p className={styles.memberNumber}>
{intl.formatMessage(
{
defaultMessage: "Member no. {nr}",
},
{
nr: user.membership.membershipNumber,
}
)}
</p>
</Typography>
)}
<div className={styles.contactInfoMobile}>
<Typography variant="Body/Supporting text (caption)/smRegular">
<p color="uiTextHighContrast">{booking.guest.email}</p>
</Typography>
<Typography variant="Body/Supporting text (caption)/smRegular">
<p color="uiTextHighContrast">{booking.guest.phoneNumber}</p>
</Typography>
</div>
<div className={styles.contactInfoDesktop}>
<Typography variant="Body/Paragraph/mdRegular">
<p color="uiTextHighContrast">{booking.guest.email}</p>
</Typography>
<Typography variant="Body/Paragraph/mdRegular">
<p color="uiTextHighContrast">{booking.guest.phoneNumber}</p>
</Typography>
</div>
</div>
{isMemberBooking ? (
<Button
variant="icon"
color="burgundy"
intent={"secondary"}
onClick={handleModifyMemberDetails}
disabled={booking.isCancelled}
size="small"
>
<MaterialIcon
icon="edit"
color="Icon/Interactive/Default"
size={20}
/>
<Typography variant="Body/Paragraph/mdRegular">
<span>
{intl.formatMessage({
defaultMessage: "Modify guest details",
})}
</span>
</Typography>
</Button>
) : (
<>
<Button
variant="icon"
color="burgundy"
intent="secondary"
onClick={() =>
setIsModifyGuestDetailsOpen(!isModifyGuestDetailsOpen)
}
disabled={booking.isCancelled}
size="small"
>
<MaterialIcon
icon="edit"
color={
booking.isCancelled
? "Icon/Interactive/Disabled"
: "Icon/Interactive/Default"
}
size={20}
/>
<Typography variant="Body/Paragraph/mdRegular">
<span>
{intl.formatMessage({
defaultMessage: "Modify guest details",
})}
</span>
</Typography>
</Button>
{isModifyGuestDetailsOpen && (
<Modal
withActions
hideHeader
isOpen={isModifyGuestDetailsOpen}
onToggle={setIsModifyGuestDetailsOpen}
>
<Dialog
aria-label={intl.formatMessage({
defaultMessage: "Modify guest details",
})}
>
{({ close }) => (
<FormProvider {...form}>
<ModalContentWithActions
title={intl.formatMessage({
defaultMessage: "Modify guest details",
})}
onClose={() => setIsModifyGuestDetailsOpen(false)}
content={
booking.guest && (
<ModifyContact
guest={booking.guest}
isFirstStep={isFirstStep}
/>
)
}
primaryAction={{
label: isFirstStep
? intl.formatMessage({
defaultMessage: "Save updates",
})
: intl.formatMessage({
defaultMessage: "Confirm",
}),
onClick: isFirstStep
? () => setCurrentStep(MODAL_STEPS.CONFIRMATION)
: () => form.handleSubmit(onSubmit)(),
disabled: !form.formState.isValid || isLoading,
intent: isFirstStep ? "secondary" : "primary",
}}
secondaryAction={{
label: isFirstStep
? intl.formatMessage({
defaultMessage: "Back",
})
: intl.formatMessage({
defaultMessage: "Cancel",
}),
onClick: () => {
close()
setCurrentStep(MODAL_STEPS.INITIAL)
},
}}
/>
</FormProvider>
)}
</Dialog>
</Modal>
)}
</>
)}
</div>
)
}

View File

@@ -1,22 +1,322 @@
"use client"
import { useMyStayStore } from "@/stores/my-stay"
import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from "next/navigation"
import { useState } from "react"
import { Dialog } from "react-aria-components"
import { FormProvider, useForm } from "react-hook-form"
import { useIntl } from "react-intl"
import Details from "./Details"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"
import type { Room } from "@/types/stores/my-stay"
import { trpc } from "@/lib/trpc/client"
import MembershipLevelIcon from "@/components/Levels/Icon"
import Modal from "@/components/Modal"
import { ModalContentWithActions } from "@/components/Modal/ModalContentWithActions"
import Button from "@/components/TempDesignSystem/Button"
import { toast } from "@/components/TempDesignSystem/Toasts"
import useLang from "@/hooks/useLang"
import ModifyContact from "../ModifyContact"
import styles from "./guestDetails.module.css"
import {
type ModifyContactSchema,
modifyContactSchema,
} from "@/types/components/hotelReservation/myStay/modifyContact"
import { MODAL_STEPS } from "@/types/components/hotelReservation/myStay/myStay"
import type { SafeUser } from "@/types/user"
import type { Guest } from "@/server/routers/booking/output"
interface GuestDetailsProps {
selectedRoom?: Room
confirmationNumber: string
guest: Guest
isCancelled: boolean
user: SafeUser
}
export default function GuestDetails({
selectedRoom,
confirmationNumber,
guest,
isCancelled,
user,
}: GuestDetailsProps) {
const booking = useMyStayStore((state) => state.bookedRoom)
const room = selectedRoom ? selectedRoom : booking
const intl = useIntl()
const lang = useLang()
const router = useRouter()
const utils = trpc.useUtils()
const [currentStep, setCurrentStep] = useState(MODAL_STEPS.INITIAL)
const [isLoading, setIsLoading] = useState(false)
return <Details booking={room} user={user} />
const [isModifyGuestDetailsOpen, setIsModifyGuestDetailsOpen] =
useState(false)
const form = useForm<ModifyContactSchema>({
resolver: zodResolver(modifyContactSchema),
defaultValues: {
firstName: guest.firstName,
lastName: guest.lastName,
email: guest.email,
phoneNumber: guest.phoneNumber,
countryCode: guest.countryCode,
},
})
const isFirstStep = currentStep === MODAL_STEPS.INITIAL
const isMemberBooking =
guest.membershipNumber === user?.membership?.membershipNumber
const updateGuest = trpc.booking.update.useMutation({
onMutate: () => setIsLoading(true),
onSuccess: (data) => {
if (data) {
utils.booking.get.invalidate({
confirmationNumber: data.confirmationNumber,
})
toast.success(
intl.formatMessage({
defaultMessage: "Guest details updated",
})
)
setIsModifyGuestDetailsOpen(false)
setCurrentStep(MODAL_STEPS.INITIAL)
} else {
toast.error(
intl.formatMessage({
defaultMessage: "Failed to update guest details",
})
)
}
},
onError: () => {
toast.error(
intl.formatMessage({
defaultMessage: "Failed to update guest details",
})
)
},
onSettled: () => {
setIsLoading(false)
},
})
async function onSubmit(data: ModifyContactSchema) {
updateGuest.mutate({
confirmationNumber,
guest: {
email: data.email,
phoneNumber: data.phoneNumber,
countryCode: data.countryCode,
},
})
}
function handleModifyMemberDetails() {
const expirationTime = Date.now() + 10 * 60 * 1000
sessionStorage.setItem(
"myStayReturnRoute",
JSON.stringify({
path: window.location.href,
expiry: expirationTime,
})
)
router.push(`/${lang}/scandic-friends/my-pages/profile/edit`)
}
return (
<div className={styles.guestDetails}>
{isMemberBooking && user.membership && (
<div className={styles.userDetails}>
<div className={styles.userDetailsTitle}>
<Typography variant="Title/Overline/sm">
<p>
{intl.formatMessage({
defaultMessage: "Your member tier",
})}
</p>
</Typography>
</div>
<div className={styles.memberLevel}>
<MembershipLevelIcon
level={user.membership.membershipLevel}
color="red"
rows={1}
className={styles.memberLevelIcon}
/>
</div>
<div className={styles.totalPoints}>
<div className={styles.totalPointsText}>
<MaterialIcon icon="diamond" color="Icon/Intense" />
<Typography variant="Title/Overline/sm">
<p>
{intl.formatMessage({
defaultMessage: "My total points",
})}
</p>
</Typography>
</div>
<Typography variant="Body/Paragraph/mdRegular">
<p>{user.membership.currentPoints}</p>
</Typography>
</div>
</div>
)}
<div className={styles.guest}>
<Typography variant="Body/Paragraph/mdBold">
<p>
{guest.firstName} {guest.lastName}
</p>
</Typography>
{isMemberBooking && user.membership && (
<Typography variant="Body/Paragraph/mdRegular">
<p className={styles.memberNumber}>
{intl.formatMessage(
{
defaultMessage: "Member no. {nr}",
},
{
nr: user.membership.membershipNumber,
}
)}
</p>
</Typography>
)}
<div className={styles.contactInfoMobile}>
<Typography variant="Body/Supporting text (caption)/smRegular">
<p color="uiTextHighContrast">{guest.email}</p>
</Typography>
<Typography variant="Body/Supporting text (caption)/smRegular">
<p color="uiTextHighContrast">{guest.phoneNumber}</p>
</Typography>
</div>
<div className={styles.contactInfoDesktop}>
<Typography variant="Body/Paragraph/mdRegular">
<p color="uiTextHighContrast">{guest.email}</p>
</Typography>
<Typography variant="Body/Paragraph/mdRegular">
<p color="uiTextHighContrast">{guest.phoneNumber}</p>
</Typography>
</div>
</div>
{isMemberBooking ? (
<Button
variant="icon"
color="burgundy"
intent="secondary"
onClick={handleModifyMemberDetails}
disabled={isCancelled}
size="small"
>
<MaterialIcon
icon="edit"
color="Icon/Interactive/Default"
size={20}
/>
<Typography variant="Body/Paragraph/mdRegular">
<span>
{intl.formatMessage({
defaultMessage: "Modify guest details",
})}
</span>
</Typography>
</Button>
) : (
<>
<Button
variant="icon"
color="burgundy"
intent="secondary"
onClick={() =>
setIsModifyGuestDetailsOpen(!isModifyGuestDetailsOpen)
}
disabled={isCancelled}
size="small"
>
<MaterialIcon
icon="edit"
color={
isCancelled
? "Icon/Interactive/Disabled"
: "Icon/Interactive/Default"
}
size={20}
/>
<Typography variant="Body/Paragraph/mdRegular">
<span>
{intl.formatMessage({
defaultMessage: "Modify guest details",
})}
</span>
</Typography>
</Button>
{isModifyGuestDetailsOpen && (
<Modal
withActions
hideHeader
isOpen={isModifyGuestDetailsOpen}
onToggle={setIsModifyGuestDetailsOpen}
>
<Dialog
aria-label={intl.formatMessage({
defaultMessage: "Modify guest details",
})}
>
{({ close }) => (
<FormProvider {...form}>
<ModalContentWithActions
title={intl.formatMessage({
defaultMessage: "Modify guest details",
})}
onClose={() => setIsModifyGuestDetailsOpen(false)}
content={
guest && (
<ModifyContact
guest={guest}
isFirstStep={isFirstStep}
/>
)
}
primaryAction={{
label: isFirstStep
? intl.formatMessage({
defaultMessage: "Save updates",
})
: intl.formatMessage({
defaultMessage: "Confirm",
}),
onClick: isFirstStep
? () => setCurrentStep(MODAL_STEPS.CONFIRMATION)
: form.handleSubmit(onSubmit),
disabled: !form.formState.isValid || isLoading,
intent: isFirstStep ? "secondary" : "primary",
}}
secondaryAction={{
label: isFirstStep
? intl.formatMessage({
defaultMessage: "Back",
})
: intl.formatMessage({
defaultMessage: "Cancel",
}),
onClick: () => {
close()
setCurrentStep(MODAL_STEPS.INITIAL)
},
}}
/>
</FormProvider>
)}
</Dialog>
</Modal>
)}
</>
)}
</div>
)
}

View File

@@ -1,43 +0,0 @@
"use client"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import useSidePeekStore from "@/stores/sidepeek"
import Button from "@/components/TempDesignSystem/Button"
import styles from "./toggleSidePeek.module.css"
import { SidePeekEnum } from "@/types/components/hotelReservation/sidePeek"
import type { ToggleSidePeekProps } from "@/types/components/hotelReservation/toggleSidePeekProps"
export default function ToggleSidePeek({
hotelId,
roomTypeCode,
user,
confirmationNumber,
}: ToggleSidePeekProps) {
const openSidePeek = useSidePeekStore((state) => state.openSidePeek)
return (
<Button
onClick={() =>
openSidePeek({
key: SidePeekEnum.bookedRoomDetails,
hotelId,
roomTypeCode,
user,
confirmationNumber,
})
}
size="small"
variant="icon"
intent="text"
wrapping
>
<div className={styles.iconContainer}>
<MaterialIcon icon="pan_zoom" color="CurrentColor" />
</div>
</Button>
)
}

View File

@@ -1,13 +1,16 @@
"use client"
import { Button as ButtonRAC, DialogTrigger } from "react-aria-components"
import { useIntl } from "react-intl"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { dt } from "@/lib/dt"
import { getBookedHotelRoom } from "@/server/routers/booking/utils"
import { IconForFeatureCode } from "@/components/HotelReservation/utils"
import Image from "@/components/Image"
import BookedRoomSidePeek from "@/components/SidePeeks/BookedRoomSidePeek"
import Divider from "@/components/TempDesignSystem/Divider"
import IconChip from "@/components/TempDesignSystem/IconChip"
import useLang from "@/hooks/useLang"
@@ -15,11 +18,11 @@ import { formatPrice } from "@/utils/numberFormatting"
import PriceType from "../../PriceType"
import { hasModifiableRate } from "../../utils"
import ToggleSidePeek from "./ToggleSidePeek"
import styles from "./multiRoom.module.css"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import type { RoomCategories } from "@/types/hotel"
import type { Room } from "@/types/stores/my-stay"
import type { SafeUser } from "@/types/user"
@@ -27,9 +30,15 @@ interface MultiRoomProps {
booking: Room
roomNr: number
user: SafeUser
roomCategories: RoomCategories
}
export default function MultiRoom({ booking, roomNr, user }: MultiRoomProps) {
export default function MultiRoom({
booking,
roomNr,
user,
roomCategories,
}: MultiRoomProps) {
const intl = useIntl()
const lang = useLang()
@@ -42,7 +51,6 @@ export default function MultiRoom({ booking, roomNr, user }: MultiRoomProps) {
childrenAges,
confirmationNumber,
currencyCode,
hotelId,
packages,
rateDefinition,
room,
@@ -94,6 +102,7 @@ export default function MultiRoom({ booking, roomNr, user }: MultiRoomProps) {
breakfast.localPrice.currency
)
}
const hotelRoom = getBookedHotelRoom(roomCategories, roomTypeCode)
return (
<article className={styles.multiRoom}>
@@ -167,12 +176,21 @@ export default function MultiRoom({ booking, roomNr, user }: MultiRoomProps) {
</Typography>
</div>
<div className={styles.toggleSidePeek}>
<ToggleSidePeek
hotelId={hotelId}
roomTypeCode={roomTypeCode}
user={user}
confirmationNumber={confirmationNumber}
/>
<DialogTrigger>
<ButtonRAC
aria-label={intl.formatMessage({
defaultMessage: "View room details",
})}
className={styles.iconContainer}
>
<MaterialIcon icon="pan_zoom" color="CurrentColor" />
</ButtonRAC>
<BookedRoomSidePeek
hotelRoom={hotelRoom}
room={booking}
user={user}
/>
</DialogTrigger>
</div>
</div>
<div

View File

@@ -31,6 +31,13 @@
position: relative;
}
.iconContainer {
display: flex;
border: 1px solid var(--Base-Border-Subtle);
border-radius: var(--Corner-radius-Small);
padding: var(--Spacing-x-half);
}
.roomName {
color: var(--Scandic-Brand-Burgundy);
}

View File

@@ -1,6 +0,0 @@
.iconContainer {
display: flex;
border: 1px solid var(--Base-Border-Subtle);
border-radius: var(--Corner-radius-Small);
padding: var(--Spacing-x-half);
}

View File

@@ -1,4 +1,5 @@
"use client"
import { Button as ButtonRAC, DialogTrigger } from "react-aria-components"
import { useIntl } from "react-intl"
import DiscountIcon from "@scandic-hotels/design-system/Icons/DiscountIcon"
@@ -6,6 +7,7 @@ import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { dt } from "@/lib/dt"
import { getBookedHotelRoom } from "@/server/routers/booking/utils"
import { useMyStayStore } from "@/stores/my-stay"
import GuestDetails from "@/components/HotelReservation/MyStay/GuestDetails"
@@ -14,26 +16,31 @@ import PriceType from "@/components/HotelReservation/MyStay/PriceType"
import { hasModifiableRate } from "@/components/HotelReservation/MyStay/utils"
import { IconForFeatureCode } from "@/components/HotelReservation/utils"
import Image from "@/components/Image"
import BookedRoomSidePeek from "@/components/SidePeeks/BookedRoomSidePeek"
import SkeletonShimmer from "@/components/SkeletonShimmer"
import IconChip from "@/components/TempDesignSystem/IconChip"
import useLang from "@/hooks/useLang"
import { formatPrice } from "@/utils/numberFormatting"
import ToggleSidePeek from "./ToggleSidePeek"
import styles from "./room.module.css"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import type { Room } from "@/types/hotel"
import type { Room, RoomCategories } from "@/types/hotel"
import type { SafeUser } from "@/types/user"
interface RoomProps {
bedType: Room["roomTypes"][number]
image: Room["images"][number]
user: SafeUser
roomCategories: RoomCategories
}
export default function SingleRoom({ bedType, image, user }: RoomProps) {
export default function SingleRoom({
bedType,
image,
user,
roomCategories,
}: RoomProps) {
const intl = useIntl()
const lang = useLang()
@@ -46,7 +53,7 @@ export default function SingleRoom({ bedType, image, user }: RoomProps) {
childrenAges,
confirmationNumber,
formattedTotalPrice,
hotel,
guest,
isCancelled,
packages,
priceType,
@@ -57,10 +64,12 @@ export default function SingleRoom({ bedType, image, user }: RoomProps) {
roomTypeCode,
totalPrice,
vouchers,
bookedRoom,
} = useMyStayStore((state) => ({
adults: state.bookedRoom.adults,
bookingCode: state.bookedRoom.bookingCode,
breakfast: state.bookedRoom.breakfast,
guest: state.bookedRoom.guest,
checkInDate: state.bookedRoom.checkInDate,
cheques: state.bookedRoom.cheques,
childrenAges: state.bookedRoom.childrenAges,
@@ -77,6 +86,7 @@ export default function SingleRoom({ bedType, image, user }: RoomProps) {
roomTypeCode: state.bookedRoom.roomTypeCode,
totalPrice: state.bookedRoom.totalPrice,
vouchers: state.bookedRoom.vouchers,
bookedRoom: state.bookedRoom,
}))
if (!roomNumber) {
@@ -149,6 +159,8 @@ export default function SingleRoom({ bedType, image, user }: RoomProps) {
)
}
const hotelRoom = getBookedHotelRoom(roomCategories, roomTypeCode)
return (
<div>
<article className={styles.room}>
@@ -187,11 +199,27 @@ export default function SingleRoom({ bedType, image, user }: RoomProps) {
</div>
</div>
<div className={styles.sidePeek}>
<ToggleSidePeek
hotelId={hotel.operaId}
roomTypeCode={roomTypeCode}
intent="text"
/>
<DialogTrigger>
<Typography variant="Body/Supporting text (caption)/smBold">
<ButtonRAC className={styles.trigger}>
<span>
{intl.formatMessage({
defaultMessage: "View room details",
})}
</span>
<MaterialIcon
color="CurrentColor"
icon="chevron_right"
size={20}
/>
</ButtonRAC>
</Typography>
<BookedRoomSidePeek
hotelRoom={hotelRoom}
room={bookedRoom}
user={user}
/>
</DialogTrigger>
</div>
</div>
<div className={styles.booking}>
@@ -401,7 +429,12 @@ export default function SingleRoom({ bedType, image, user }: RoomProps) {
</div>
</div>
<div className={styles.guestDetailsDesktopWrapper}>
<GuestDetails user={user} />
<GuestDetails
confirmationNumber={confirmationNumber}
guest={guest}
isCancelled={isCancelled}
user={user}
/>
</div>
</div>
</div>
@@ -454,7 +487,12 @@ export default function SingleRoom({ bedType, image, user }: RoomProps) {
<PriceDetails />
<div className={styles.guestDetailsMobileWrapper}>
<GuestDetails user={user} />
<GuestDetails
confirmationNumber={confirmationNumber}
guest={guest}
isCancelled={isCancelled}
user={user}
/>
</div>
</article>
</div>

View File

@@ -6,6 +6,17 @@
padding: var(--Spacing-x3) 0;
}
.trigger {
align-items: center;
background: none;
border: none;
color: var(--Component-Button-Brand-Secondary-On-fill-Default);
cursor: pointer;
display: flex;
gap: var(--Space-x1);
padding: var(--Space-x025) 0;
}
.roomName {
color: var(--Scandic-Brand-Burgundy);
padding: 0 var(--Spacing-x2);

View File

@@ -20,12 +20,15 @@ interface RoomsProps {
export default function Rooms({ user }: RoomsProps) {
const intl = useIntl()
const { allRoomsAreCancelled, room, rooms } = useMyStayStore((state) => ({
allRoomsAreCancelled: state.allRoomsAreCancelled,
hotel: state.hotel,
room: state.bookedRoom.room,
rooms: state.rooms,
}))
const { allRoomsAreCancelled, room, rooms, roomCategories } = useMyStayStore(
(state) => ({
allRoomsAreCancelled: state.allRoomsAreCancelled,
hotel: state.hotel,
room: state.bookedRoom.room,
rooms: state.rooms,
roomCategories: state.roomCategories,
})
)
if (!room) {
return null
@@ -50,6 +53,7 @@ export default function Rooms({ user }: RoomsProps) {
bedType={room.bedType}
image={room.images[0]}
user={user}
roomCategories={roomCategories}
/>
) : (
<div className={styles.roomsContainer}>
@@ -58,7 +62,12 @@ export default function Rooms({ user }: RoomsProps) {
key={booking.confirmationNumber}
className={styles.roomWrapper}
>
<MultiRoom booking={booking} roomNr={index + 1} user={user} />
<MultiRoom
booking={booking}
roomNr={index + 1}
user={user}
roomCategories={roomCategories}
/>
</div>
))}
</div>

View File

@@ -1,20 +0,0 @@
import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums"
import type { Child } from "@/types/components/hotelReservation/selectRate/selectRate"
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
const bedTypeToMapEnum: Record<string, ChildBedMapEnum> = {
Crib: ChildBedMapEnum.IN_CRIB,
ExtraBed: ChildBedMapEnum.IN_EXTRA_BED,
ParentsBed: ChildBedMapEnum.IN_ADULTS_BED,
Unknown: ChildBedMapEnum.UNKNOWN,
}
export function convertToChildType(
childrenAges: number[],
childBedPreferences: BookingConfirmation["booking"]["childBedPreferences"]
): Child[] {
return childBedPreferences.map((preference, index) => ({
age: childrenAges[index],
bed: bedTypeToMapEnum[preference.bedType] ?? ChildBedMapEnum.UNKNOWN,
}))
}

View File

@@ -1,12 +0,0 @@
import { PriceTypeEnum } from "@/types/components/hotelReservation/myStay/myStay"
export function getPriceType(
cheques: number,
points: number,
vouchers: number
): PriceTypeEnum {
if (points > 0) return PriceTypeEnum.points
if (vouchers > 0) return PriceTypeEnum.voucher
if (cheques > 0) return PriceTypeEnum.cheque
return PriceTypeEnum.money
}

View File

@@ -1,9 +1,10 @@
import { BookingStatusEnum, CancellationRuleEnum } from "@/constants/booking"
import { dt } from "@/lib/dt"
import { convertToChildType } from "@/components/HotelReservation/utils/convertToChildType"
import { getPriceType } from "@/components/HotelReservation/utils/getPriceType"
import { formatChildBedPreferences } from "../utils"
import { convertToChildType } from "./convertToChildType"
import { getPriceType } from "./getPriceType"
import type { BreakfastPackage } from "@/types/components/hotelReservation/breakfast"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"