Merged in feature/select-room-ux-one-page (pull request #523)
This updates the select room page according to the new UX. It has different sections on the same page, but with specific URLs per section. Since neither UX, UI nor API is completely done both design and data structures are a bit temporary. Approved-by: Simon.Emanuelsson
This commit is contained in:
+15
@@ -0,0 +1,15 @@
|
||||
.card {
|
||||
font-size: 14px;
|
||||
border-radius: var(--Corner-radius-Medium);
|
||||
border: 1px solid var(--Base-Border-Normal);
|
||||
padding: var(--Spacing-x-one-and-half) var(--Spacing-x2);
|
||||
}
|
||||
|
||||
input[type="radio"]:checked + .card {
|
||||
background-color: var(--Base-Surface-Primary-light-Hover-alt);
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
"use client"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
|
||||
import styles from "./flexibilityOption.module.css"
|
||||
|
||||
import { FlexibilityOptionProps } from "@/types/components/hotelReservation/selectRate/flexibilityOption"
|
||||
|
||||
export default function FlexibilityOption({
|
||||
currency,
|
||||
standardPrice,
|
||||
memberPrice,
|
||||
name,
|
||||
value,
|
||||
paymentTerm,
|
||||
}: FlexibilityOptionProps) {
|
||||
const intl = useIntl()
|
||||
return (
|
||||
<label>
|
||||
<input type="radio" name="flexibility" value={value} />
|
||||
<div className={styles.card}>
|
||||
<div className={styles.header}>
|
||||
<Body>{name}</Body>
|
||||
<Caption>{paymentTerm}</Caption>
|
||||
</div>
|
||||
<dl>
|
||||
<div>
|
||||
<dt>{intl.formatMessage({ id: "Standard price" })}</dt>
|
||||
<dd>
|
||||
{standardPrice} {currency}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{intl.formatMessage({ id: "Member price" })}</dt>
|
||||
<dd>
|
||||
{memberPrice} {currency}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
@@ -1,49 +1,92 @@
|
||||
"use client"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import FlexibilityOption from "@/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||
import { getIntl } from "@/i18n"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
|
||||
import styles from "./roomCard.module.css"
|
||||
|
||||
import { RoomCardProps } from "@/types/components/hotelReservation/selectRate/roomCard"
|
||||
|
||||
export default async function RoomCard({ room }: RoomCardProps) {
|
||||
const { formatMessage } = await getIntl()
|
||||
export default function RoomCard({
|
||||
room,
|
||||
nrOfAdults,
|
||||
nrOfNights,
|
||||
breakfastIncluded,
|
||||
}: RoomCardProps) {
|
||||
const intl = useIntl()
|
||||
return (
|
||||
<div className={styles.card}>
|
||||
<div className={styles.cardBody}>
|
||||
<div>
|
||||
<Title className={styles.name} as="h5" level="h3">
|
||||
<div className={styles.specification}>
|
||||
<Subtitle className={styles.name} type="two">
|
||||
{room.name}
|
||||
</Title>
|
||||
<div className={styles.nameInfo}>i</div>
|
||||
</Subtitle>
|
||||
<Caption>{room.size}</Caption>
|
||||
<Button intent="text" type="button" size="small" theme="base">
|
||||
{intl.formatMessage({ id: "See room details" })}
|
||||
</Button>
|
||||
<Caption>
|
||||
{/*TODO: Handle pluralisation*/}
|
||||
{intl.formatMessage(
|
||||
{
|
||||
id: "Nr night, nr adult",
|
||||
defaultMessage:
|
||||
"{nights, number} night, {adults, number} adult",
|
||||
},
|
||||
{ nights: nrOfNights, adults: nrOfAdults }
|
||||
)}
|
||||
{" | "}
|
||||
{breakfastIncluded
|
||||
? intl.formatMessage({
|
||||
id: "Breakfast included",
|
||||
})
|
||||
: intl.formatMessage({
|
||||
id: "Breakfast excluded",
|
||||
})}
|
||||
</Caption>
|
||||
</div>
|
||||
<Caption color="burgundy">{room.size}</Caption>
|
||||
<Caption color="burgundy">{room.description}</Caption>
|
||||
|
||||
<Caption color="burgundy">
|
||||
{/* TODO: Handle currency and this whole line of text in a better way through intl */}
|
||||
{formatMessage({ id: "From" })}{" "}
|
||||
<span className={styles.price}>{room.pricePerNight}</span>{" "}
|
||||
{room.currency}/{formatMessage({ id: "night" })}
|
||||
</Caption>
|
||||
<FlexibilityOption
|
||||
name={intl.formatMessage({ id: "Non-refundable" })}
|
||||
value="non-refundable"
|
||||
paymentTerm={intl.formatMessage({ id: "Pay now" })}
|
||||
standardPrice={room.prices.nonRefundable.standard}
|
||||
memberPrice={room.prices.nonRefundable.member}
|
||||
currency={room.prices.currency}
|
||||
/>
|
||||
<FlexibilityOption
|
||||
name={intl.formatMessage({ id: "Free rebooking" })}
|
||||
value="free-rebooking"
|
||||
paymentTerm={intl.formatMessage({ id: "Pay now" })}
|
||||
standardPrice={room.prices.freeRebooking.standard}
|
||||
memberPrice={room.prices.freeRebooking.member}
|
||||
currency={room.prices.currency}
|
||||
/>
|
||||
<FlexibilityOption
|
||||
name={intl.formatMessage({ id: "Free cancellation" })}
|
||||
value="free-cancellation"
|
||||
paymentTerm={intl.formatMessage({ id: "Pay later" })}
|
||||
standardPrice={room.prices.freeCancellation.standard}
|
||||
memberPrice={room.prices.freeCancellation.member}
|
||||
currency={room.prices.currency}
|
||||
/>
|
||||
|
||||
<Button
|
||||
asChild
|
||||
type="button"
|
||||
type="submit"
|
||||
size="small"
|
||||
theme="primaryDark"
|
||||
className={styles.button}
|
||||
>
|
||||
<label htmlFor={`room-${room.id}`}>
|
||||
{formatMessage({ id: "Choose room" })}
|
||||
</label>
|
||||
{intl.formatMessage({ id: "Choose room" })}
|
||||
</Button>
|
||||
</div>
|
||||
{/* TODO: maybe use the `Image` component instead of the `img` tag. Waiting until we know how to get the image */}
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
alt={formatMessage({ id: "A photo of the room" })}
|
||||
alt={intl.formatMessage({ id: "A photo of the room" })}
|
||||
src={room.imageSrc}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
.card {
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
background-color: #fff;
|
||||
@@ -8,12 +7,15 @@
|
||||
border: 1px solid rgba(77, 0, 27, 0.1);
|
||||
}
|
||||
|
||||
input[type="radio"]:checked + .card {
|
||||
border: 3px solid var(--Scandic-Brand-Scandic-Red);
|
||||
.cardBody {
|
||||
padding: var(--Spacing-x1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x1);
|
||||
}
|
||||
|
||||
.cardBody {
|
||||
padding: var(--Spacing-x2);
|
||||
.specification {
|
||||
padding: var(--Spacing-x1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x1);
|
||||
@@ -22,15 +24,6 @@ input[type="radio"]:checked + .card {
|
||||
.name {
|
||||
display: inline-block;
|
||||
}
|
||||
.nameInfo {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.price {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card .button {
|
||||
display: inline;
|
||||
@@ -38,6 +31,6 @@ input[type="radio"]:checked + .card {
|
||||
|
||||
.card img {
|
||||
max-width: 100%;
|
||||
aspect-ratio: 2.45;
|
||||
aspect-ratio: 1.5;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
@@ -1,42 +1,52 @@
|
||||
import Header from "@/components/Section/Header"
|
||||
import { getIntl } from "@/i18n"
|
||||
"use client"
|
||||
import { useRouter, useSearchParams } from "next/navigation"
|
||||
|
||||
import RoomCard from "./RoomCard"
|
||||
|
||||
import styles from "./roomSelection.module.css"
|
||||
|
||||
import { RoomSelectionProps } from "@/types/components/hotelReservation/selectRate/roomSelection"
|
||||
import { RoomSelectionProps } from "@/types/components/hotelReservation/selectRate/section"
|
||||
|
||||
export default async function RoomSelection({ rooms }: RoomSelectionProps) {
|
||||
const { formatMessage } = await getIntl()
|
||||
export default function RoomSelection({
|
||||
alternatives,
|
||||
nextPath,
|
||||
nrOfNights,
|
||||
nrOfAdults,
|
||||
}: RoomSelectionProps) {
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||||
e.preventDefault()
|
||||
const queryParams = new URLSearchParams(searchParams)
|
||||
queryParams.set("roomClass", e.currentTarget.roomClass?.value)
|
||||
queryParams.set("flexibility", e.currentTarget.flexibility?.value)
|
||||
router.push(`${nextPath}?${queryParams}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.header}>
|
||||
<Header
|
||||
title={formatMessage({ id: "Choose room" })}
|
||||
subtitle={formatMessage({
|
||||
id: "Which room class suits you the best?",
|
||||
})}
|
||||
link={{
|
||||
href: "#",
|
||||
text: formatMessage({
|
||||
id: "All rooms comes with standard amenities",
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ul className={styles.roomList}>
|
||||
{rooms.map((room) => (
|
||||
{alternatives.map((room) => (
|
||||
<li key={room.id}>
|
||||
<input
|
||||
type="radio"
|
||||
name="room"
|
||||
value={room.id}
|
||||
id={`room-${room.id}`}
|
||||
/>
|
||||
<RoomCard room={room} />
|
||||
<form
|
||||
method="GET"
|
||||
action={`${nextPath}?${searchParams}`}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<input
|
||||
type="hidden"
|
||||
name="roomClass"
|
||||
value={room.id}
|
||||
id={`room-${room.id}`}
|
||||
/>
|
||||
<RoomCard
|
||||
room={room}
|
||||
nrOfAdults={nrOfAdults}
|
||||
nrOfNights={nrOfNights}
|
||||
breakfastIncluded={room.breakfastIncluded}
|
||||
/>
|
||||
</form>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
@@ -2,16 +2,12 @@
|
||||
border-bottom: 1px solid rgba(17, 17, 17, 0.2);
|
||||
padding-bottom: var(--Spacing-x3);
|
||||
}
|
||||
.header {
|
||||
margin-top: var(--Spacing-x2);
|
||||
margin-bottom: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.roomList {
|
||||
margin-top: var(--Spacing-x4);
|
||||
list-style: none;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||
column-gap: var(--Spacing-x2);
|
||||
row-gap: var(--Spacing-x4);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user