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

View File

@@ -78,7 +78,7 @@ export default function SectionAccordion({
asChild
textTransform="uppercase"
type="label"
color="uiTextPlaceholder"
color="uiTextHighContrast"
>
<h2>{header}</h2>
</Footnote>

View File

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

View File

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

View File

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

View File

@@ -66,6 +66,9 @@
color: var(--UI-Text-Medium-contrast);
}
.uiTextHighContrast {
color: var(--UI-Text-High-contrast);
}
.uiTextPlaceholder {
color: var(--UI-Text-Placeholder);
}

View File

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

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

View File

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

View File

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