feat(SW-713): Added sidepeek functionality for rooms
This commit is contained in:
@@ -1,24 +1,23 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
|
import Link from "next/link"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import useSidePeekStore from "@/stores/sidepeek"
|
|
||||||
|
|
||||||
import { ChevronRightSmallIcon } from "@/components/Icons"
|
import { ChevronRightSmallIcon } from "@/components/Icons"
|
||||||
import ImageGallery from "@/components/ImageGallery"
|
import ImageGallery from "@/components/ImageGallery"
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
|
|
||||||
|
import { getRoomNameAsParam } from "../../utils"
|
||||||
|
|
||||||
import styles from "./roomCard.module.css"
|
import styles from "./roomCard.module.css"
|
||||||
|
|
||||||
import type { RoomCardProps } from "@/types/components/hotelPage/room"
|
import type { RoomCardProps } from "@/types/components/hotelPage/room"
|
||||||
import { SidePeekEnum } from "@/types/components/hotelReservation/sidePeek"
|
|
||||||
|
|
||||||
export function RoomCard({ hotelId, room }: RoomCardProps) {
|
export function RoomCard({ room }: RoomCardProps) {
|
||||||
const { images, name, roomSize, occupancy } = room
|
const { images, name, roomSize, occupancy } = room
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const openSidePeek = useSidePeekStore((state) => state.openSidePeek)
|
|
||||||
|
|
||||||
const size =
|
const size =
|
||||||
roomSize?.min === roomSize?.max
|
roomSize?.min === roomSize?.max
|
||||||
@@ -51,21 +50,11 @@ export function RoomCard({ hotelId, room }: RoomCardProps) {
|
|||||||
)}
|
)}
|
||||||
</Body>
|
</Body>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button intent="text" type="button" size="medium" theme="base" asChild>
|
||||||
intent="text"
|
<Link scroll={false} href={`?s=${getRoomNameAsParam(name)}`}>
|
||||||
type="button"
|
{intl.formatMessage({ id: "See room details" })}
|
||||||
size="medium"
|
<ChevronRightSmallIcon color="burgundy" width={20} height={20} />
|
||||||
theme="base"
|
</Link>
|
||||||
onClick={() =>
|
|
||||||
openSidePeek({
|
|
||||||
key: SidePeekEnum.roomDetails,
|
|
||||||
hotelId,
|
|
||||||
roomTypeCode: room.roomTypes[0].code,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{intl.formatMessage({ id: "See room details" })}
|
|
||||||
<ChevronRightSmallIcon color="burgundy" width={20} height={20} />
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import styles from "./rooms.module.css"
|
|||||||
import type { RoomsProps } from "@/types/components/hotelPage/room"
|
import type { RoomsProps } from "@/types/components/hotelPage/room"
|
||||||
import { HotelHashValues } from "@/types/components/hotelPage/tabNavigation"
|
import { HotelHashValues } from "@/types/components/hotelPage/tabNavigation"
|
||||||
|
|
||||||
export function Rooms({ hotelId, rooms }: RoomsProps) {
|
export function Rooms({ rooms }: RoomsProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const showToggleButton = rooms.length > 3
|
const showToggleButton = rooms.length > 3
|
||||||
const [allRoomsVisible, setAllRoomsVisible] = useState(!showToggleButton)
|
const [allRoomsVisible, setAllRoomsVisible] = useState(!showToggleButton)
|
||||||
@@ -45,7 +45,7 @@ export function Rooms({ hotelId, rooms }: RoomsProps) {
|
|||||||
>
|
>
|
||||||
{rooms.map((room) => (
|
{rooms.map((room) => (
|
||||||
<div key={room.id}>
|
<div key={room.id}>
|
||||||
<RoomCard hotelId={hotelId} room={room} />
|
<RoomCard room={room} />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</Grids.Stackable>
|
</Grids.Stackable>
|
||||||
|
|||||||
118
components/ContentType/HotelPage/SidePeeks/Room/index.tsx
Normal file
118
components/ContentType/HotelPage/SidePeeks/Room/index.tsx
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import Link from "next/link"
|
||||||
|
|
||||||
|
import ImageGallery from "@/components/ImageGallery"
|
||||||
|
import { getBedIcon } from "@/components/SidePeeks/RoomSidePeek/bedIcon"
|
||||||
|
import { getFacilityIcon } from "@/components/SidePeeks/RoomSidePeek/facilityIcon"
|
||||||
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
|
import SidePeek from "@/components/TempDesignSystem/SidePeek"
|
||||||
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
|
import { getRoomNameAsParam } from "../../utils"
|
||||||
|
|
||||||
|
import styles from "./room.module.css"
|
||||||
|
|
||||||
|
import type { RoomSidePeekProps } from "@/types/components/hotelPage/sidepeek/room"
|
||||||
|
|
||||||
|
export default async function RoomSidePeek({ room }: RoomSidePeekProps) {
|
||||||
|
const intl = await getIntl()
|
||||||
|
const { roomSize, occupancy, descriptions, images } = room
|
||||||
|
const roomDescription = descriptions.medium
|
||||||
|
const totalOccupancy = occupancy.total
|
||||||
|
// TODO: Not defined where this should lead.
|
||||||
|
const ctaUrl = ""
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SidePeek contentKey={getRoomNameAsParam(room.name)} title={room.name}>
|
||||||
|
<div className={styles.content}>
|
||||||
|
<div className={styles.innerContent}>
|
||||||
|
<Body color="baseTextMediumContrast">
|
||||||
|
{roomSize.min === roomSize.max
|
||||||
|
? roomSize.min
|
||||||
|
: `${roomSize.min} - ${roomSize.max}`}
|
||||||
|
m².{" "}
|
||||||
|
{intl.formatMessage(
|
||||||
|
{ id: "booking.accommodatesUpTo" },
|
||||||
|
{ nrOfGuests: totalOccupancy }
|
||||||
|
)}
|
||||||
|
</Body>
|
||||||
|
<div className={styles.imageContainer}>
|
||||||
|
<ImageGallery images={images} title={room.name} height={280} />
|
||||||
|
</div>
|
||||||
|
<Body color="uiTextHighContrast">{roomDescription}</Body>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.innerContent}>
|
||||||
|
<Subtitle type="two" color="uiTextHighContrast" asChild>
|
||||||
|
<h3>
|
||||||
|
{intl.formatMessage({ id: "booking.thisRoomIsEquippedWith" })}
|
||||||
|
</h3>
|
||||||
|
</Subtitle>
|
||||||
|
<ul className={styles.facilityList}>
|
||||||
|
{room.roomFacilities
|
||||||
|
.sort((a, b) => a.sortOrder - b.sortOrder)
|
||||||
|
.map((facility) => {
|
||||||
|
const Icon = getFacilityIcon(facility.icon)
|
||||||
|
return (
|
||||||
|
<li className={styles.listItem} key={facility.name}>
|
||||||
|
{Icon && (
|
||||||
|
<Icon
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
color="uiTextMediumContrast"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Body
|
||||||
|
asChild
|
||||||
|
className={!Icon ? styles.noIcon : undefined}
|
||||||
|
color="uiTextMediumContrast"
|
||||||
|
>
|
||||||
|
<span>{facility.name}</span>
|
||||||
|
</Body>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.innerContent}>
|
||||||
|
<Subtitle type="two" color="uiTextHighContrast" asChild>
|
||||||
|
<h3>{intl.formatMessage({ id: "booking.bedOptions" })}</h3>
|
||||||
|
</Subtitle>
|
||||||
|
<Body color="grey">
|
||||||
|
{intl.formatMessage({ id: "booking.basedOnAvailability" })}
|
||||||
|
</Body>
|
||||||
|
<ul className={styles.bedOptions}>
|
||||||
|
{room.roomTypes.map((roomType) => {
|
||||||
|
const BedIcon = getBedIcon(roomType.mainBed.type)
|
||||||
|
return (
|
||||||
|
<li className={styles.listItem} key={roomType.code}>
|
||||||
|
{BedIcon && (
|
||||||
|
<BedIcon
|
||||||
|
color="uiTextMediumContrast"
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Body color="uiTextMediumContrast" asChild>
|
||||||
|
<span>{roomType.mainBed.description}</span>
|
||||||
|
</Body>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{ctaUrl && (
|
||||||
|
<div className={styles.buttonContainer}>
|
||||||
|
<Button fullWidth theme="base" intent="primary" asChild>
|
||||||
|
<Link href={ctaUrl}>
|
||||||
|
{intl.formatMessage({ id: "booking.selectRoom" })}
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</SidePeek>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
.content {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--Spacing-x2);
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: calc(
|
||||||
|
var(--Spacing-x4) * 2 + 80px
|
||||||
|
); /* Creates space between the wrapper and buttonContainer */
|
||||||
|
}
|
||||||
|
.innerContent {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--Spacing-x-one-and-half);
|
||||||
|
}
|
||||||
|
|
||||||
|
.imageContainer {
|
||||||
|
position: relative;
|
||||||
|
border-radius: var(--Corner-radius-Medium);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.facilityList {
|
||||||
|
column-count: 2;
|
||||||
|
column-gap: var(--Spacing-x2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bedOptions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.listItem {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--Spacing-x1);
|
||||||
|
margin-bottom: var(--Spacing-x-half);
|
||||||
|
}
|
||||||
|
|
||||||
|
.noIcon {
|
||||||
|
margin-left: var(--Spacing-x4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonContainer {
|
||||||
|
background-color: var(--Base-Background-Primary-Normal);
|
||||||
|
border-top: 1px solid var(--Base-Border-Subtle);
|
||||||
|
padding: var(--Spacing-x4) var(--Spacing-x2);
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
export { default as AboutTheHotelSidePeek } from "./AboutTheHotel"
|
export { default as AboutTheHotelSidePeek } from "./AboutTheHotel"
|
||||||
|
export { default as RoomSidePeek } from "./Room"
|
||||||
export { default as WellnessAndExerciseSidePeek } from "./WellnessAndExercise"
|
export { default as WellnessAndExerciseSidePeek } from "./WellnessAndExercise"
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import { notFound } from "next/navigation"
|
import { notFound } from "next/navigation"
|
||||||
|
|
||||||
import hotelPageParams from "@/constants/routes/hotelPageParams"
|
import {
|
||||||
|
activities,
|
||||||
|
amenities,
|
||||||
|
meetingsAndConferences,
|
||||||
|
restaurantAndBar,
|
||||||
|
} from "@/constants/routes/hotelPageParams"
|
||||||
import { env } from "@/env/server"
|
import { env } from "@/env/server"
|
||||||
import { getHotelData, getHotelPage } from "@/lib/trpc/memoizedRequests"
|
import { getHotelData, getHotelPage } from "@/lib/trpc/memoizedRequests"
|
||||||
|
|
||||||
import AccordionSection from "@/components/Blocks/Accordion"
|
import AccordionSection from "@/components/Blocks/Accordion"
|
||||||
import HotelReservationSidePeek from "@/components/HotelReservation/SidePeek"
|
|
||||||
import SidePeekProvider from "@/components/SidePeeks/SidePeekProvider"
|
import SidePeekProvider from "@/components/SidePeeks/SidePeekProvider"
|
||||||
import Alert from "@/components/TempDesignSystem/Alert"
|
import Alert from "@/components/TempDesignSystem/Alert"
|
||||||
import SidePeek from "@/components/TempDesignSystem/SidePeek"
|
import SidePeek from "@/components/TempDesignSystem/SidePeek"
|
||||||
@@ -24,7 +28,11 @@ import Facilities from "./Facilities"
|
|||||||
import IntroSection from "./IntroSection"
|
import IntroSection from "./IntroSection"
|
||||||
import PreviewImages from "./PreviewImages"
|
import PreviewImages from "./PreviewImages"
|
||||||
import { Rooms } from "./Rooms"
|
import { Rooms } from "./Rooms"
|
||||||
import { AboutTheHotelSidePeek, WellnessAndExerciseSidePeek } from "./SidePeeks"
|
import {
|
||||||
|
AboutTheHotelSidePeek,
|
||||||
|
RoomSidePeek,
|
||||||
|
WellnessAndExerciseSidePeek,
|
||||||
|
} from "./SidePeeks"
|
||||||
import TabNavigation from "./TabNavigation"
|
import TabNavigation from "./TabNavigation"
|
||||||
|
|
||||||
import styles from "./hotelPage.module.css"
|
import styles from "./hotelPage.module.css"
|
||||||
@@ -144,7 +152,7 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<Rooms hotelId={hotelId} rooms={roomCategories} />
|
<Rooms rooms={roomCategories} />
|
||||||
<Facilities facilities={facilities} activitiesCard={activitiesCard} />
|
<Facilities facilities={facilities} activitiesCard={activitiesCard} />
|
||||||
{faq.accordions.length > 0 && (
|
{faq.accordions.length > 0 && (
|
||||||
<AccordionSection accordion={faq.accordions} title={faq.title} />
|
<AccordionSection accordion={faq.accordions} title={faq.title} />
|
||||||
@@ -169,9 +177,8 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
|
|||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
<SidePeekProvider>
|
<SidePeekProvider>
|
||||||
{/* eslint-disable import/no-named-as-default-member */}
|
|
||||||
<SidePeek
|
<SidePeek
|
||||||
contentKey={hotelPageParams.amenities[lang]}
|
contentKey={amenities[lang]}
|
||||||
title={intl.formatMessage({ id: "Amenities" })}
|
title={intl.formatMessage({ id: "Amenities" })}
|
||||||
>
|
>
|
||||||
{/* TODO: Render amenities as per the design. */}
|
{/* TODO: Render amenities as per the design. */}
|
||||||
@@ -186,7 +193,7 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
|
|||||||
descriptions={hotelContent.texts}
|
descriptions={hotelContent.texts}
|
||||||
/>
|
/>
|
||||||
<SidePeek
|
<SidePeek
|
||||||
contentKey={hotelPageParams.restaurantAndBar[lang]}
|
contentKey={restaurantAndBar[lang]}
|
||||||
title={intl.formatMessage({ id: "Restaurant & Bar" })}
|
title={intl.formatMessage({ id: "Restaurant & Bar" })}
|
||||||
>
|
>
|
||||||
{/* TODO */}
|
{/* TODO */}
|
||||||
@@ -197,22 +204,23 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
|
|||||||
buttonUrl="#"
|
buttonUrl="#"
|
||||||
/>
|
/>
|
||||||
<SidePeek
|
<SidePeek
|
||||||
contentKey={hotelPageParams.activities[lang]}
|
contentKey={activities[lang]}
|
||||||
title={intl.formatMessage({ id: "Activities" })}
|
title={intl.formatMessage({ id: "Activities" })}
|
||||||
>
|
>
|
||||||
{/* TODO */}
|
{/* TODO */}
|
||||||
Activities
|
Activities
|
||||||
</SidePeek>
|
</SidePeek>
|
||||||
<SidePeek
|
<SidePeek
|
||||||
contentKey={hotelPageParams.meetingsAndConferences[lang]}
|
contentKey={meetingsAndConferences[lang]}
|
||||||
title={intl.formatMessage({ id: "Meetings & Conferences" })}
|
title={intl.formatMessage({ id: "Meetings & Conferences" })}
|
||||||
>
|
>
|
||||||
{/* TODO */}
|
{/* TODO */}
|
||||||
Meetings & Conferences
|
Meetings & Conferences
|
||||||
</SidePeek>
|
</SidePeek>
|
||||||
{/* eslint-enable import/no-named-as-default-member */}
|
{roomCategories.map((room) => (
|
||||||
|
<RoomSidePeek key={room.name} room={room} />
|
||||||
|
))}
|
||||||
</SidePeekProvider>
|
</SidePeekProvider>
|
||||||
<HotelReservationSidePeek hotel={null} />
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
3
components/ContentType/HotelPage/utils.ts
Normal file
3
components/ContentType/HotelPage/utils.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export function getRoomNameAsParam(roomName: string) {
|
||||||
|
return roomName.replace(/[()]/g, "").replaceAll(" ", "-").toLowerCase()
|
||||||
|
}
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
import type { RoomData } from "@/types/hotel"
|
import type { RoomData } from "@/types/hotel"
|
||||||
|
|
||||||
export interface RoomCardProps {
|
export interface RoomCardProps {
|
||||||
hotelId: string
|
|
||||||
room: RoomData
|
room: RoomData
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RoomsProps = {
|
export type RoomsProps = {
|
||||||
hotelId: string
|
|
||||||
rooms: RoomData[]
|
rooms: RoomData[]
|
||||||
}
|
}
|
||||||
|
|||||||
5
types/components/hotelPage/sidepeek/room.ts
Normal file
5
types/components/hotelPage/sidepeek/room.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import type { RoomData } from "@/types/hotel"
|
||||||
|
|
||||||
|
export interface RoomSidePeekProps {
|
||||||
|
room: RoomData
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user