Merged in feat/SW-3289-replace-sidepeek-hotel-reservation (pull request #2686)

feat(SW-3289): replace sidepeek

* fix(SW-3289): replace sidepeek

* fix(SW-3289): add wrapping prop and change prop name to buttonVariant

* fix(SW-3289): replace body with typography

* fix(SW-3289): fix intl message


Approved-by: Joakim Jäderberg
This commit is contained in:
Bianca Widstam
2025-08-22 11:43:39 +00:00
parent e2544f9f89
commit d9b858c823
47 changed files with 527 additions and 708 deletions

View File

@@ -1,48 +0,0 @@
"use client"
import { DialogTrigger } from "react-aria-components"
import { useIntl } from "react-intl"
import { RoomSidePeekContent } from "@scandic-hotels/booking-flow/components/RoomSidePeekContent"
import { Button } from "@scandic-hotels/design-system/Button"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { getBookedHotelRoom } from "@scandic-hotels/trpc/routers/booking/helpers"
import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
import SidePeekSelfControlled from "@/components/TempDesignSystem/SidePeekSelfControlled"
interface RoomDetailsSidePeekProps {
roomTypeCode: string
}
export default function RoomDetailsSidePeek({
roomTypeCode,
}: RoomDetailsSidePeekProps) {
const { roomCategories } = useBookingConfirmationStore((state) => ({
roomCategories: state.roomCategories,
}))
const room = getBookedHotelRoom(roomCategories, roomTypeCode)
const intl = useIntl()
if (!room) {
return null
}
return (
<DialogTrigger>
<Button
variant="Text"
color="Primary"
size="Small"
typography="Body/Supporting text (caption)/smBold"
>
{intl.formatMessage({ defaultMessage: "View room details" })}
<MaterialIcon icon="chevron_right" size={14} color="CurrentColor" />
</Button>
<SidePeekSelfControlled title={room.name}>
<RoomSidePeekContent room={room} />
</SidePeekSelfControlled>
</DialogTrigger>
)
}

View File

@@ -11,13 +11,14 @@ import Caption from "@scandic-hotels/design-system/Caption"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import Image from "@scandic-hotels/design-system/Image"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { getHotelRoom } from "@scandic-hotels/trpc/routers/booking/helpers"
import { CancellationRuleEnum } from "@/constants/booking"
import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
import RoomDetailsSidePeek from "@/components/SidePeeks/RoomDetailsSidePeek"
import useLang from "@/hooks/useLang"
import RoomDetailsSidePeek from "./RoomDetailsSidePeek"
import styles from "./room.module.css"
import type { RoomProps } from "@/types/components/hotelReservation/bookingConfirmation/rooms/room"
@@ -31,6 +32,10 @@ export default function Room({
}: RoomProps) {
const intl = useIntl()
const lang = useLang()
const { roomCategories } = useBookingConfirmationStore((state) => ({
roomCategories: state.roomCategories,
}))
const room = getHotelRoom(roomCategories, booking.roomTypeCode)
const guestName = `${booking.guest.firstName} ${booking.guest.lastName}`
const fromDate = dt(booking.checkInDate).locale(lang)
@@ -116,7 +121,18 @@ export default function Room({
<Typography variant="Title/Subtitle/md">
<h2>{roomName}</h2>
</Typography>
<RoomDetailsSidePeek roomTypeCode={booking.roomTypeCode} />
{room && (
<RoomDetailsSidePeek
hotelId={booking.hotelId}
room={room}
roomTypeCode={booking.roomTypeCode}
buttonVariant="primary"
triggerLabel={intl.formatMessage({
defaultMessage: "View room details",
})}
wrapping={false}
/>
)}
</div>
<Typography variant="Body/Paragraph/mdRegular">
<ul className={styles.details}>

View File

@@ -1,41 +0,0 @@
"use client"
import { useIntl } from "react-intl"
import useSidePeekStore, {
SidePeekEnum,
} from "@scandic-hotels/booking-flow/stores/sidepeek"
import { Button } from "@scandic-hotels/design-system/Button"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { trackOpenSidePeekEvent } from "@/utils/tracking"
import type { ToggleSidePeekProps } from "@/types/components/hotelReservation/toggleSidePeekProps"
export default function ToggleSidePeek({ hotelId }: ToggleSidePeekProps) {
const intl = useIntl()
const openSidePeek = useSidePeekStore((state) => state.openSidePeek)
return (
<Button
onPress={() => {
openSidePeek({ key: SidePeekEnum.hotelDetails, hotelId })
trackOpenSidePeekEvent({
name: SidePeekEnum.hotelDetails,
hotelId,
includePathname: true,
})
}}
size="Small"
variant="Secondary"
color="Inverted"
wrapping
typography="Body/Paragraph/mdBold"
>
{intl.formatMessage({
defaultMessage: "See hotel details",
})}
<MaterialIcon icon="chevron_right" size={20} color="CurrentColor" />
</Button>
)
}

View File

@@ -2,17 +2,18 @@ import Image from "@scandic-hotels/design-system/Image"
import Title from "@scandic-hotels/design-system/Title"
import { Typography } from "@scandic-hotels/design-system/Typography"
import ToggleSidePeek from "./ToggleSidePeek"
import HotelDetailsSidePeek from "@/components/SidePeeks/HotelDetailsSidePeek"
import { getIntl } from "@/i18n"
import styles from "./header.module.css"
import type { HotelHeaderProps } from "@/types/components/hotelReservation/enterDetails/hotelHeader"
export default async function HotelHeader({
hotelData: { hotel },
hotelData: { hotel, url, restaurants, additionalData },
}: HotelHeaderProps) {
const image = hotel.hotelContent?.images
const intl = await getIntl()
const addressStr = `${hotel.address.streetAddress}, ${hotel.address.city}`
return (
@@ -49,7 +50,16 @@ export default async function HotelHeader({
<div className={styles.address}>{addressStr}</div>
</Typography>
</div>
<ToggleSidePeek hotelId={hotel.operaId} />
<HotelDetailsSidePeek
hotel={{ ...hotel, url: url }}
restaurants={restaurants}
additionalHotelData={additionalData}
triggerLabel={intl.formatMessage({
defaultMessage: "See hotel details",
})}
buttonVariant={"secondary"}
/>
</div>
</div>
</header>

View File

@@ -1,46 +0,0 @@
"use client"
import { useIntl } from "react-intl"
import useSidePeekStore, {
SidePeekEnum,
} from "@scandic-hotels/booking-flow/stores/sidepeek"
import { Button } from "@scandic-hotels/design-system/Button"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { trackOpenSidePeekEvent } from "@/utils/tracking"
import type { ToggleSidePeekProps } from "@/types/components/hotelReservation/toggleSidePeekProps"
export default function ToggleSidePeek({
hotelId,
roomTypeCode,
title,
}: ToggleSidePeekProps) {
const intl = useIntl()
const openSidePeek = useSidePeekStore((state) => state.openSidePeek)
return (
<Button
onPress={() => {
openSidePeek({ key: SidePeekEnum.roomDetails, hotelId, roomTypeCode })
trackOpenSidePeekEvent({
name: SidePeekEnum.roomDetails,
hotelId,
roomTypeCode,
includePathname: true,
})
}}
size="Small"
variant="Text"
wrapping
typography="Body/Paragraph/mdBold"
>
{title ||
intl.formatMessage({
defaultMessage: "See room details",
})}
<MaterialIcon icon="chevron_right" size={20} color="CurrentColor" />
</Button>
)
}

View File

@@ -9,14 +9,14 @@ import { Button } from "@scandic-hotels/design-system/Button"
import Footnote from "@scandic-hotels/design-system/Footnote"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import Subtitle from "@scandic-hotels/design-system/Subtitle"
import { getHotelRoom } from "@scandic-hotels/trpc/routers/booking/helpers"
import { useEnterDetailsStore } from "@/stores/enter-details"
import RoomDetailsSidePeek from "@/components/SidePeeks/RoomDetailsSidePeek"
import { useRoomContext } from "@/contexts/Details/Room"
import useLang from "@/hooks/useLang"
import ToggleSidePeek from "./ToggleSidePeek"
import styles from "./selectedRoom.module.css"
export default function SelectedRoom() {
@@ -25,10 +25,13 @@ export default function SelectedRoom() {
const router = useRouter()
const [isPending, startTransition] = useTransition()
const { room, idx } = useRoomContext()
const { hotelId, searchParamsStr } = useEnterDetailsStore((state) => ({
hotelId: state.booking.hotelId,
searchParamsStr: state.searchParamString,
}))
const { hotelId, roomCategories, searchParamsStr } = useEnterDetailsStore(
(state) => ({
hotelId: state.booking.hotelId,
roomCategories: state.roomCategories,
searchParamsStr: state.searchParamString,
})
)
function changeRoom() {
const searchParams = new URLSearchParams(searchParamsStr)
@@ -39,6 +42,8 @@ export default function SelectedRoom() {
})
}
const selectedRoom = getHotelRoom(roomCategories, room.roomTypeCode)
return (
<div className={styles.wrapper} data-available={room.isAvailable}>
<div className={styles.main}>
@@ -79,6 +84,7 @@ export default function SelectedRoom() {
size="Small"
onPress={changeRoom}
isDisabled={isPending}
wrapping={false}
typography="Body/Supporting text (caption)/smBold"
>
<MaterialIcon icon="edit_square" size={20} color="CurrentColor" />
@@ -87,11 +93,17 @@ export default function SelectedRoom() {
})}
</Button>
</div>
{room.roomTypeCode && (
{room.roomTypeCode && selectedRoom && (
<div className={styles.details}>
<ToggleSidePeek
<RoomDetailsSidePeek
hotelId={hotelId}
roomTypeCode={room.roomTypeCode}
room={selectedRoom}
buttonVariant="primary"
triggerLabel={intl.formatMessage({
defaultMessage: "See room details",
})}
wrapping={false}
/>
</div>
)}

View File

@@ -48,6 +48,7 @@
.details {
display: flex;
justify-content: flex-start;
margin-top: var(--Space-x05);
}
@media screen and (min-width: 768px) {

View File

@@ -10,9 +10,7 @@ import {
import { memo } from "react"
import { useIntl } from "react-intl"
import OpenSidePeekButton from "@scandic-hotels/booking-flow/components/OpenSidePeekButton"
import TripAdvisorChip from "@scandic-hotels/booking-flow/components/TripAdvisorChip"
import { SidePeekEnum } from "@scandic-hotels/booking-flow/stores/sidepeek"
import {
alternativeHotelsMap,
selectHotelMap,
@@ -30,6 +28,7 @@ import { useHotelsMapStore } from "@/stores/hotels-map"
import BookingCodeChip from "@/components/BookingCodeChip"
import { FacilityToIcon } from "@/components/ContentType/HotelPage/data"
import HotelDetailsSidePeek from "@/components/SidePeeks/HotelDetailsSidePeek"
import { mapApiImagesToGalleryImages } from "@/utils/imageGallery"
import HotelChequeCard from "./HotelChequeCard"
@@ -47,7 +46,7 @@ import { HotelCardListingTypeEnum } from "@/types/components/hotelReservation/se
import type { HotelCardProps } from "@/types/components/hotelReservation/selectHotel/hotelCardProps"
function HotelCard({
hotelData: { availability, hotel },
hotelData: { availability, hotel, additionalData, restaurants, url },
isUserLoggedIn,
state = "default",
type = HotelCardListingTypeEnum.PageListing,
@@ -184,13 +183,14 @@ function HotelCard({
</div>
))}
</div>
<OpenSidePeekButton
label={intl.formatMessage({
<HotelDetailsSidePeek
hotel={{ ...hotel, url: url }}
restaurants={restaurants}
additionalHotelData={additionalData}
triggerLabel={intl.formatMessage({
defaultMessage: "See hotel details",
})}
hotelId={hotel.operaId}
showCTA={true}
sidePeekKey={SidePeekEnum.hotelDetails}
buttonVariant="primary"
/>
</div>
<PricesWrapper

View File

@@ -4,9 +4,11 @@ import { Button as ButtonRAC, DialogTrigger } from "react-aria-components"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import BookedRoomSidePeek from "@/components/SidePeeks/BookedRoomSidePeek"
import { trackOpenSidePeekEvent } from "@/utils/tracking"
import styles from "./sidePeek.module.css"
import { SidePeekEnum } from "@/types/sidepeek"
import type { Room as MyStayRoom } from "@/types/stores/my-stay"
import type { SafeUser } from "@/types/user"
@@ -21,7 +23,16 @@ export default function RoomDetailsSidePeek({
}: RoomDetailsSidePeekProps) {
return (
<DialogTrigger>
<ButtonRAC className={styles.trigger}>
<ButtonRAC
className={styles.trigger}
onPress={() => {
trackOpenSidePeekEvent({
name: SidePeekEnum.bookedRoomDetails,
hotelId: booking.hotelId,
includePathname: true,
})
}}
>
<MaterialIcon icon="pan_zoom" color="CurrentColor" />
</ButtonRAC>
<BookedRoomSidePeek hotelRoom={booking.room} room={booking} user={user} />

View File

@@ -9,7 +9,9 @@ import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton
import { useMyStayStore } from "@/stores/my-stay"
import BookedRoomSidePeek from "@/components/SidePeeks/BookedRoomSidePeek"
import { trackOpenSidePeekEvent } from "@/utils/tracking"
import { SidePeekEnum } from "@/types/sidepeek"
import type { SafeUser } from "@/types/user"
interface RoomDetailsSidePeekProps {
@@ -21,9 +23,23 @@ export default function RoomDetailsSidePeek({
}: RoomDetailsSidePeekProps) {
const intl = useIntl()
const bookedRoom = useMyStayStore((state) => state.bookedRoom)
return (
<DialogTrigger>
<Button intent="text" size="small" theme="base" variant="icon" wrapping>
<Button
intent="text"
size="small"
theme="base"
variant="icon"
wrapping
onPress={() => {
trackOpenSidePeekEvent({
name: SidePeekEnum.bookedRoomDetails,
hotelId: bookedRoom.hotelId,
includePathname: true,
})
}}
>
{intl.formatMessage({ defaultMessage: "See room details" })}
<MaterialIcon icon="chevron_right" size={14} color="CurrentColor" />
</Button>

View File

@@ -1,7 +1,6 @@
import { cookies } from "next/headers"
import { notFound } from "next/navigation"
import SidePeek from "@scandic-hotels/booking-flow/components/HotelReservationSidePeek"
import { dt } from "@scandic-hotels/common/dt"
import { logger } from "@scandic-hotels/common/logger"
import * as maskValue from "@scandic-hotels/common/utils/maskValue"
@@ -268,7 +267,6 @@ export default async function MyStay(props: {
)}
</div>
</main>
<SidePeek />
</MyStayProvider>
)
}

View File

@@ -9,7 +9,11 @@ import { getLang } from "@/i18n/serverContext"
import type { HotelsAvailabilityItem } from "@scandic-hotels/trpc/types/availability"
import type { Child } from "@scandic-hotels/trpc/types/child"
import type { AdditionalData, Hotel } from "@scandic-hotels/trpc/types/hotel"
import type {
AdditionalData,
Hotel,
Restaurant,
} from "@scandic-hotels/trpc/types/hotel"
import type {
HotelLocation,
Location,
@@ -32,6 +36,8 @@ export interface HotelResponse {
availability: HotelsAvailabilityItem
hotel: Hotel
additionalData: AdditionalData
url: string | null
restaurants: Restaurant[]
}
type Result = AvailabilityResponse | null
@@ -55,6 +61,8 @@ async function enhanceHotels(hotels: HotelsAvailabilityItem[]) {
availability,
hotel: hotelData.hotel,
additionalData: hotelData.additionalData,
url: hotelData.url,
restaurants: hotelData.restaurants,
}
})
)

View File

@@ -4,25 +4,32 @@ import { useState } from "react"
import { Button as ButtonRAC } from "react-aria-components"
import { useIntl } from "react-intl"
import OpenSidePeekButton from "@scandic-hotels/booking-flow/components/OpenSidePeekButton"
import { SidePeekEnum } from "@scandic-hotels/booking-flow/stores/sidepeek"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { FacilityToIcon } from "@/components/ContentType/HotelPage/data"
import HotelDetailsSidePeek from "@/components/SidePeeks/HotelDetailsSidePeek"
import Alert from "@/components/TempDesignSystem/Alert"
import styles from "./hotelDescription.module.css"
import type { Hotel } from "@scandic-hotels/trpc/types/hotel"
import type {
AdditionalData,
Hotel,
Restaurant,
} from "@scandic-hotels/trpc/types/hotel"
export default function HotelDescription({
description,
hotel,
sortedFacilities,
restaurants,
additionalData,
}: {
description?: string
hotel: Hotel
sortedFacilities: Hotel["detailedFacilities"]
restaurants: Restaurant[]
additionalData: AdditionalData | undefined
}) {
const intl = useIntl()
@@ -69,13 +76,14 @@ export default function HotelDescription({
{expanded && (
<div className={styles.expandedContent}>
<OpenSidePeekButton
label={intl.formatMessage({
<HotelDetailsSidePeek
hotel={{ ...hotel, url: null }}
restaurants={restaurants}
additionalHotelData={additionalData}
triggerLabel={intl.formatMessage({
defaultMessage: "See all amenities",
})}
hotelId={hotel.operaId}
showCTA={false}
sidePeekKey={SidePeekEnum.hotelDetails}
buttonVariant="primary"
/>
{hotel.specialAlerts.map((alert) => (
<Alert

View File

@@ -1,6 +1,4 @@
import OpenSidePeekButton from "@scandic-hotels/booking-flow/components/OpenSidePeekButton"
import TripAdvisorChip from "@scandic-hotels/booking-flow/components/TripAdvisorChip"
import { SidePeekEnum } from "@scandic-hotels/booking-flow/stores/sidepeek"
import { dt } from "@scandic-hotels/common/dt"
import { getSingleDecimal } from "@scandic-hotels/common/utils/numberFormatting"
import { Divider } from "@scandic-hotels/design-system/Divider"
@@ -9,6 +7,7 @@ import SkeletonShimmer from "@scandic-hotels/design-system/SkeletonShimmer"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { FacilityToIcon } from "@/components/ContentType/HotelPage/data"
import HotelDetailsSidePeek from "@/components/SidePeeks/HotelDetailsSidePeek"
import Alert from "@/components/TempDesignSystem/Alert"
import { getIntl } from "@/i18n"
import { mapApiImagesToGalleryImages } from "@/utils/imageGallery"
@@ -18,16 +17,27 @@ import HotelDescription from "./HotelDescription"
import styles from "./hotelInfoCard.module.css"
import type { Hotel } from "@scandic-hotels/trpc/types/hotel"
import type {
AdditionalData,
Hotel,
Restaurant,
} from "@scandic-hotels/trpc/types/hotel"
import type { SelectRateBooking } from "@/types/components/hotelReservation/selectRate/selectRate"
export type HotelInfoCardProps = {
booking: SelectRateBooking
hotel: Hotel
restaurants: Restaurant[]
additionalData: AdditionalData | undefined
}
export async function HotelInfoCard({ booking, hotel }: HotelInfoCardProps) {
export async function HotelInfoCard({
booking,
hotel,
restaurants,
additionalData,
}: HotelInfoCardProps) {
const intl = await getIntl()
const sortedFacilities = hotel.detailedFacilities
@@ -85,6 +95,8 @@ export async function HotelInfoCard({ booking, hotel }: HotelInfoCardProps) {
key={hotel.operaId}
description={hotel.hotelContent.texts.descriptions?.medium}
hotel={hotel}
restaurants={restaurants}
additionalData={additionalData}
sortedFacilities={sortedFacilities}
/>
</div>
@@ -101,13 +113,14 @@ export async function HotelInfoCard({ booking, hotel }: HotelInfoCardProps) {
</div>
))}
</div>
<OpenSidePeekButton
label={intl.formatMessage({
<HotelDetailsSidePeek
hotel={{ ...hotel, url: null }}
restaurants={restaurants}
additionalHotelData={additionalData}
triggerLabel={intl.formatMessage({
defaultMessage: "See all amenities",
})}
hotelId={hotel.operaId}
showCTA={false}
sidePeekKey={SidePeekEnum.hotelDetails}
buttonVariant="primary"
/>
</div>
</div>

View File

@@ -1,50 +0,0 @@
"use client"
import { useIntl } from "react-intl"
import useSidePeekStore, {
SidePeekEnum,
} from "@scandic-hotels/booking-flow/stores/sidepeek"
import { Button } from "@scandic-hotels/design-system/Button"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { trackOpenSidePeekEvent } from "@/utils/tracking"
import styles from "./details.module.css"
import type { ToggleSidePeekProps } from "@/types/components/hotelReservation/toggleSidePeekProps"
export default function ToggleSidePeek({
hotelId,
roomTypeCode,
}: ToggleSidePeekProps) {
const intl = useIntl()
const openSidePeek = useSidePeekStore((state) => state.openSidePeek)
return (
<Button
onClick={() => {
openSidePeek({
key: SidePeekEnum.roomDetails,
hotelId,
roomTypeCode,
})
trackOpenSidePeekEvent({
name: SidePeekEnum.roomDetails,
hotelId,
roomTypeCode,
includePathname: true,
})
}}
size="Small"
variant="Text"
wrapping
typography="Body/Supporting text (caption)/smBold"
color="Inverted"
className={styles.sidePeekButton}
>
{intl.formatMessage({ defaultMessage: "View room details" })}
<MaterialIcon icon="chevron_right" size={20} color="CurrentColor" />
</Button>
)
}

View File

@@ -4,12 +4,13 @@ import { useIntl } from "react-intl"
import ImageGallery from "@scandic-hotels/design-system/ImageGallery"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { getHotelRoom } from "@scandic-hotels/trpc/routers/booking/helpers"
import { IconForFeatureCode } from "@/components/HotelReservation/utils"
import RoomDetailsSidePeek from "@/components/SidePeeks/RoomDetailsSidePeek"
import { useSelectRateContext } from "@/contexts/SelectRate/SelectRateContext"
import { mapApiImagesToGalleryImages } from "@/utils/imageGallery"
import ToggleSidePeek from "../Details/ToggleSidePeek"
import styles from "./image.module.css"
import type { ApiImage } from "@scandic-hotels/trpc/types/hotel"
@@ -34,7 +35,9 @@ const RoomImage = memo(function RoomImage({
hotelId,
}: RoomListItemImageProps) {
const galleryImages = mapApiImagesToGalleryImages(images || [])
const { hotel } = useSelectRateContext()
const room = getHotelRoom(hotel?.data?.roomCategories ?? [], roomTypeCode)
const intl = useIntl()
return (
<div className={styles.imageContainer}>
<div className={styles.chipContainer}>
@@ -53,8 +56,16 @@ const RoomImage = memo(function RoomImage({
imageCountPosition="top"
/>
<div className={styles.toggleSidePeek}>
{roomTypeCode && (
<ToggleSidePeek hotelId={hotelId} roomTypeCode={roomTypeCode} />
{roomTypeCode && room && (
<RoomDetailsSidePeek
hotelId={hotelId}
room={room}
roomTypeCode={roomTypeCode}
triggerLabel={intl.formatMessage({
defaultMessage: "View room details",
})}
buttonVariant="secondary"
/>
)}
</div>
</div>

View File

@@ -29,7 +29,12 @@ export default async function SelectRatePage({
}
return (
<>
<HotelInfoCard hotel={hotelData.hotel} booking={booking} />
<HotelInfoCard
hotel={hotelData.hotel}
restaurants={hotelData.restaurants}
additionalData={hotelData.additionalData}
booking={booking}
/>
{isInValidFNF ? (
<FnFNotAllowedAlert />