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:
Pontus Dreij
2024-10-15 08:39:49 +00:00
14 changed files with 143 additions and 29 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,6 @@
import { Rate } from "./selectRate"
export interface RateSummaryProps {
rateSummary: Rate
isUserLoggedIn: boolean
}

View File

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

View File

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

View File

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