feat: Convert Selected-Room to Steplike design and fix select-rate link

This commit is contained in:
Arvid Norlin
2024-11-04 16:11:25 +01:00
parent 7bf89e80ff
commit e8e23a2113
10 changed files with 148 additions and 107 deletions

View File

@@ -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 ? (

View File

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

View File

@@ -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}
width={60}
/>
</div> </div>
<div className={styles.content}> </div>
<div className={styles.main}>
<div className={styles.headerContainer}>
<div> <div>
<div className={styles.textContainer}>
<Footnote <Footnote
className={styles.label} asChild
color="uiTextPlaceholder"
textTransform="uppercase" textTransform="uppercase"
> type="label"
{intl.formatMessage({ id: "Your room" })}
</Footnote>
<div className={styles.text}>
{/**
* [TEMP]
* No translation on Subtitles as they will be derived
* from Room selection.
*/}
<Subtitle
className={styles.room}
color="uiTextHighContrast" color="uiTextHighContrast"
type="two"
> >
{room.roomType} <h2>{intl.formatMessage({ id: "Your room" })}</h2>
</Subtitle> </Footnote>
<Subtitle <Subtitle
className={styles.invertFontWeight}
color="uiTextMediumContrast"
type="two" type="two"
className={styles.selection}
color="uiTextHighContrast"
> >
Free rebooking {room.roomType}{" "}
</Subtitle> <span className={styles.rate}>{`(${rateDescription})`}</span>
<Subtitle
className={styles.invertFontWeight}
color="uiTextMediumContrast"
type="two"
>
Pay now
</Subtitle> </Subtitle>
</div> </div>
<Link
color="burgundy"
href={selectRateUrl}
size="small"
variant="icon"
>
<EditIcon color="burgundy" />
{intl.formatMessage({ id: "Change room" })}{" "}
</Link>
</div> </div>
{room?.roomTypeCode && ( {room?.roomTypeCode && (
<div className={styles.details}>
<ToggleSidePeek <ToggleSidePeek
hotelId={hotelId} hotelId={hotelId}
roomTypeCode={room.roomTypeCode} roomTypeCode={room.roomTypeCode}
/> />
</div>
)} )}
</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>
) )
} }

View File

@@ -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);
} }

View File

@@ -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
}

View File

@@ -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);
} }

View File

@@ -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
View File

@@ -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.

View File

@@ -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) =>

View File

@@ -0,0 +1,7 @@
import { RoomConfiguration } from "@/server/routers/hotels/output"
export interface SelectedRoomProps {
hotelId: string
room: RoomConfiguration
rateDescription: string
}