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

@@ -125,6 +125,7 @@ export default async function DetailsPage(
searchParamsStr={selectRoomParams.toString()}
user={user}
vat={hotel.vat}
roomCategories={hotelData.roomCategories}
>
<HotelHeader hotelData={hotelData} />
<div className={styles.container}>

View File

@@ -1,5 +1,3 @@
import SidePeek from "@scandic-hotels/booking-flow/components/HotelReservationSidePeek"
import styles from "./layout.module.css"
import type { LangParams, LayoutArgs } from "@/types/params"
@@ -7,10 +5,5 @@ import type { LangParams, LayoutArgs } from "@/types/params"
export default function StandardHotelReservationLayout({
children,
}: React.PropsWithChildren<LayoutArgs<LangParams>>) {
return (
<div className={styles.layout}>
{children}
<SidePeek />
</div>
)
return <div className={styles.layout}>{children}</div>
}

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

View File

@@ -0,0 +1,107 @@
import { useIntl } from "react-intl"
import AdditionalAmenities from "@scandic-hotels/booking-flow/components/AdditionalAmenities"
import Contact from "@scandic-hotels/booking-flow/components/Contact"
import BreakfastAccordionItem from "@scandic-hotels/booking-flow/components/SidePeekAccordions/BreakfastAccordionItem"
import CheckInCheckOutAccordionItem from "@scandic-hotels/booking-flow/components/SidePeekAccordions/CheckInCheckOutAccordionItem"
import ParkingAccordionItem from "@scandic-hotels/booking-flow/components/SidePeekAccordions/ParkingAccordionItem"
import Accordion from "@scandic-hotels/design-system/Accordion"
import AccordionItem from "@scandic-hotels/design-system/Accordion/AccordionItem"
import ButtonLink from "@scandic-hotels/design-system/ButtonLink"
import { IconName } from "@scandic-hotels/design-system/Icons/iconName"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { trackAccordionClick } from "@/utils/tracking"
import styles from "./hotelSidePeek.module.css"
import type {
AdditionalData,
Hotel,
Restaurant,
} from "@scandic-hotels/trpc/types/hotel"
interface HotelSidePeekContentProps {
hotel: Hotel & { url: string | null }
restaurants: Restaurant[]
additionalHotelData: AdditionalData | undefined
}
export function HotelSidePeekContent({
hotel,
restaurants,
additionalHotelData,
}: HotelSidePeekContentProps) {
const intl = useIntl()
return (
<div className={styles.content}>
<Typography variant="Title/Subtitle/lg">
<h3>
{intl.formatMessage({ defaultMessage: "Practical information" })}
</h3>
</Typography>
<Contact hotel={hotel} />
<Accordion>
<ParkingAccordionItem
parking={hotel.parking}
elevatorPitch={additionalHotelData?.hotelParking.elevatorPitch}
/>
<BreakfastAccordionItem
restaurants={restaurants}
hotelType={hotel.hotelType}
/>
<CheckInCheckOutAccordionItem checkInData={hotel.hotelFacts.checkin} />
<AccessibilityAccordionItem
elevatorPitch={additionalHotelData?.hotelSpecialNeeds.elevatorPitch}
/>
<AdditionalAmenities amenities={hotel.detailedFacilities} />
</Accordion>
{hotel.url ? (
<ButtonLink
href={hotel.url}
variant="Secondary"
size="Medium"
typography="Body/Paragraph/mdBold"
>
{intl.formatMessage({
defaultMessage: "Read more about the hotel",
})}
</ButtonLink>
) : null}
</div>
)
}
type AccessibilityAccordionItemProps = {
elevatorPitch?: string
}
function AccessibilityAccordionItem({
elevatorPitch,
}: AccessibilityAccordionItemProps) {
const intl = useIntl()
if (!elevatorPitch) {
return null
}
return (
<AccordionItem
title={intl.formatMessage({
defaultMessage: "Accessibility",
})}
iconName={IconName.Accessibility}
className={styles.accordionItem}
variant="sidepeek"
onOpen={() => trackAccordionClick("amenities:accessibility")}
>
<div className={styles.accessibilityContent}>
<Typography variant="Body/Paragraph/mdRegular">
<p>{elevatorPitch}</p>
</Typography>
</div>
</AccordionItem>
)
}

View File

@@ -0,0 +1,89 @@
"use client"
import { DialogTrigger } from "react-aria-components"
import { Button } from "@scandic-hotels/design-system/Button"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import SidePeekSelfControlled from "@/components/TempDesignSystem/SidePeekSelfControlled"
import { trackOpenSidePeekEvent } from "@/utils/tracking"
import { HotelSidePeekContent } from "./HotelSidePeekContent"
import type {
AdditionalData,
Hotel,
Restaurant,
} from "@scandic-hotels/trpc/types/hotel"
import { SidePeekEnum } from "@/types/sidepeek"
interface HotelDetailsSidePeekProps {
hotel: Hotel & { url: string | null }
restaurants: Restaurant[]
additionalHotelData: AdditionalData | undefined
triggerLabel: string
buttonVariant: "primary" | "secondary"
}
const buttonPropsMap: Record<
HotelDetailsSidePeekProps["buttonVariant"],
Pick<
React.ComponentProps<typeof Button>,
"variant" | "color" | "size" | "typography"
>
> = {
primary: {
variant: "Text",
color: "Primary",
size: "Medium",
typography: "Body/Paragraph/mdBold",
},
secondary: {
variant: "Secondary",
color: "Inverted",
size: "Small",
typography: "Body/Supporting text (caption)/smBold",
},
}
export default function HotelDetailsSidePeek({
hotel,
restaurants,
additionalHotelData,
triggerLabel,
buttonVariant,
}: HotelDetailsSidePeekProps) {
const buttonProps = buttonPropsMap[buttonVariant]
return (
<DialogTrigger>
<Button
{...buttonProps}
wrapping
onPress={() =>
trackOpenSidePeekEvent({
name: SidePeekEnum.hotelDetails,
hotelId: hotel.operaId,
includePathname: true,
})
}
>
{triggerLabel}
<MaterialIcon
icon="chevron_right"
size={buttonVariant === "primary" ? 24 : 20}
color="CurrentColor"
/>
</Button>
<SidePeekSelfControlled title={hotel.name}>
<HotelSidePeekContent
hotel={hotel}
restaurants={restaurants}
additionalHotelData={additionalHotelData}
/>
</SidePeekSelfControlled>
</DialogTrigger>
)
}

View File

@@ -1,11 +1,13 @@
import { useIntl } from "react-intl"
import {
BED_TYPE_ICONS,
type BedTypes,
} from "@scandic-hotels/booking-flow/bedTypeIcons"
import { FacilityIcon } from "@scandic-hotels/design-system/Icons/FacilityIcon"
import ImageGallery from "@scandic-hotels/design-system/ImageGallery"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { BED_TYPE_ICONS, type BedTypes } from "../../../misc/bedTypeIcons"
import styles from "./roomSidePeekContent.module.css"
import type { ApiImage, Room } from "@scandic-hotels/trpc/types/hotel"
@@ -79,7 +81,7 @@ export function RoomSidePeekContent({ room }: RoomSidePeekContentProps) {
</Typography>
<Typography variant="Body/Paragraph/mdRegular">
<ul className={styles.facilityList}>
{room.roomFacilities
{[...room.roomFacilities]
.sort((a, b) => a.sortOrder - b.sortOrder)
.map((facility) => {
return (

View File

@@ -0,0 +1,80 @@
"use client"
import { DialogTrigger } from "react-aria-components"
import { Button } from "@scandic-hotels/design-system/Button"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import SidePeekSelfControlled from "@/components/TempDesignSystem/SidePeekSelfControlled"
import { trackOpenSidePeekEvent } from "@/utils/tracking"
import { RoomSidePeekContent } from "./RoomSidePeekContent"
import type { Room } from "@scandic-hotels/trpc/types/hotel"
import { SidePeekEnum } from "@/types/sidepeek"
interface RoomDetailsSidePeekProps {
hotelId: string
room: Room
triggerLabel: string
roomTypeCode?: string
buttonVariant?: "primary" | "secondary"
wrapping?: boolean
}
const buttonPropsMap: Record<
NonNullable<RoomDetailsSidePeekProps["buttonVariant"]>,
Pick<
React.ComponentProps<typeof Button>,
"variant" | "color" | "size" | "typography"
>
> = {
primary: {
variant: "Text",
color: "Primary",
size: "Medium",
typography: "Body/Paragraph/mdBold",
},
secondary: {
variant: "Text",
color: "Inverted",
size: "Small",
typography: "Body/Supporting text (caption)/smBold",
},
}
export default function RoomDetailsSidePeek({
hotelId,
room,
roomTypeCode,
triggerLabel,
wrapping = true,
buttonVariant: variant = "primary",
}: RoomDetailsSidePeekProps) {
const buttonProps = buttonPropsMap[variant]
return (
<DialogTrigger>
<Button
{...buttonProps}
wrapping={wrapping}
onPress={() =>
trackOpenSidePeekEvent({
name: SidePeekEnum.roomDetails,
hotelId,
roomTypeCode,
includePathname: true,
})
}
>
{triggerLabel}
<MaterialIcon icon="chevron_right" size={20} color="CurrentColor" />
</Button>
<SidePeekSelfControlled title={room.name}>
<RoomSidePeekContent room={room} />
</SidePeekSelfControlled>
</DialogTrigger>
)
}

View File

@@ -4,8 +4,8 @@ import { useEffect } from "react"
import { Dialog, Modal, ModalOverlay } from "react-aria-components"
import { useIntl } from "react-intl"
import { IconButton } from "@scandic-hotels/design-system/IconButton"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton"
import { Typography } from "@scandic-hotels/design-system/Typography"
import useSetOverflowVisibleOnRA from "@/hooks/useSetOverflowVisibleOnRA"
@@ -34,19 +34,20 @@ export default function SidePeekSelfControlled({
<h2>{title}</h2>
</Typography>
) : null}
<Button
<IconButton
theme="Black"
style="Muted"
onPress={close}
aria-label={intl.formatMessage({
defaultMessage: "Close",
})}
className={styles.closeButton}
intent="text"
onPress={close}
>
<MaterialIcon
icon="close"
size={24}
color="Icon/Interactive/Default"
/>
</Button>
</IconButton>
</header>
<div className={styles.sidePeekContent}>{children}</div>
<KeepBodyVisible />

View File

@@ -72,10 +72,6 @@
justify-content: space-between;
}
.closeButton {
padding: 0;
}
.heading {
color: var(--Text-Heading);
}

View File

@@ -32,6 +32,7 @@ export default function EnterDetailsProvider({
searchParamsStr,
user,
vat,
roomCategories,
}: DetailsProviderProps) {
// This state is needed to be able to use defaultValues for
// react-hook-form since values needs to be there on mount
@@ -70,6 +71,7 @@ export default function EnterDetailsProvider({
isFlexRate: room.isFlexRate,
})),
vat,
roomCategories,
}
storeRef.current = createDetailsStore(

View File

@@ -97,6 +97,7 @@ export function createDetailsStore(
return create<DetailsState>()((set) => ({
availableBeds,
booking: initialState.booking,
roomCategories: initialState.roomCategories,
breakfastPackages,
canProceedToPayment: false,
isSubmitting: false,

View File

@@ -3,7 +3,7 @@ import { produce } from "immer"
import { useContext } from "react"
import { create, useStore } from "zustand"
import { getBookedHotelRoom } from "@scandic-hotels/trpc/routers/booking/helpers"
import { getHotelRoom } from "@scandic-hotels/trpc/routers/booking/helpers"
import { mapRoomDetails } from "@/components/HotelReservation/MyStay/utils/mapRoomDetails"
import { MyStayContext } from "@/contexts/MyStay"
@@ -39,7 +39,7 @@ export function createMyStayStore({
}
const mappedRooms = rooms.map((booking, idx) => {
const room = getBookedHotelRoom(roomCategories, booking.roomTypeCode)
const room = getHotelRoom(roomCategories, booking.roomTypeCode)
return mapRoomDetails({
booking,
rates,

View File

@@ -1,14 +0,0 @@
import type { SidePeekEnum } from "@scandic-hotels/booking-flow/stores/sidepeek"
import type {
AdditionalData,
Hotel,
Restaurant,
} from "@scandic-hotels/trpc/types/hotel"
export type AmenitiesSidePeekProps = {
hotel: Hotel & { url: string | null }
restaurants: Restaurant[]
additionalHotelData: AdditionalData | undefined
activeSidePeek: SidePeekEnum
close: () => void
}

View File

@@ -1,5 +1,5 @@
import type { HotelData } from "@scandic-hotels/trpc/types/hotel"
export interface HotelHeaderProps {
hotelData: HotelData
hotelData: HotelData & { url: string | null }
}

View File

@@ -1,9 +0,0 @@
import type { SafeUser } from "@/types/user"
export type ToggleSidePeekProps = {
hotelId: string
roomTypeCode?: string
title?: string
user?: SafeUser
confirmationNumber?: string
}

View File

@@ -1,4 +1,5 @@
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { RoomCategories } from "@scandic-hotels/trpc/types/hotel"
import type { Room } from "@scandic-hotels/trpc/types/room"
import type { SafeUser } from "@/types/user"
@@ -13,4 +14,5 @@ export interface DetailsProviderProps extends React.PropsWithChildren {
searchParamsStr: string
user: SafeUser
vat: number
roomCategories: RoomCategories
}

View File

@@ -0,0 +1,5 @@
export enum SidePeekEnum {
hotelDetails = "hotel-detail-side-peek",
roomDetails = "room-detail-side-peek",
bookedRoomDetails = "booked-room-detail-side-peek",
}

View File

@@ -1,6 +1,7 @@
import type { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import type { BedTypeSelection } from "@scandic-hotels/trpc/types/bedTypeSelection"
import type { Child } from "@scandic-hotels/trpc/types/child"
import type { RoomCategories } from "@scandic-hotels/trpc/types/hotel"
import type { Packages } from "@scandic-hotels/trpc/types/packages"
import type {
@@ -80,6 +81,7 @@ export type InitialState = {
booking: DetailsBooking
rooms: InitialRoomData[]
vat: number
roomCategories: RoomCategories
}
export interface DetailsState {
@@ -100,6 +102,7 @@ export interface DetailsState {
searchParamString: string
totalPrice: Price
vat: number
roomCategories: RoomCategories
defaultCurrency: CurrencyEnum
preSubmitCallbacks: Record<string, () => void>
}

View File

@@ -59,9 +59,3 @@
flex-direction: column;
justify-content: center;
}
.link {
text-decoration: underline;
font-family: var(--typography-Body-Regular-fontFamily);
color: var(--Text-Interactive-Secondary);
}

View File

@@ -2,7 +2,6 @@
import { useIntl } from "react-intl"
import Body from "@scandic-hotels/design-system/Body"
import FacebookIcon from "@scandic-hotels/design-system/Icons/FacebookIcon"
import InstagramIcon from "@scandic-hotels/design-system/Icons/InstagramIcon"
import Image from "@scandic-hotels/design-system/Image"
@@ -13,6 +12,7 @@ import useLang from "../../hooks/useLang"
import styles from "./contact.module.css"
import type { Hotel } from "@scandic-hotels/trpc/types/hotel"
import { Typography } from "@scandic-hotels/design-system/Typography"
interface ContactProps {
hotel: Hotel
@@ -30,55 +30,67 @@ export default function Contact({ hotel }: ContactProps) {
<address className={styles.address}>
<ul className={styles.contactInfo}>
<li>
<Body textTransform="bold">
{intl.formatMessage({
defaultMessage: "Address",
})}
</Body>
<Body>
{addressStr}
<br />
{cityStr}
</Body>
<Typography variant="Body/Paragraph/mdBold">
<p>
{intl.formatMessage({
defaultMessage: "Address",
})}
</p>
</Typography>
<Typography variant="Body/Paragraph/mdRegular">
<p>
{addressStr}
<br />
{cityStr}
</p>
</Typography>
</li>
<li>
<Body textTransform="bold">
{intl.formatMessage({
defaultMessage: "Driving directions",
})}
</Body>
<Typography variant="Body/Paragraph/mdBold">
<p>
{intl.formatMessage({
defaultMessage: "Driving directions",
})}
</p>
</Typography>
<Link
href={`https://www.google.com/maps/dir/?api=1&destination=${encodeURIComponent(
`${hotel.name}, ${hotel.address.streetAddress}, ${hotel.address.zipCode} ${hotel.address.city}`
)}`}
>
<span className={styles.link}>
{intl.formatMessage({
defaultMessage: "Google Maps",
})}
</span>
<Typography variant="Body/Underline/md">
<p>
{intl.formatMessage({
defaultMessage: "Google Maps",
})}
</p>
</Typography>
</Link>
</li>
<li>
<Body textTransform="bold">
{intl.formatMessage({
defaultMessage: "Contact us",
})}
</Body>
<Typography variant="Body/Paragraph/mdBold">
<p>
{intl.formatMessage({
defaultMessage: "Contact us",
})}
</p>
</Typography>
<Link href={`tel:${hotel.contactInformation.phoneNumber}`}>
<span className={styles.link}>
{hotel.contactInformation.phoneNumber}
</span>
<Typography variant="Body/Underline/md">
<p>{hotel.contactInformation.phoneNumber}</p>
</Typography>
</Link>
</li>
<li>
{(hotel.socialMedia.facebook || hotel.socialMedia.instagram) && (
<>
<Body textTransform="bold">
{intl.formatMessage({
defaultMessage: "Follow us",
})}
</Body>
<Typography variant="Body/Paragraph/mdBold">
<p>
{intl.formatMessage({
defaultMessage: "Follow us",
})}
</p>
</Typography>
<div className={styles.soMeIcons}>
{hotel.socialMedia.instagram && (
<Link href={hotel.socialMedia.instagram} target="_blank">
@@ -95,15 +107,17 @@ export default function Contact({ hotel }: ContactProps) {
)}
</li>
<li>
<Body textTransform="bold">
{intl.formatMessage({
defaultMessage: "Email",
})}
</Body>
<Typography variant="Body/Paragraph/mdBold">
<p>
{intl.formatMessage({
defaultMessage: "Email",
})}
</p>
</Typography>
<Link href={`mailto:${hotel.contactInformation.email}`}>
<span className={styles.link}>
{hotel.contactInformation.email}
</span>
<Typography variant="Body/Underline/md">
<p>{hotel.contactInformation.email}</p>
</Typography>
</Link>
</li>
</ul>

View File

@@ -1,5 +0,0 @@
.spacing {
display: flex;
flex-direction: column;
gap: var(--Spacing-x2);
}

View File

@@ -1,84 +0,0 @@
"use client"
import { useEffect } from "react"
import { trpc } from "@scandic-hotels/trpc/client"
import useLang from "../../hooks/useLang"
import useSidePeekStore from "../../stores/sidepeek"
import HotelSidePeek from "../HotelSidePeek"
import RoomSidePeek from "../RoomSidePeek"
export default function HotelReservationSidePeek() {
const { activeSidePeek, hotelId, roomTypeCode, showCTA } = useSidePeekStore(
(state) => ({
activeSidePeek: state.activeSidePeek,
hotelId: state.hotelId,
roomTypeCode: state.roomTypeCode,
showCTA: state.showCTA,
})
)
const closeFn = useSidePeekStore((state) => state.closeSidePeek)
const lang = useLang()
const { data: hotelData } = trpc.hotel.get.useQuery(
{
hotelId: hotelId ?? "",
language: lang,
isCardOnlyPayment: false,
},
{
enabled: !!hotelId,
}
)
const selectedRoom = hotelData?.roomCategories.find((room) =>
room.roomTypes.some((type) => type.code === roomTypeCode)
)
useEffect(() => {
if (activeSidePeek) {
window.history.pushState(null, "", window.location.href)
}
}, [activeSidePeek])
useEffect(() => {
function handlePopState() {
if (activeSidePeek) {
closeFn()
}
}
window.addEventListener("popstate", handlePopState)
return () => {
window.removeEventListener("popstate", handlePopState)
}
}, [activeSidePeek, closeFn])
if (activeSidePeek) {
return (
<>
{hotelData && (
<HotelSidePeek
additionalHotelData={hotelData.additionalData}
hotel={{ ...hotelData.hotel, url: hotelData.url }}
restaurants={hotelData.restaurants}
activeSidePeek={activeSidePeek}
close={closeFn}
showCTA={showCTA}
/>
)}
{selectedRoom && (
<RoomSidePeek
room={selectedRoom}
activeSidePeek={activeSidePeek}
close={closeFn}
/>
)}
</>
)
}
return null
}

View File

@@ -1,128 +0,0 @@
"use client"
import { useIntl } from "react-intl"
import Accordion from "@scandic-hotels/design-system/Accordion"
import AccordionItem from "@scandic-hotels/design-system/Accordion/AccordionItem"
import ButtonLink from "@scandic-hotels/design-system/ButtonLink"
import { IconName } from "@scandic-hotels/design-system/Icons/iconName"
import SidePeek from "@scandic-hotels/design-system/SidePeek"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { SidePeekEnum } from "../../stores/sidepeek"
import { useTrackingContext } from "../../trackingContext"
import AdditionalAmenities from "../AdditionalAmenities"
import Contact from "../Contact"
import BreakfastAccordionItem from "../SidePeekAccordions/BreakfastAccordionItem"
import CheckInCheckOutAccordionItem from "../SidePeekAccordions/CheckInCheckOutAccordionItem"
import ParkingAccordionItem from "../SidePeekAccordions/ParkingAccordionItem"
import styles from "./hotelSidePeek.module.css"
import type {
AdditionalData,
Hotel,
Restaurant,
} from "@scandic-hotels/trpc/types/hotel"
type HotelSidePeekProps = {
hotel: Hotel & { url: string | null }
restaurants: Restaurant[]
additionalHotelData: AdditionalData | undefined
activeSidePeek: SidePeekEnum
close: () => void
showCTA: boolean
}
export default function HotelSidePeek({
hotel,
restaurants,
additionalHotelData,
activeSidePeek,
close,
}: HotelSidePeekProps) {
const intl = useIntl()
return (
<SidePeek
title={hotel.name}
isOpen={activeSidePeek === SidePeekEnum.hotelDetails}
handleClose={close}
closeLabel={intl.formatMessage({
defaultMessage: "Close",
})}
>
<div className={styles.content}>
<Typography variant="Title/Subtitle/lg">
<h3>
{intl.formatMessage({ defaultMessage: "Practical information" })}
</h3>
</Typography>
<Contact hotel={hotel} />
<Accordion>
<ParkingAccordionItem
parking={hotel.parking}
elevatorPitch={additionalHotelData?.hotelParking.elevatorPitch}
/>
<BreakfastAccordionItem
restaurants={restaurants}
hotelType={hotel.hotelType}
/>
<CheckInCheckOutAccordionItem
checkInData={hotel.hotelFacts.checkin}
/>
<AccessibilityAccordionItem
elevatorPitch={additionalHotelData?.hotelSpecialNeeds.elevatorPitch}
/>
<AdditionalAmenities amenities={hotel.detailedFacilities} />
</Accordion>
{hotel.url ? (
<ButtonLink
href={hotel.url}
variant="Secondary"
size="Medium"
typography="Body/Paragraph/mdBold"
>
{intl.formatMessage({
defaultMessage: "Read more about the hotel",
})}
</ButtonLink>
) : null}
</div>
</SidePeek>
)
}
type AccessibilityAccordionItemProps = {
elevatorPitch?: string
}
function AccessibilityAccordionItem({
elevatorPitch,
}: AccessibilityAccordionItemProps) {
const intl = useIntl()
const tracking = useTrackingContext()
if (!elevatorPitch) {
return null
}
return (
<AccordionItem
title={intl.formatMessage({
defaultMessage: "Accessibility",
})}
iconName={IconName.Accessibility}
className={styles.accordionItem}
variant="sidepeek"
onOpen={() => tracking.trackAccordionItemOpen("amenities:accessibility")}
>
<div className={styles.accessibilityContent}>
<Typography variant="Body/Paragraph/mdRegular">
<p>{elevatorPitch}</p>
</Typography>
</div>
</AccordionItem>
)
}

View File

@@ -1,52 +0,0 @@
"use client"
import { useEffect } from "react"
import { Button } from "@scandic-hotels/design-system/Button"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import useSidePeekStore, { type SidePeekEnum } from "../../stores/sidepeek"
import { useTrackingContext } from "../../trackingContext"
interface OpenSidePeekButtonProps {
label: string
hotelId: string
showCTA: boolean
sidePeekKey: SidePeekEnum
}
export default function OpenSidePeekButton({
label,
hotelId,
showCTA,
sidePeekKey,
}: OpenSidePeekButtonProps) {
const tracking = useTrackingContext()
const { openSidePeek, closeSidePeek } = useSidePeekStore((state) => ({
openSidePeek: state.openSidePeek,
closeSidePeek: state.closeSidePeek,
}))
useEffect(() => {
return () => {
closeSidePeek()
}
}, [closeSidePeek])
return (
<Button
onPress={() => {
openSidePeek({ key: sidePeekKey, hotelId, showCTA })
tracking.trackOpenSidePeek({
name: sidePeekKey,
hotelId,
includePathname: true,
})
}}
variant="Text"
typography="Body/Paragraph/mdBold"
>
{label}
<MaterialIcon icon="chevron_right" size={24} color="CurrentColor" />
</Button>
)
}

View File

@@ -1,35 +0,0 @@
import { useIntl } from "react-intl"
import SidePeek from "@scandic-hotels/design-system/SidePeek"
import { SidePeekEnum } from "../../stores/sidepeek"
import { RoomSidePeekContent } from "./RoomSidePeekContent"
import type { Room } from "@scandic-hotels/trpc/types/hotel"
export type RoomSidePeekProps = {
room: Room
activeSidePeek: SidePeekEnum | null
close: () => void
}
export default function RoomSidePeek({
room,
activeSidePeek,
close,
}: RoomSidePeekProps) {
const intl = useIntl()
return (
<SidePeek
title={room.name}
isOpen={activeSidePeek === SidePeekEnum.roomDetails}
handleClose={close}
closeLabel={intl.formatMessage({
defaultMessage: "Close",
})}
>
<RoomSidePeekContent room={room} />
</SidePeek>
)
}

View File

@@ -1,62 +0,0 @@
import { create } from "zustand"
export enum SidePeekEnum {
hotelDetails = "hotel-detail-side-peek",
roomDetails = "room-detail-side-peek",
bookedRoomDetails = "booked-room-detail-side-peek",
}
interface SidePeekState {
activeSidePeek: SidePeekEnum | null
hotelId: string | null
roomTypeCode: string | null
showCTA: boolean
confirmationNumber: string
openSidePeek: ({
key,
hotelId,
roomTypeCode,
showCTA,
confirmationNumber,
}: {
key: SidePeekEnum | null
hotelId: string
roomTypeCode?: string
showCTA?: boolean
confirmationNumber?: string
}) => void
closeSidePeek: () => void
}
const useSidePeekStore = create<SidePeekState>((set) => ({
activeSidePeek: null,
hotelId: null,
roomTypeCode: null,
showCTA: true,
user: null,
confirmationNumber: "",
openSidePeek: ({
key,
hotelId,
roomTypeCode,
showCTA,
confirmationNumber,
}) => {
set({
activeSidePeek: key,
hotelId,
roomTypeCode,
showCTA,
confirmationNumber,
})
},
closeSidePeek: () =>
set({
activeSidePeek: null,
hotelId: null,
roomTypeCode: null,
confirmationNumber: "",
}),
}))
export default useSidePeekStore

View File

@@ -22,8 +22,8 @@
"./searchType": "./lib/misc/searchType.ts",
"./bedTypeIcons": "./lib/misc/bedTypeIcons.ts",
"./stores/bookingCode-filter": "./lib/stores/bookingCode-filter.ts",
"./stores/sidepeek": "./lib/stores/sidepeek.ts",
"./components/TripAdvisorChip": "./lib/components/TripAdvisorChip/index.tsx",
"./components/Contact": "./lib/components/Contact/index.tsx",
"./components/AdditionalAmenities": "./lib/components/AdditionalAmenities/index.tsx",
"./components/HotelReservationSidePeek": "./lib/components/HotelReservationSidePeek/index.tsx",
"./components/RoomSidePeekContent": "./lib/components/RoomSidePeek/RoomSidePeekContent/index.tsx",

View File

@@ -1,7 +1,7 @@
import type { BookingConfirmation } from "../../types/bookingConfirmation"
import type { Room } from "../../types/hotel"
export function getBookedHotelRoom(
export function getHotelRoom(
rooms: Room[],
roomTypeCode: BookingConfirmation["booking"]["roomTypeCode"]
) {

View File

@@ -12,7 +12,7 @@ import { getHotel } from "../../routers/hotels/utils"
import { toApiLang } from "../../utils"
import { encrypt } from "../../utils/encryption"
import { isValidSession } from "../../utils/session"
import { getBookedHotelRoom } from "./helpers"
import { getHotelRoom } from "./helpers"
import {
createRefIdInput,
findBookingInput,
@@ -85,10 +85,7 @@ export const bookingQueryRouter = router({
return {
...hotelData,
booking,
room: getBookedHotelRoom(
hotelData.roomCategories,
booking.roomTypeCode
),
room: getHotelRoom(hotelData.roomCategories, booking.roomTypeCode),
}
}),
findBooking: safeProtectedServiceProcedure
@@ -158,10 +155,7 @@ export const bookingQueryRouter = router({
return {
...hotelData,
booking,
room: getBookedHotelRoom(
hotelData.roomCategories,
booking.roomTypeCode
),
room: getHotelRoom(hotelData.roomCategories, booking.roomTypeCode),
}
}),
linkedReservations: safeProtectedServiceProcedure