Merged in feat/sw-697-pet-room-fee-on-card (pull request #827)

feat(SW-697): Update package structure on price from API

Approved-by: Linus Flood
Approved-by: Niclas Edenvin
This commit is contained in:
Pontus Dreij
2024-11-04 15:42:06 +00:00
14 changed files with 211 additions and 57 deletions

View File

@@ -78,8 +78,8 @@ export default function Breakfast({ packages }: BreakfastProps) {
? intl.formatMessage<React.ReactNode>( ? intl.formatMessage<React.ReactNode>(
{ id: "breakfast.price.free" }, { id: "breakfast.price.free" },
{ {
amount: pkg.originalPrice, amount: pkg.localPrice.price,
currency: pkg.currency, currency: pkg.localPrice.currency,
free: (str) => <Highlight>{str}</Highlight>, free: (str) => <Highlight>{str}</Highlight>,
strikethrough: (str) => <s>{str}</s>, strikethrough: (str) => <s>{str}</s>,
} }
@@ -87,8 +87,8 @@ export default function Breakfast({ packages }: BreakfastProps) {
: intl.formatMessage( : intl.formatMessage(
{ id: "breakfast.price" }, { id: "breakfast.price" },
{ {
amount: pkg.packagePrice, amount: pkg.localPrice.price,
currency: pkg.currency, currency: pkg.localPrice.currency,
} }
) )
} }

View File

@@ -150,8 +150,8 @@ export default function Summary({
{intl.formatMessage( {intl.formatMessage(
{ id: "{amount} {currency}" }, { id: "{amount} {currency}" },
{ {
amount: chosenBreakfast.totalPrice, amount: chosenBreakfast.localPrice.price,
currency: chosenBreakfast.currency, currency: chosenBreakfast.localPrice.currency,
} }
)} )}
</Caption> </Caption>

View File

@@ -1,9 +1,14 @@
import { useSearchParams } from "next/navigation"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { dt } from "@/lib/dt"
import Body from "@/components/TempDesignSystem/Text/Body" import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption" import Caption from "@/components/TempDesignSystem/Text/Caption"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import { calculatePricesPerNight } from "./utils"
import styles from "./priceList.module.css" import styles from "./priceList.module.css"
import { PriceListProps } from "@/types/components/hotelReservation/selectRate/flexibilityOption" import { PriceListProps } from "@/types/components/hotelReservation/selectRate/flexibilityOption"
@@ -11,6 +16,7 @@ import { PriceListProps } from "@/types/components/hotelReservation/selectRate/f
export default function PriceList({ export default function PriceList({
publicPrice = {}, publicPrice = {},
memberPrice = {}, memberPrice = {},
petRoomPackage,
}: PriceListProps) { }: PriceListProps) {
const intl = useIntl() const intl = useIntl()
@@ -19,7 +25,34 @@ export default function PriceList({
const { localPrice: memberLocalPrice, requestedPrice: memberRequestedPrice } = const { localPrice: memberLocalPrice, requestedPrice: memberRequestedPrice } =
memberPrice memberPrice
const petRoomLocalPrice = petRoomPackage?.localPrice
const petRoomRequestedPrice = petRoomPackage?.requestedPrice
const showRequestedPrice = publicRequestedPrice && memberRequestedPrice const showRequestedPrice = publicRequestedPrice && memberRequestedPrice
const searchParams = useSearchParams()
const fromDate = searchParams.get("fromDate")
const toDate = searchParams.get("toDate")
let nights = 1
if (fromDate && toDate) {
nights = dt(toDate).diff(dt(fromDate), "days")
}
const {
totalPublicLocalPricePerNight,
totalMemberLocalPricePerNight,
totalPublicRequestedPricePerNight,
totalMemberRequestedPricePerNight,
} = calculatePricesPerNight({
publicLocalPrice,
memberLocalPrice,
publicRequestedPrice,
memberRequestedPrice,
petRoomLocalPrice,
petRoomRequestedPrice,
nights,
})
return ( return (
<dl className={styles.priceList}> <dl className={styles.priceList}>
@@ -27,7 +60,9 @@ export default function PriceList({
<dt> <dt>
<Caption <Caption
type="bold" type="bold"
color={publicLocalPrice ? "uiTextHighContrast" : "disabled"} color={
totalPublicLocalPricePerNight ? "uiTextHighContrast" : "disabled"
}
> >
{intl.formatMessage({ id: "Standard price" })} {intl.formatMessage({ id: "Standard price" })}
</Caption> </Caption>
@@ -36,7 +71,7 @@ export default function PriceList({
{publicLocalPrice ? ( {publicLocalPrice ? (
<div className={styles.price}> <div className={styles.price}>
<Subtitle type="two" color="uiTextHighContrast"> <Subtitle type="two" color="uiTextHighContrast">
{publicLocalPrice.pricePerNight} {totalPublicLocalPricePerNight}
</Subtitle> </Subtitle>
<Body color="uiTextHighContrast" textTransform="bold"> <Body color="uiTextHighContrast" textTransform="bold">
{publicLocalPrice.currency} {publicLocalPrice.currency}
@@ -63,7 +98,7 @@ export default function PriceList({
{memberLocalPrice ? ( {memberLocalPrice ? (
<div className={styles.price}> <div className={styles.price}>
<Subtitle type="two" color="red"> <Subtitle type="two" color="red">
{memberLocalPrice.pricePerNight} {totalMemberLocalPricePerNight}
</Subtitle> </Subtitle>
<Body color="red" textTransform="bold"> <Body color="red" textTransform="bold">
{memberLocalPrice.currency} {memberLocalPrice.currency}
@@ -91,8 +126,8 @@ export default function PriceList({
<dd> <dd>
{showRequestedPrice ? ( {showRequestedPrice ? (
<Caption color="uiTextMediumContrast"> <Caption color="uiTextMediumContrast">
{publicRequestedPrice.pricePerNight}/ {totalPublicRequestedPricePerNight}/
{memberRequestedPrice.pricePerNight}{" "} {totalMemberRequestedPricePerNight}{" "}
{publicRequestedPrice.currency} {publicRequestedPrice.currency}
</Caption> </Caption>
) : ( ) : (

View File

@@ -0,0 +1,46 @@
import type { CalculatePricesPerNightProps } from "@/types/components/hotelReservation/selectRate/roomCard"
export function calculatePricesPerNight({
publicLocalPrice,
memberLocalPrice,
publicRequestedPrice,
memberRequestedPrice,
petRoomLocalPrice,
petRoomRequestedPrice,
nights,
}: CalculatePricesPerNightProps) {
const totalPublicLocalPricePerNight = publicLocalPrice
? petRoomLocalPrice
? Number(publicLocalPrice.pricePerNight) +
Number(petRoomLocalPrice.price) / nights
: Number(publicLocalPrice.pricePerNight)
: undefined
const totalMemberLocalPricePerNight = memberLocalPrice
? petRoomLocalPrice
? Number(memberLocalPrice.pricePerNight) +
Number(petRoomLocalPrice.price) / nights
: Number(memberLocalPrice.pricePerNight)
: undefined
const totalPublicRequestedPricePerNight = publicRequestedPrice
? petRoomRequestedPrice
? Number(publicRequestedPrice.pricePerNight) +
Number(petRoomRequestedPrice.price) / nights
: Number(publicRequestedPrice.pricePerNight)
: undefined
const totalMemberRequestedPricePerNight = memberRequestedPrice
? petRoomRequestedPrice
? Number(memberRequestedPrice.pricePerNight) +
Number(petRoomRequestedPrice.price) / nights
: Number(memberRequestedPrice.pricePerNight)
: undefined
return {
totalPublicLocalPricePerNight,
totalMemberLocalPricePerNight,
totalPublicRequestedPricePerNight,
totalMemberRequestedPricePerNight,
}
}

View File

@@ -20,6 +20,7 @@ export default function FlexibilityOption({
roomType, roomType,
roomTypeCode, roomTypeCode,
features, features,
petRoomPackage,
handleSelectRate, handleSelectRate,
}: FlexibilityOptionProps) { }: FlexibilityOptionProps) {
const [rootDiv, setRootDiv] = useState<Element | undefined>(undefined) const [rootDiv, setRootDiv] = useState<Element | undefined>(undefined)
@@ -113,7 +114,11 @@ export default function FlexibilityOption({
<Caption color="uiTextHighContrast">{name}</Caption> <Caption color="uiTextHighContrast">{name}</Caption>
<Caption color="uiTextPlaceholder">({paymentTerm})</Caption> <Caption color="uiTextPlaceholder">({paymentTerm})</Caption>
</div> </div>
<PriceTable publicPrice={publicPrice} memberPrice={memberPrice} /> <PriceTable
publicPrice={publicPrice}
memberPrice={memberPrice}
petRoomPackage={petRoomPackage}
/>
<CheckCircleIcon <CheckCircleIcon
color="blue" color="blue"
className={styles.checkIcon} className={styles.checkIcon}

View File

@@ -1,6 +1,7 @@
import { differenceInCalendarDays } from "date-fns"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { dt } from "@/lib/dt"
import Button from "@/components/TempDesignSystem/Button" import Button from "@/components/TempDesignSystem/Button"
import Body from "@/components/TempDesignSystem/Text/Body" import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption" import Caption from "@/components/TempDesignSystem/Text/Caption"
@@ -36,12 +37,12 @@ export default function RateSummary({
(pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM (pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM
) )
const petRoomPrice = petRoomPackage?.calculatedPrice ?? null const petRoomPrice = petRoomPackage?.localPrice.totalPrice ?? null
const petRoomCurrency = petRoomPackage?.currency ?? null const petRoomCurrency = petRoomPackage?.localPrice.currency ?? null
const checkInDate = new Date(roomsAvailability.checkInDate) const checkInDate = new Date(roomsAvailability.checkInDate)
const checkOutDate = new Date(roomsAvailability.checkOutDate) const checkOutDate = new Date(roomsAvailability.checkOutDate)
const nights = differenceInCalendarDays(checkOutDate, checkInDate) const nights = dt(checkOutDate).diff(dt(checkInDate), "days")
return ( return (
<div className={styles.summary}> <div className={styles.summary}>

View File

@@ -18,12 +18,14 @@ import { getIconForFeatureCode } from "../../utils"
import styles from "./roomCard.module.css" import styles from "./roomCard.module.css"
import type { RoomCardProps } from "@/types/components/hotelReservation/selectRate/roomCard" import type { RoomCardProps } from "@/types/components/hotelReservation/selectRate/roomCard"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
export default function RoomCard({ export default function RoomCard({
rateDefinitions, rateDefinitions,
roomConfiguration, roomConfiguration,
roomCategories, roomCategories,
selectedPackages, selectedPackages,
packages,
handleSelectRate, handleSelectRate,
}: RoomCardProps) { }: RoomCardProps) {
const intl = useIntl() const intl = useIntl()
@@ -55,6 +57,10 @@ export default function RoomCard({
?.generalTerms ?.generalTerms
} }
const petRoomPackage = packages.find(
(pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM
)
const selectedRoom = roomCategories.find( const selectedRoom = roomCategories.find(
(room) => room.name === roomConfiguration.roomType (room) => room.name === roomConfiguration.roomType
) )
@@ -118,6 +124,7 @@ export default function RoomCard({
roomType={roomConfiguration.roomType} roomType={roomConfiguration.roomType}
roomTypeCode={roomConfiguration.roomTypeCode} roomTypeCode={roomConfiguration.roomTypeCode}
features={roomConfiguration.features} features={roomConfiguration.features}
petRoomPackage={petRoomPackage}
/> />
))} ))}
</div> </div>

View File

@@ -72,6 +72,7 @@ export default function RoomSelection({
roomCategories={roomCategories} roomCategories={roomCategories}
handleSelectRate={setRateSummary} handleSelectRate={setRateSummary}
selectedPackages={selectedPackages} selectedPackages={selectedPackages}
packages={packages}
/> />
</li> </li>
))} ))}

View File

@@ -488,7 +488,7 @@ export type HotelsAvailability = z.infer<typeof hotelsAvailabilitySchema>
export type HotelsAvailabilityPrices = export type HotelsAvailabilityPrices =
HotelsAvailability["data"][number]["attributes"]["bestPricePerNight"] HotelsAvailability["data"][number]["attributes"]["bestPricePerNight"]
const priceSchema = z.object({ export const priceSchema = z.object({
pricePerNight: z.string(), pricePerNight: z.string(),
pricePerStay: z.string(), pricePerStay: z.string(),
currency: z.string(), currency: z.string(),
@@ -774,17 +774,27 @@ export const apiLocationsSchema = z.object({
), ),
}) })
const breakfastPackagePriceSchema = z
.object({
currency: z.nativeEnum(CurrencyEnum),
price: z.string(),
totalPrice: z.string(),
})
.default({
currency: CurrencyEnum.SEK,
price: "0",
totalPrice: "0",
}) // TODO: Remove optional and default when the API change has been deployed
export const breakfastPackageSchema = z.object({ export const breakfastPackageSchema = z.object({
code: z.string(), code: z.string(),
currency: z.nativeEnum(CurrencyEnum),
description: z.string(), description: z.string(),
originalPrice: z.number().default(0), localPrice: breakfastPackagePriceSchema,
packagePrice: z.number(), requestedPrice: breakfastPackagePriceSchema,
packageType: z.enum([ packageType: z.enum([
PackageTypeEnum.BreakfastAdult, PackageTypeEnum.BreakfastAdult,
PackageTypeEnum.BreakfastChildren, PackageTypeEnum.BreakfastChildren,
]), ]),
totalPrice: z.number(),
}) })
export const breakfastPackagesSchema = z export const breakfastPackagesSchema = z

View File

@@ -913,11 +913,16 @@ export const hotelQueryRouter = router({
const { hotelId, startDate, endDate, adults, children, packageCodes } = const { hotelId, startDate, endDate, adults, children, packageCodes } =
input input
const { lang } = ctx
const apiLang = toApiLang(lang)
const searchParams = new URLSearchParams({ const searchParams = new URLSearchParams({
startDate, startDate,
endDate, endDate,
adults: adults.toString(), adults: adults.toString(),
children: children.toString(), children: children.toString(),
language: apiLang,
}) })
packageCodes.forEach((code) => { packageCodes.forEach((code) => {
@@ -993,11 +998,17 @@ export const hotelQueryRouter = router({
breakfast: safeProtectedServiceProcedure breakfast: safeProtectedServiceProcedure
.input(getBreakfastPackageInputSchema) .input(getBreakfastPackageInputSchema)
.query(async function ({ ctx, input }) { .query(async function ({ ctx, input }) {
const { lang } = ctx
const apiLang = toApiLang(lang)
const params = { const params = {
Adults: input.adults, Adults: input.adults,
EndDate: dt(input.toDate).format("YYYY-MM-DD"), EndDate: dt(input.toDate).format("YYYY-MM-DD"),
StartDate: dt(input.fromDate).format("YYYY-MM-DD"), StartDate: dt(input.fromDate).format("YYYY-MM-DD"),
language: apiLang,
} }
const metricsData = { ...params, hotelId: input.hotelId } const metricsData = { ...params, hotelId: input.hotelId }
breakfastPackagesCounter.add(1, metricsData) breakfastPackagesCounter.add(1, metricsData)
console.info( console.info(
@@ -1084,10 +1095,13 @@ export const hotelQueryRouter = router({
const freeBreakfastPackage = breakfastPackages.data.find( const freeBreakfastPackage = breakfastPackages.data.find(
(pkg) => pkg.code === BreakfastPackageEnum.FREE_MEMBER_BREAKFAST (pkg) => pkg.code === BreakfastPackageEnum.FREE_MEMBER_BREAKFAST
) )
if (freeBreakfastPackage) { if (freeBreakfastPackage && freeBreakfastPackage.localPrice) {
if (originalBreakfastPackage) { if (
freeBreakfastPackage.originalPrice = originalBreakfastPackage &&
originalBreakfastPackage.packagePrice originalBreakfastPackage.localPrice
) {
freeBreakfastPackage.localPrice.price =
originalBreakfastPackage.localPrice.price
} }
return [freeBreakfastPackage] return [freeBreakfastPackage]
} }

View File

@@ -1,6 +1,7 @@
import { z } from "zod" import { z } from "zod"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import { CurrencyEnum } from "@/types/enums/currency"
export const getRoomPackagesInputSchema = z.object({ export const getRoomPackagesInputSchema = z.object({
hotelId: z.string(), hotelId: z.string(),
@@ -11,17 +12,25 @@ export const getRoomPackagesInputSchema = z.object({
packageCodes: z.array(z.string()).optional().default([]), packageCodes: z.array(z.string()).optional().default([]),
}) })
const packagesSchema = z.array( export const packagePriceSchema = z
z.object({ .object({
code: z.enum([ currency: z.nativeEnum(CurrencyEnum),
RoomPackageCodeEnum.PET_ROOM, price: z.string(),
RoomPackageCodeEnum.ALLERGY_ROOM, totalPrice: z.string(),
RoomPackageCodeEnum.ACCESSIBILITY_ROOM, })
]), .optional()
.default({
currency: CurrencyEnum.SEK,
price: "0",
totalPrice: "0",
}) // TODO: Remove optional and default when the API change has been deployed
export const packagesSchema = z.object({
code: z.nativeEnum(RoomPackageCodeEnum),
itemCode: z.string(), itemCode: z.string(),
description: z.string(), description: z.string(),
currency: z.string(), localPrice: packagePriceSchema,
calculatedPrice: z.number(), requestedPrice: packagePriceSchema,
inventories: z.array( inventories: z.array(
z.object({ z.object({
date: z.string(), date: z.string(),
@@ -29,15 +38,14 @@ const packagesSchema = z.array(
available: z.number(), available: z.number(),
}) })
), ),
}) })
)
export const getRoomPackagesSchema = z export const getRoomPackagesSchema = z
.object({ .object({
data: z.object({ data: z.object({
attributes: z.object({ attributes: z.object({
hotelId: z.number(), hotelId: z.number(),
packages: packagesSchema, packages: z.array(packagesSchema),
}), }),
relationships: z relationships: z
.object({ .object({

View File

@@ -1,14 +1,17 @@
import { z } from "zod" import { z } from "zod"
import { import {
priceSchema,
Product, Product,
productTypePriceSchema, productTypePriceSchema,
RoomConfiguration, RoomConfiguration,
} from "@/server/routers/hotels/output" } from "@/server/routers/hotels/output"
import { RoomPackage } from "./roomFilter"
import { Rate } from "./selectRate" import { Rate } from "./selectRate"
type ProductPrice = z.output<typeof productTypePriceSchema> type ProductPrice = z.output<typeof productTypePriceSchema>
export type RoomPriceSchema = z.output<typeof priceSchema>
export type FlexibilityOptionProps = { export type FlexibilityOptionProps = {
product: Product | undefined product: Product | undefined
@@ -19,10 +22,12 @@ export type FlexibilityOptionProps = {
roomType: RoomConfiguration["roomType"] roomType: RoomConfiguration["roomType"]
roomTypeCode: RoomConfiguration["roomTypeCode"] roomTypeCode: RoomConfiguration["roomTypeCode"]
features: RoomConfiguration["features"] features: RoomConfiguration["features"]
petRoomPackage: RoomPackage | undefined
handleSelectRate: (rate: Rate) => void handleSelectRate: (rate: Rate) => void
} }
export interface PriceListProps { export interface PriceListProps {
publicPrice?: ProductPrice | Record<string, never> publicPrice?: ProductPrice | Record<string, never>
memberPrice?: ProductPrice | Record<string, never> memberPrice?: ProductPrice | Record<string, never>
petRoomPackage?: RoomPackage | undefined
} }

View File

@@ -1,17 +1,34 @@
import { z } from "zod"
import { import {
RateDefinition, RateDefinition,
RoomConfiguration, RoomConfiguration,
} from "@/server/routers/hotels/output" } from "@/server/routers/hotels/output"
import { packagePriceSchema } from "@/server/routers/hotels/schemas/packages"
import { RoomPriceSchema } from "./flexibilityOption"
import { Rate } from "./selectRate" import { Rate } from "./selectRate"
import type { RoomData } from "@/types/hotel" import type { RoomData } from "@/types/hotel"
import type { RoomPackageCodes } from "./roomFilter" import type { RoomPackageCodes, RoomPackageData } from "./roomFilter"
export type RoomCardProps = { export type RoomCardProps = {
roomConfiguration: RoomConfiguration roomConfiguration: RoomConfiguration
rateDefinitions: RateDefinition[] rateDefinitions: RateDefinition[]
roomCategories: RoomData[] roomCategories: RoomData[]
selectedPackages: RoomPackageCodes[] selectedPackages: RoomPackageCodes[]
packages: RoomPackageData
handleSelectRate: (rate: Rate) => void handleSelectRate: (rate: Rate) => void
} }
type RoomPackagePriceSchema = z.output<typeof packagePriceSchema>
export type CalculatePricesPerNightProps = {
publicLocalPrice: RoomPriceSchema
memberLocalPrice: RoomPriceSchema
publicRequestedPrice?: RoomPriceSchema
memberRequestedPrice?: RoomPriceSchema
petRoomLocalPrice?: RoomPackagePriceSchema
petRoomRequestedPrice?: RoomPackagePriceSchema
nights: number
}

View File

@@ -1,6 +1,9 @@
import { z } from "zod" import { z } from "zod"
import { getRoomPackagesSchema } from "@/server/routers/hotels/schemas/packages" import {
getRoomPackagesSchema,
packagesSchema,
} from "@/server/routers/hotels/schemas/packages"
export enum RoomPackageCodeEnum { export enum RoomPackageCodeEnum {
PET_ROOM = "PETR", PET_ROOM = "PETR",
@@ -17,3 +20,5 @@ export interface RoomPackageData
extends z.output<typeof getRoomPackagesSchema> {} extends z.output<typeof getRoomPackagesSchema> {}
export type RoomPackageCodes = RoomPackageData[number]["code"] export type RoomPackageCodes = RoomPackageData[number]["code"]
export type RoomPackage = z.output<typeof packagesSchema>