Merged in feat/SW-589-room-summary (pull request #693)
feat(SW-589): Room selection summary Approved-by: Niclas Edenvin
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
|
import { getProfileSafely } from "@/lib/trpc/memoizedRequests"
|
||||||
import { serverClient } from "@/lib/trpc/server"
|
import { serverClient } from "@/lib/trpc/server"
|
||||||
import tempHotelData from "@/server/routers/hotels/tempHotelData.json"
|
|
||||||
|
|
||||||
import RoomSelection from "@/components/HotelReservation/SelectRate/RoomSelection"
|
import RoomSelection from "@/components/HotelReservation/SelectRate/RoomSelection"
|
||||||
import { setLang } from "@/i18n/serverContext"
|
import { setLang } from "@/i18n/serverContext"
|
||||||
@@ -15,18 +15,20 @@ export default async function SelectRatePage({
|
|||||||
}: PageArgs<LangParams & { section: string }, SelectRateSearchParams>) {
|
}: PageArgs<LangParams & { section: string }, SelectRateSearchParams>) {
|
||||||
setLang(params.lang)
|
setLang(params.lang)
|
||||||
|
|
||||||
const hotelData = await serverClient().hotel.hotelData.get({
|
const [hotelData, roomConfigurations, user] = await Promise.all([
|
||||||
hotelId: searchParams.hotel,
|
serverClient().hotel.hotelData.get({
|
||||||
language: params.lang,
|
hotelId: searchParams.hotel,
|
||||||
include: ["RoomCategories"],
|
language: params.lang,
|
||||||
})
|
include: ["RoomCategories"],
|
||||||
|
}),
|
||||||
const roomConfigurations = await serverClient().hotel.availability.rooms({
|
serverClient().hotel.availability.rooms({
|
||||||
hotelId: parseInt(searchParams.hotel, 10),
|
hotelId: parseInt(searchParams.hotel, 10),
|
||||||
roomStayStartDate: "2024-11-02",
|
roomStayStartDate: "2024-11-02",
|
||||||
roomStayEndDate: "2024-11-03",
|
roomStayEndDate: "2024-11-03",
|
||||||
adults: 1,
|
adults: 1,
|
||||||
})
|
}),
|
||||||
|
getProfileSafely(),
|
||||||
|
])
|
||||||
|
|
||||||
if (!roomConfigurations) {
|
if (!roomConfigurations) {
|
||||||
return "No rooms found" // TODO: Add a proper error message
|
return "No rooms found" // TODO: Add a proper error message
|
||||||
@@ -47,6 +49,7 @@ export default async function SelectRatePage({
|
|||||||
<RoomSelection
|
<RoomSelection
|
||||||
roomConfigurations={roomConfigurations}
|
roomConfigurations={roomConfigurations}
|
||||||
roomCategories={roomCategories ?? []}
|
roomCategories={roomCategories ?? []}
|
||||||
|
user={user}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ export default function FlexibilityOption({
|
|||||||
name,
|
name,
|
||||||
paymentTerm,
|
paymentTerm,
|
||||||
priceInformation,
|
priceInformation,
|
||||||
|
roomType,
|
||||||
|
handleSelectRate,
|
||||||
}: FlexibilityOptionProps) {
|
}: FlexibilityOptionProps) {
|
||||||
const [rootDiv, setRootDiv] = useState<Element | undefined>(undefined)
|
const [rootDiv, setRootDiv] = useState<Element | undefined>(undefined)
|
||||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false)
|
const [isPopoverOpen, setIsPopoverOpen] = useState(false)
|
||||||
@@ -42,9 +44,24 @@ export default function FlexibilityOption({
|
|||||||
|
|
||||||
const { public: publicPrice, member: memberPrice } = product.productType
|
const { public: publicPrice, member: memberPrice } = product.productType
|
||||||
|
|
||||||
|
function onChange() {
|
||||||
|
const rate = {
|
||||||
|
roomType: roomType,
|
||||||
|
priceName: name,
|
||||||
|
public: publicPrice,
|
||||||
|
member: memberPrice,
|
||||||
|
}
|
||||||
|
handleSelectRate(rate)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<label>
|
<label>
|
||||||
<input type="radio" name="rateCode" value={publicPrice?.rateCode} />
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="rateCode"
|
||||||
|
value={publicPrice?.rateCode}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
<div className={styles.card}>
|
<div className={styles.card}>
|
||||||
<div className={styles.header} ref={setRef}>
|
<div className={styles.header} ref={setRef}>
|
||||||
<DialogTrigger>
|
<DialogTrigger>
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
|
|
||||||
|
import styles from "./rateSummary.module.css"
|
||||||
|
|
||||||
|
import { RateSummaryProps } from "@/types/components/hotelReservation/selectRate/rateSummary"
|
||||||
|
|
||||||
|
export default function RateSummary({
|
||||||
|
rateSummary,
|
||||||
|
isUserLoggedIn,
|
||||||
|
}: RateSummaryProps) {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
|
const priceToShow = isUserLoggedIn ? rateSummary.member : rateSummary.public
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.summary}>
|
||||||
|
<div className={styles.summaryText}>
|
||||||
|
<Subtitle color="uiTextHighContrast">{rateSummary.roomType}</Subtitle>
|
||||||
|
<Body color="uiTextMediumContrast">{rateSummary.priceName}</Body>
|
||||||
|
</div>
|
||||||
|
<div className={styles.summaryPrice}>
|
||||||
|
<div className={styles.summaryPriceText}>
|
||||||
|
<>
|
||||||
|
<Subtitle color={isUserLoggedIn ? "red" : "uiTextHighContrast"}>
|
||||||
|
{priceToShow?.localPrice.pricePerStay}{" "}
|
||||||
|
{priceToShow?.localPrice.currency}
|
||||||
|
</Subtitle>
|
||||||
|
<Body color="uiTextMediumContrast">
|
||||||
|
{intl.formatMessage({ id: "Approx." })}{" "}
|
||||||
|
{priceToShow?.requestedPrice?.pricePerStay}{" "}
|
||||||
|
{priceToShow?.requestedPrice?.currency}
|
||||||
|
</Body>
|
||||||
|
</>
|
||||||
|
</div>
|
||||||
|
<Button type="submit" theme="base">
|
||||||
|
{intl.formatMessage({ id: "Continue" })}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
.summary {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 10;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: var(--Base-Surface-Primary-light-Normal);
|
||||||
|
padding: var(--Spacing-x3) var(--Spacing-x7) var(--Spacing-x5);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summaryPrice {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--Spacing-x4);
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { RateDefinition } from "@/server/routers/hotels/output"
|
import { RateDefinition } from "@/server/routers/hotels/output"
|
||||||
@@ -21,6 +22,7 @@ export default function RoomCard({
|
|||||||
rateDefinitions,
|
rateDefinitions,
|
||||||
roomConfiguration,
|
roomConfiguration,
|
||||||
roomCategories,
|
roomCategories,
|
||||||
|
handleSelectRate,
|
||||||
}: RoomCardProps) {
|
}: RoomCardProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
@@ -112,6 +114,8 @@ export default function RoomCard({
|
|||||||
paymentTerm={intl.formatMessage({ id: "Pay now" })}
|
paymentTerm={intl.formatMessage({ id: "Pay now" })}
|
||||||
product={findProductForRate(saveRate)}
|
product={findProductForRate(saveRate)}
|
||||||
priceInformation={getPriceForRate(saveRate)}
|
priceInformation={getPriceForRate(saveRate)}
|
||||||
|
handleSelectRate={handleSelectRate}
|
||||||
|
roomType={roomConfiguration.roomType}
|
||||||
/>
|
/>
|
||||||
<FlexibilityOption
|
<FlexibilityOption
|
||||||
name={intl.formatMessage({ id: "Free rebooking" })}
|
name={intl.formatMessage({ id: "Free rebooking" })}
|
||||||
@@ -119,6 +123,8 @@ export default function RoomCard({
|
|||||||
paymentTerm={intl.formatMessage({ id: "Pay now" })}
|
paymentTerm={intl.formatMessage({ id: "Pay now" })}
|
||||||
product={findProductForRate(changeRate)}
|
product={findProductForRate(changeRate)}
|
||||||
priceInformation={getPriceForRate(changeRate)}
|
priceInformation={getPriceForRate(changeRate)}
|
||||||
|
handleSelectRate={handleSelectRate}
|
||||||
|
roomType={roomConfiguration.roomType}
|
||||||
/>
|
/>
|
||||||
<FlexibilityOption
|
<FlexibilityOption
|
||||||
name={intl.formatMessage({ id: "Free cancellation" })}
|
name={intl.formatMessage({ id: "Free cancellation" })}
|
||||||
@@ -126,6 +132,8 @@ export default function RoomCard({
|
|||||||
paymentTerm={intl.formatMessage({ id: "Pay later" })}
|
paymentTerm={intl.formatMessage({ id: "Pay later" })}
|
||||||
product={findProductForRate(flexRate)}
|
product={findProductForRate(flexRate)}
|
||||||
priceInformation={getPriceForRate(flexRate)}
|
priceInformation={getPriceForRate(flexRate)}
|
||||||
|
handleSelectRate={handleSelectRate}
|
||||||
|
roomType={roomConfiguration.roomType}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import { useRouter, useSearchParams } from "next/navigation"
|
import { useRouter, useSearchParams } from "next/navigation"
|
||||||
|
import { useState } from "react"
|
||||||
|
|
||||||
|
import RateSummary from "./RateSummary"
|
||||||
import RoomCard from "./RoomCard"
|
import RoomCard from "./RoomCard"
|
||||||
|
|
||||||
import styles from "./roomSelection.module.css"
|
import styles from "./roomSelection.module.css"
|
||||||
|
|
||||||
import { RoomSelectionProps } from "@/types/components/hotelReservation/selectRate/roomSelection"
|
import { RoomSelectionProps } from "@/types/components/hotelReservation/selectRate/roomSelection"
|
||||||
|
import { Rate } from "@/types/components/hotelReservation/selectRate/selectRate"
|
||||||
|
|
||||||
export default function RoomSelection({
|
export default function RoomSelection({
|
||||||
roomConfigurations,
|
roomConfigurations,
|
||||||
roomCategories,
|
roomCategories,
|
||||||
|
user,
|
||||||
}: RoomSelectionProps) {
|
}: RoomSelectionProps) {
|
||||||
|
const [rateSummary, setRateSummary] = useState<Rate | null>(null)
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
|
|
||||||
@@ -36,16 +42,14 @@ export default function RoomSelection({
|
|||||||
rateDefinitions={roomConfigurations.rateDefinitions}
|
rateDefinitions={roomConfigurations.rateDefinitions}
|
||||||
roomConfiguration={roomConfiguration}
|
roomConfiguration={roomConfiguration}
|
||||||
roomCategories={roomCategories}
|
roomCategories={roomCategories}
|
||||||
|
handleSelectRate={setRateSummary}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
{/* <div className={styles.summary}>
|
{rateSummary && (
|
||||||
This is summary
|
<RateSummary rateSummary={rateSummary} isUserLoggedIn={!!user} />
|
||||||
<Button type="submit" size="small" theme="primaryDark">
|
)}
|
||||||
{intl.formatMessage({ id: "Choose room" })}
|
|
||||||
</Button>
|
|
||||||
</div> */}
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -20,15 +20,6 @@
|
|||||||
width: 0;
|
width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.summary {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
background-color: white;
|
|
||||||
padding: var(--Spacing-x3) var(--Spacing-x7) var(--Spacing-x5);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 767px) {
|
@media (min-width: 767px) {
|
||||||
.roomList {
|
.roomList {
|
||||||
grid-template-columns: repeat(3, minmax(240px, 1fr));
|
grid-template-columns: repeat(3, minmax(240px, 1fr));
|
||||||
|
|||||||
@@ -96,6 +96,10 @@
|
|||||||
color: var(--UI-Text-High-contrast);
|
color: var(--UI-Text-High-contrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.uiTextMediumContrast {
|
||||||
|
color: var(--UI-Text-Medium-contrast);
|
||||||
|
}
|
||||||
|
|
||||||
.uiTextPlaceholder {
|
.uiTextPlaceholder {
|
||||||
color: var(--UI-Text-Placeholder);
|
color: var(--UI-Text-Placeholder);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const config = {
|
|||||||
peach50: styles.peach50,
|
peach50: styles.peach50,
|
||||||
peach80: styles.peach80,
|
peach80: styles.peach80,
|
||||||
uiTextHighContrast: styles.uiTextHighContrast,
|
uiTextHighContrast: styles.uiTextHighContrast,
|
||||||
|
uiTextMediumContrast: styles.uiTextMediumContrast,
|
||||||
uiTextPlaceholder: styles.uiTextPlaceholder,
|
uiTextPlaceholder: styles.uiTextPlaceholder,
|
||||||
},
|
},
|
||||||
textAlign: {
|
textAlign: {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { z } from "zod"
|
|||||||
|
|
||||||
import { Product, productTypePriceSchema } from "@/server/routers/hotels/output"
|
import { Product, productTypePriceSchema } from "@/server/routers/hotels/output"
|
||||||
|
|
||||||
|
import { Rate } from "./selectRate"
|
||||||
|
|
||||||
type ProductPrice = z.output<typeof productTypePriceSchema>
|
type ProductPrice = z.output<typeof productTypePriceSchema>
|
||||||
|
|
||||||
export type FlexibilityOptionProps = {
|
export type FlexibilityOptionProps = {
|
||||||
@@ -10,6 +12,8 @@ export type FlexibilityOptionProps = {
|
|||||||
value: string
|
value: string
|
||||||
paymentTerm: string
|
paymentTerm: string
|
||||||
priceInformation?: Array<string>
|
priceInformation?: Array<string>
|
||||||
|
roomType: string
|
||||||
|
handleSelectRate: (rate: Rate) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PriceListProps {
|
export interface PriceListProps {
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { Rate } from "./selectRate"
|
||||||
|
|
||||||
|
export interface RateSummaryProps {
|
||||||
|
rateSummary: Rate
|
||||||
|
isUserLoggedIn: boolean
|
||||||
|
}
|
||||||
@@ -3,10 +3,13 @@ import {
|
|||||||
RoomConfiguration,
|
RoomConfiguration,
|
||||||
} from "@/server/routers/hotels/output"
|
} from "@/server/routers/hotels/output"
|
||||||
|
|
||||||
|
import { Rate } from "./selectRate"
|
||||||
|
|
||||||
import { RoomData } from "@/types/hotel"
|
import { RoomData } from "@/types/hotel"
|
||||||
|
|
||||||
export type RoomCardProps = {
|
export type RoomCardProps = {
|
||||||
roomConfiguration: RoomConfiguration
|
roomConfiguration: RoomConfiguration
|
||||||
rateDefinitions: RateDefinition[]
|
rateDefinitions: RateDefinition[]
|
||||||
roomCategories: RoomData[]
|
roomCategories: RoomData[]
|
||||||
|
handleSelectRate: (rate: Rate) => void
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { RoomsAvailability } from "@/server/routers/hotels/output"
|
import { RoomsAvailability } from "@/server/routers/hotels/output"
|
||||||
|
|
||||||
import { RoomData } from "@/types/hotel"
|
import { RoomData } from "@/types/hotel"
|
||||||
|
import { User } from "@/types/user"
|
||||||
|
|
||||||
export interface RoomSelectionProps {
|
export interface RoomSelectionProps {
|
||||||
roomConfigurations: RoomsAvailability
|
roomConfigurations: RoomsAvailability
|
||||||
roomCategories: RoomData[]
|
roomCategories: RoomData[]
|
||||||
|
user: User | null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
|
import { Product } from "@/server/routers/hotels/output"
|
||||||
|
|
||||||
export interface SelectRateSearchParams {
|
export interface SelectRateSearchParams {
|
||||||
fromDate: string
|
fromDate: string
|
||||||
toDate: string
|
toDate: string
|
||||||
hotel: string
|
hotel: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Rate {
|
||||||
|
roomType: string
|
||||||
|
priceName: string
|
||||||
|
public: Product["productType"]["public"]
|
||||||
|
member: Product["productType"]["member"]
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user