feat: Convert Selected-Room to Steplike design and fix select-rate link
This commit is contained in:
@@ -100,8 +100,11 @@ export default async function StepPage({
|
||||
return (
|
||||
<section>
|
||||
<HistoryStateManager />
|
||||
|
||||
<SelectedRoom hotelId={hotelId} room={roomAvailability.selectedRoom} />
|
||||
<SelectedRoom
|
||||
hotelId={hotelId}
|
||||
room={roomAvailability.selectedRoom}
|
||||
rateDescription={roomAvailability.cancellationText}
|
||||
/>
|
||||
|
||||
{/* TODO: How to handle no beds found? */}
|
||||
{roomAvailability.bedTypes ? (
|
||||
|
||||
@@ -78,7 +78,7 @@ export default function SectionAccordion({
|
||||
asChild
|
||||
textTransform="uppercase"
|
||||
type="label"
|
||||
color="uiTextPlaceholder"
|
||||
color="uiTextHighContrast"
|
||||
>
|
||||
<h2>{header}</h2>
|
||||
</Footnote>
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { RoomConfiguration } from "@/server/routers/hotels/output"
|
||||
import { useEnterDetailsStore } from "@/stores/enter-details"
|
||||
|
||||
import { EditIcon, ImageIcon } from "@/components/Icons"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import { CheckIcon, EditIcon } from "@/components/Icons"
|
||||
import Link from "@/components/TempDesignSystem/Link"
|
||||
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
@@ -14,82 +13,64 @@ import ToggleSidePeek from "./ToggleSidePeek"
|
||||
|
||||
import styles from "./selectedRoom.module.css"
|
||||
|
||||
import { SelectedRoomProps } from "@/types/components/hotelReservation/enterDetails/room"
|
||||
|
||||
export default function SelectedRoom({
|
||||
hotelId,
|
||||
room,
|
||||
}: {
|
||||
hotelId: string
|
||||
room: RoomConfiguration
|
||||
}) {
|
||||
rateDescription,
|
||||
}: SelectedRoomProps) {
|
||||
const intl = useIntl()
|
||||
|
||||
const selectRateUrl = useEnterDetailsStore((state) => state.selectRateUrl)
|
||||
|
||||
return (
|
||||
<article className={styles.container}>
|
||||
<div className={styles.tempImage}>
|
||||
<ImageIcon
|
||||
color="baseButtonTertiaryOnFillNormal"
|
||||
height={60}
|
||||
width={60}
|
||||
/>
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.iconWrapper}>
|
||||
<div className={styles.circle}>
|
||||
<CheckIcon color="white" height="16" width="16" />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<div>
|
||||
<div className={styles.textContainer}>
|
||||
<div className={styles.main}>
|
||||
<div className={styles.headerContainer}>
|
||||
<div>
|
||||
<Footnote
|
||||
className={styles.label}
|
||||
color="uiTextPlaceholder"
|
||||
asChild
|
||||
textTransform="uppercase"
|
||||
type="label"
|
||||
color="uiTextHighContrast"
|
||||
>
|
||||
{intl.formatMessage({ id: "Your room" })}
|
||||
<h2>{intl.formatMessage({ id: "Your room" })}</h2>
|
||||
</Footnote>
|
||||
<div className={styles.text}>
|
||||
{/**
|
||||
* [TEMP]
|
||||
* No translation on Subtitles as they will be derived
|
||||
* from Room selection.
|
||||
*/}
|
||||
<Subtitle
|
||||
className={styles.room}
|
||||
color="uiTextHighContrast"
|
||||
type="two"
|
||||
>
|
||||
{room.roomType}
|
||||
</Subtitle>
|
||||
<Subtitle
|
||||
className={styles.invertFontWeight}
|
||||
color="uiTextMediumContrast"
|
||||
type="two"
|
||||
>
|
||||
Free rebooking
|
||||
</Subtitle>
|
||||
<Subtitle
|
||||
className={styles.invertFontWeight}
|
||||
color="uiTextMediumContrast"
|
||||
type="two"
|
||||
>
|
||||
Pay now
|
||||
</Subtitle>
|
||||
</div>
|
||||
<Subtitle
|
||||
type="two"
|
||||
className={styles.selection}
|
||||
color="uiTextHighContrast"
|
||||
>
|
||||
{room.roomType}{" "}
|
||||
<span className={styles.rate}>{`(${rateDescription})`}</span>
|
||||
</Subtitle>
|
||||
</div>
|
||||
{room?.roomTypeCode && (
|
||||
|
||||
<Link
|
||||
color="burgundy"
|
||||
href={selectRateUrl}
|
||||
size="small"
|
||||
variant="icon"
|
||||
>
|
||||
<EditIcon color="burgundy" />
|
||||
{intl.formatMessage({ id: "Change room" })}{" "}
|
||||
</Link>
|
||||
</div>
|
||||
{room?.roomTypeCode && (
|
||||
<div className={styles.details}>
|
||||
<ToggleSidePeek
|
||||
hotelId={hotelId}
|
||||
roomTypeCode={room.roomTypeCode}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
asChild
|
||||
intent="tertiary"
|
||||
size="small"
|
||||
theme="base"
|
||||
variant="icon"
|
||||
>
|
||||
<Link href="#">
|
||||
<EditIcon color="baseButtonTertiaryOnFillNormal" />
|
||||
{intl.formatMessage({ id: "Modify" })}
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,51 +1,65 @@
|
||||
.container {
|
||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
||||
border-radius: var(--Corner-radius-Large);
|
||||
display: grid;
|
||||
grid-template-columns: 144px 1fr;
|
||||
.wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--Spacing-x3);
|
||||
padding: var(--Spacing-x2) var(--Spacing-x4) var(--Spacing-x2)
|
||||
var(--Spacing-x2);
|
||||
padding-top: var(--Spacing-x3);
|
||||
}
|
||||
|
||||
.tempImage {
|
||||
align-items: center;
|
||||
background-color: lightgray;
|
||||
border-radius: var(--Corner-radius-Medium);
|
||||
.wrapper::after {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
bottom: 0;
|
||||
top: var(--Spacing-x5);
|
||||
height: 100%;
|
||||
content: "";
|
||||
border-left: 1px solid var(--Primary-Light-On-Surface-Divider-subtle);
|
||||
}
|
||||
|
||||
.main {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid var(--Primary-Light-On-Surface-Divider-subtle);
|
||||
padding-bottom: var(--Spacing-x3);
|
||||
grid-template-rows: 2em 0fr;
|
||||
}
|
||||
|
||||
.headerContainer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.selection {
|
||||
font-weight: 450;
|
||||
font-size: var(--typography-Title-4-fontSize);
|
||||
}
|
||||
|
||||
.iconWrapper {
|
||||
position: relative;
|
||||
top: var(--Spacing-x1);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.circle {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 100px;
|
||||
border: 2px solid var(--Base-Border-Inverted);
|
||||
display: flex;
|
||||
height: auto;
|
||||
justify-content: center;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.content {
|
||||
align-items: center;
|
||||
display: grid;
|
||||
gap: var(--Spacing-x3);
|
||||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
|
||||
.textContainer {
|
||||
display: grid;
|
||||
.circle {
|
||||
background-color: var(--UI-Input-Controls-Fill-Selected);
|
||||
}
|
||||
|
||||
.label {
|
||||
grid-column: 1 / -1;
|
||||
.rate {
|
||||
color: var(--UI-Text-Placeholder);
|
||||
}
|
||||
|
||||
.text {
|
||||
.details {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--Spacing-x1);
|
||||
}
|
||||
|
||||
p.invertFontWeight {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.invertFontWeight:not(:last-of-type)::after,
|
||||
.room::after {
|
||||
color: var(--UI-Text-Medium-contrast);
|
||||
content: "∙";
|
||||
padding-left: var(--Spacing-x1);
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getFormattedUrlQueryParams } from "@/utils/url"
|
||||
|
||||
import { BedTypeEnum } from "@/types/components/bookingWidget/enums"
|
||||
import { BookingData } from "@/types/components/hotelReservation/enterDetails/bookingData"
|
||||
import type {
|
||||
Child,
|
||||
SelectRateSearchParams,
|
||||
@@ -52,3 +53,26 @@ export function getQueryParamsForEnterDetails(searchParams: URLSearchParams) {
|
||||
rateCode: room[0].ratecode,
|
||||
}
|
||||
}
|
||||
|
||||
export function createSelectRateUrl(roomData: BookingData) {
|
||||
const { hotel, fromDate, toDate } = roomData
|
||||
const params = new URLSearchParams({ fromDate, toDate, hotel })
|
||||
|
||||
roomData.room.forEach((room, index) => {
|
||||
params.set(`room[${index}].adults`, room.adults.toString())
|
||||
|
||||
if (room.child) {
|
||||
room.child.forEach((child, childIndex) => {
|
||||
params.set(
|
||||
`room[${index}].child[${childIndex}].age`,
|
||||
child.age.toString()
|
||||
)
|
||||
params.set(
|
||||
`room[${index}].child[${childIndex}].bed`,
|
||||
child.bed.toString()
|
||||
)
|
||||
})
|
||||
}
|
||||
})
|
||||
return params
|
||||
}
|
||||
|
||||
@@ -66,6 +66,9 @@
|
||||
color: var(--UI-Text-Medium-contrast);
|
||||
}
|
||||
|
||||
.uiTextHighContrast {
|
||||
color: var(--UI-Text-High-contrast);
|
||||
}
|
||||
.uiTextPlaceholder {
|
||||
color: var(--UI-Text-Placeholder);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ const config = {
|
||||
pale: styles.pale,
|
||||
peach50: styles.peach50,
|
||||
uiTextMediumContrast: styles.uiTextMediumContrast,
|
||||
uiTextHighContrast: styles.uiTextHighContrast,
|
||||
uiTextPlaceholder: styles.uiTextPlaceholder,
|
||||
white: styles.white,
|
||||
},
|
||||
|
||||
2
next-env.d.ts
vendored
2
next-env.d.ts
vendored
@@ -2,4 +2,4 @@
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
|
||||
|
||||
@@ -9,7 +9,10 @@ import {
|
||||
guestDetailsSchema,
|
||||
signedInDetailsSchema,
|
||||
} from "@/components/HotelReservation/EnterDetails/Details/schema"
|
||||
import { getQueryParamsForEnterDetails } from "@/components/HotelReservation/SelectRate/RoomSelection/utils"
|
||||
import {
|
||||
createSelectRateUrl,
|
||||
getQueryParamsForEnterDetails,
|
||||
} from "@/components/HotelReservation/SelectRate/RoomSelection/utils"
|
||||
|
||||
import { BookingData } from "@/types/components/hotelReservation/enterDetails/bookingData"
|
||||
import { BreakfastPackage } from "@/types/components/hotelReservation/enterDetails/breakfast"
|
||||
@@ -26,6 +29,7 @@ interface EnterDetailsState {
|
||||
} & DetailsSchema
|
||||
roomData: BookingData
|
||||
steps: StepEnum[]
|
||||
selectRateUrl: string
|
||||
currentStep: StepEnum
|
||||
isValid: Record<StepEnum, boolean>
|
||||
completeStep: (updatedData: Partial<EnterDetailsState["userData"]>) => void
|
||||
@@ -47,8 +51,11 @@ export function initEditDetailsState(
|
||||
: null
|
||||
|
||||
let roomData: BookingData
|
||||
let selectRateUrl: string
|
||||
if (searchParams?.size) {
|
||||
roomData = getQueryParamsForEnterDetails(searchParams)
|
||||
const data = getQueryParamsForEnterDetails(searchParams)
|
||||
roomData = data
|
||||
selectRateUrl = `select-rate?${createSelectRateUrl(data)}`
|
||||
}
|
||||
|
||||
const defaultUserData: EnterDetailsState["userData"] = {
|
||||
@@ -115,6 +122,7 @@ export function initEditDetailsState(
|
||||
return create<EnterDetailsState>()((set, get) => ({
|
||||
userData: initialData,
|
||||
roomData,
|
||||
selectRateUrl,
|
||||
steps: Object.values(StepEnum),
|
||||
setCurrentStep: (step) => set({ currentStep: step }),
|
||||
navigate: (step, updatedData) =>
|
||||
|
||||
7
types/components/hotelReservation/enterDetails/room.ts
Normal file
7
types/components/hotelReservation/enterDetails/room.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { RoomConfiguration } from "@/server/routers/hotels/output"
|
||||
|
||||
export interface SelectedRoomProps {
|
||||
hotelId: string
|
||||
room: RoomConfiguration
|
||||
rateDescription: string
|
||||
}
|
||||
Reference in New Issue
Block a user