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 (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<HistoryStateManager />
|
<HistoryStateManager />
|
||||||
|
<SelectedRoom
|
||||||
<SelectedRoom hotelId={hotelId} room={roomAvailability.selectedRoom} />
|
hotelId={hotelId}
|
||||||
|
room={roomAvailability.selectedRoom}
|
||||||
|
rateDescription={roomAvailability.cancellationText}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* TODO: How to handle no beds found? */}
|
{/* TODO: How to handle no beds found? */}
|
||||||
{roomAvailability.bedTypes ? (
|
{roomAvailability.bedTypes ? (
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export default function SectionAccordion({
|
|||||||
asChild
|
asChild
|
||||||
textTransform="uppercase"
|
textTransform="uppercase"
|
||||||
type="label"
|
type="label"
|
||||||
color="uiTextPlaceholder"
|
color="uiTextHighContrast"
|
||||||
>
|
>
|
||||||
<h2>{header}</h2>
|
<h2>{header}</h2>
|
||||||
</Footnote>
|
</Footnote>
|
||||||
|
|||||||
@@ -2,10 +2,9 @@
|
|||||||
|
|
||||||
import { useIntl } from "react-intl"
|
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 { CheckIcon, EditIcon } from "@/components/Icons"
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
|
||||||
import Link from "@/components/TempDesignSystem/Link"
|
import Link from "@/components/TempDesignSystem/Link"
|
||||||
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
@@ -14,82 +13,64 @@ import ToggleSidePeek from "./ToggleSidePeek"
|
|||||||
|
|
||||||
import styles from "./selectedRoom.module.css"
|
import styles from "./selectedRoom.module.css"
|
||||||
|
|
||||||
|
import { SelectedRoomProps } from "@/types/components/hotelReservation/enterDetails/room"
|
||||||
|
|
||||||
export default function SelectedRoom({
|
export default function SelectedRoom({
|
||||||
hotelId,
|
hotelId,
|
||||||
room,
|
room,
|
||||||
}: {
|
rateDescription,
|
||||||
hotelId: string
|
}: SelectedRoomProps) {
|
||||||
room: RoomConfiguration
|
|
||||||
}) {
|
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
|
const selectRateUrl = useEnterDetailsStore((state) => state.selectRateUrl)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article className={styles.container}>
|
<div className={styles.wrapper}>
|
||||||
<div className={styles.tempImage}>
|
<div className={styles.iconWrapper}>
|
||||||
<ImageIcon
|
<div className={styles.circle}>
|
||||||
color="baseButtonTertiaryOnFillNormal"
|
<CheckIcon color="white" height="16" width="16" />
|
||||||
height={60}
|
</div>
|
||||||
width={60}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.content}>
|
<div className={styles.main}>
|
||||||
<div>
|
<div className={styles.headerContainer}>
|
||||||
<div className={styles.textContainer}>
|
<div>
|
||||||
<Footnote
|
<Footnote
|
||||||
className={styles.label}
|
asChild
|
||||||
color="uiTextPlaceholder"
|
|
||||||
textTransform="uppercase"
|
textTransform="uppercase"
|
||||||
|
type="label"
|
||||||
|
color="uiTextHighContrast"
|
||||||
>
|
>
|
||||||
{intl.formatMessage({ id: "Your room" })}
|
<h2>{intl.formatMessage({ id: "Your room" })}</h2>
|
||||||
</Footnote>
|
</Footnote>
|
||||||
<div className={styles.text}>
|
<Subtitle
|
||||||
{/**
|
type="two"
|
||||||
* [TEMP]
|
className={styles.selection}
|
||||||
* No translation on Subtitles as they will be derived
|
color="uiTextHighContrast"
|
||||||
* from Room selection.
|
>
|
||||||
*/}
|
{room.roomType}{" "}
|
||||||
<Subtitle
|
<span className={styles.rate}>{`(${rateDescription})`}</span>
|
||||||
className={styles.room}
|
</Subtitle>
|
||||||
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>
|
|
||||||
</div>
|
</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
|
<ToggleSidePeek
|
||||||
hotelId={hotelId}
|
hotelId={hotelId}
|
||||||
roomTypeCode={room.roomTypeCode}
|
roomTypeCode={room.roomTypeCode}
|
||||||
/>
|
/>
|
||||||
)}
|
</div>
|
||||||
</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 {
|
.wrapper {
|
||||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
position: relative;
|
||||||
border-radius: var(--Corner-radius-Large);
|
display: flex;
|
||||||
display: grid;
|
flex-direction: row;
|
||||||
grid-template-columns: 144px 1fr;
|
|
||||||
gap: var(--Spacing-x3);
|
gap: var(--Spacing-x3);
|
||||||
padding: var(--Spacing-x2) var(--Spacing-x4) var(--Spacing-x2)
|
padding-top: var(--Spacing-x3);
|
||||||
var(--Spacing-x2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tempImage {
|
.wrapper::after {
|
||||||
align-items: center;
|
position: absolute;
|
||||||
background-color: lightgray;
|
left: 12px;
|
||||||
border-radius: var(--Corner-radius-Medium);
|
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;
|
display: flex;
|
||||||
height: auto;
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
min-height: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: grid;
|
|
||||||
gap: var(--Spacing-x3);
|
|
||||||
grid-template-columns: 1fr auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.textContainer {
|
.circle {
|
||||||
display: grid;
|
background-color: var(--UI-Input-Controls-Fill-Selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.rate {
|
||||||
grid-column: 1 / -1;
|
color: var(--UI-Text-Placeholder);
|
||||||
}
|
}
|
||||||
|
|
||||||
.text {
|
.details {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
justify-content: flex-start;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { getFormattedUrlQueryParams } from "@/utils/url"
|
import { getFormattedUrlQueryParams } from "@/utils/url"
|
||||||
|
|
||||||
import { BedTypeEnum } from "@/types/components/bookingWidget/enums"
|
import { BedTypeEnum } from "@/types/components/bookingWidget/enums"
|
||||||
|
import { BookingData } from "@/types/components/hotelReservation/enterDetails/bookingData"
|
||||||
import type {
|
import type {
|
||||||
Child,
|
Child,
|
||||||
SelectRateSearchParams,
|
SelectRateSearchParams,
|
||||||
@@ -52,3 +53,26 @@ export function getQueryParamsForEnterDetails(searchParams: URLSearchParams) {
|
|||||||
rateCode: room[0].ratecode,
|
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);
|
color: var(--UI-Text-Medium-contrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.uiTextHighContrast {
|
||||||
|
color: var(--UI-Text-High-contrast);
|
||||||
|
}
|
||||||
.uiTextPlaceholder {
|
.uiTextPlaceholder {
|
||||||
color: var(--UI-Text-Placeholder);
|
color: var(--UI-Text-Placeholder);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const config = {
|
|||||||
pale: styles.pale,
|
pale: styles.pale,
|
||||||
peach50: styles.peach50,
|
peach50: styles.peach50,
|
||||||
uiTextMediumContrast: styles.uiTextMediumContrast,
|
uiTextMediumContrast: styles.uiTextMediumContrast,
|
||||||
|
uiTextHighContrast: styles.uiTextHighContrast,
|
||||||
uiTextPlaceholder: styles.uiTextPlaceholder,
|
uiTextPlaceholder: styles.uiTextPlaceholder,
|
||||||
white: styles.white,
|
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" />
|
/// <reference types="next/image-types/global" />
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
// 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,
|
guestDetailsSchema,
|
||||||
signedInDetailsSchema,
|
signedInDetailsSchema,
|
||||||
} from "@/components/HotelReservation/EnterDetails/Details/schema"
|
} 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 { BookingData } from "@/types/components/hotelReservation/enterDetails/bookingData"
|
||||||
import { BreakfastPackage } from "@/types/components/hotelReservation/enterDetails/breakfast"
|
import { BreakfastPackage } from "@/types/components/hotelReservation/enterDetails/breakfast"
|
||||||
@@ -26,6 +29,7 @@ interface EnterDetailsState {
|
|||||||
} & DetailsSchema
|
} & DetailsSchema
|
||||||
roomData: BookingData
|
roomData: BookingData
|
||||||
steps: StepEnum[]
|
steps: StepEnum[]
|
||||||
|
selectRateUrl: string
|
||||||
currentStep: StepEnum
|
currentStep: StepEnum
|
||||||
isValid: Record<StepEnum, boolean>
|
isValid: Record<StepEnum, boolean>
|
||||||
completeStep: (updatedData: Partial<EnterDetailsState["userData"]>) => void
|
completeStep: (updatedData: Partial<EnterDetailsState["userData"]>) => void
|
||||||
@@ -47,8 +51,11 @@ export function initEditDetailsState(
|
|||||||
: null
|
: null
|
||||||
|
|
||||||
let roomData: BookingData
|
let roomData: BookingData
|
||||||
|
let selectRateUrl: string
|
||||||
if (searchParams?.size) {
|
if (searchParams?.size) {
|
||||||
roomData = getQueryParamsForEnterDetails(searchParams)
|
const data = getQueryParamsForEnterDetails(searchParams)
|
||||||
|
roomData = data
|
||||||
|
selectRateUrl = `select-rate?${createSelectRateUrl(data)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultUserData: EnterDetailsState["userData"] = {
|
const defaultUserData: EnterDetailsState["userData"] = {
|
||||||
@@ -115,6 +122,7 @@ export function initEditDetailsState(
|
|||||||
return create<EnterDetailsState>()((set, get) => ({
|
return create<EnterDetailsState>()((set, get) => ({
|
||||||
userData: initialData,
|
userData: initialData,
|
||||||
roomData,
|
roomData,
|
||||||
|
selectRateUrl,
|
||||||
steps: Object.values(StepEnum),
|
steps: Object.values(StepEnum),
|
||||||
setCurrentStep: (step) => set({ currentStep: step }),
|
setCurrentStep: (step) => set({ currentStep: step }),
|
||||||
navigate: (step, updatedData) =>
|
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