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:
@@ -76,21 +76,21 @@ export default function Breakfast({ packages }: BreakfastProps) {
|
||||
subtitle={
|
||||
pkg.code === BreakfastPackageEnum.FREE_MEMBER_BREAKFAST
|
||||
? intl.formatMessage<React.ReactNode>(
|
||||
{ id: "breakfast.price.free" },
|
||||
{
|
||||
amount: pkg.originalPrice,
|
||||
currency: pkg.currency,
|
||||
free: (str) => <Highlight>{str}</Highlight>,
|
||||
strikethrough: (str) => <s>{str}</s>,
|
||||
}
|
||||
)
|
||||
{ id: "breakfast.price.free" },
|
||||
{
|
||||
amount: pkg.localPrice.price,
|
||||
currency: pkg.localPrice.currency,
|
||||
free: (str) => <Highlight>{str}</Highlight>,
|
||||
strikethrough: (str) => <s>{str}</s>,
|
||||
}
|
||||
)
|
||||
: intl.formatMessage(
|
||||
{ id: "breakfast.price" },
|
||||
{
|
||||
amount: pkg.packagePrice,
|
||||
currency: pkg.currency,
|
||||
}
|
||||
)
|
||||
{ id: "breakfast.price" },
|
||||
{
|
||||
amount: pkg.localPrice.price,
|
||||
currency: pkg.localPrice.currency,
|
||||
}
|
||||
)
|
||||
}
|
||||
text={intl.formatMessage({
|
||||
id: "All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.",
|
||||
|
||||
@@ -150,8 +150,8 @@ export default function Summary({
|
||||
{intl.formatMessage(
|
||||
{ id: "{amount} {currency}" },
|
||||
{
|
||||
amount: chosenBreakfast.totalPrice,
|
||||
currency: chosenBreakfast.currency,
|
||||
amount: chosenBreakfast.localPrice.price,
|
||||
currency: chosenBreakfast.localPrice.currency,
|
||||
}
|
||||
)}
|
||||
</Caption>
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import { useSearchParams } from "next/navigation"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { dt } from "@/lib/dt"
|
||||
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
|
||||
import { calculatePricesPerNight } from "./utils"
|
||||
|
||||
import styles from "./priceList.module.css"
|
||||
|
||||
import { PriceListProps } from "@/types/components/hotelReservation/selectRate/flexibilityOption"
|
||||
@@ -11,6 +16,7 @@ import { PriceListProps } from "@/types/components/hotelReservation/selectRate/f
|
||||
export default function PriceList({
|
||||
publicPrice = {},
|
||||
memberPrice = {},
|
||||
petRoomPackage,
|
||||
}: PriceListProps) {
|
||||
const intl = useIntl()
|
||||
|
||||
@@ -19,7 +25,34 @@ export default function PriceList({
|
||||
const { localPrice: memberLocalPrice, requestedPrice: memberRequestedPrice } =
|
||||
memberPrice
|
||||
|
||||
const petRoomLocalPrice = petRoomPackage?.localPrice
|
||||
const petRoomRequestedPrice = petRoomPackage?.requestedPrice
|
||||
|
||||
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 (
|
||||
<dl className={styles.priceList}>
|
||||
@@ -27,7 +60,9 @@ export default function PriceList({
|
||||
<dt>
|
||||
<Caption
|
||||
type="bold"
|
||||
color={publicLocalPrice ? "uiTextHighContrast" : "disabled"}
|
||||
color={
|
||||
totalPublicLocalPricePerNight ? "uiTextHighContrast" : "disabled"
|
||||
}
|
||||
>
|
||||
{intl.formatMessage({ id: "Standard price" })}
|
||||
</Caption>
|
||||
@@ -36,7 +71,7 @@ export default function PriceList({
|
||||
{publicLocalPrice ? (
|
||||
<div className={styles.price}>
|
||||
<Subtitle type="two" color="uiTextHighContrast">
|
||||
{publicLocalPrice.pricePerNight}
|
||||
{totalPublicLocalPricePerNight}
|
||||
</Subtitle>
|
||||
<Body color="uiTextHighContrast" textTransform="bold">
|
||||
{publicLocalPrice.currency}
|
||||
@@ -63,7 +98,7 @@ export default function PriceList({
|
||||
{memberLocalPrice ? (
|
||||
<div className={styles.price}>
|
||||
<Subtitle type="two" color="red">
|
||||
{memberLocalPrice.pricePerNight}
|
||||
{totalMemberLocalPricePerNight}
|
||||
</Subtitle>
|
||||
<Body color="red" textTransform="bold">
|
||||
{memberLocalPrice.currency}
|
||||
@@ -91,8 +126,8 @@ export default function PriceList({
|
||||
<dd>
|
||||
{showRequestedPrice ? (
|
||||
<Caption color="uiTextMediumContrast">
|
||||
{publicRequestedPrice.pricePerNight}/
|
||||
{memberRequestedPrice.pricePerNight}{" "}
|
||||
{totalPublicRequestedPricePerNight}/
|
||||
{totalMemberRequestedPricePerNight}{" "}
|
||||
{publicRequestedPrice.currency}
|
||||
</Caption>
|
||||
) : (
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ export default function FlexibilityOption({
|
||||
roomType,
|
||||
roomTypeCode,
|
||||
features,
|
||||
petRoomPackage,
|
||||
handleSelectRate,
|
||||
}: FlexibilityOptionProps) {
|
||||
const [rootDiv, setRootDiv] = useState<Element | undefined>(undefined)
|
||||
@@ -113,7 +114,11 @@ export default function FlexibilityOption({
|
||||
<Caption color="uiTextHighContrast">{name}</Caption>
|
||||
<Caption color="uiTextPlaceholder">({paymentTerm})</Caption>
|
||||
</div>
|
||||
<PriceTable publicPrice={publicPrice} memberPrice={memberPrice} />
|
||||
<PriceTable
|
||||
publicPrice={publicPrice}
|
||||
memberPrice={memberPrice}
|
||||
petRoomPackage={petRoomPackage}
|
||||
/>
|
||||
<CheckCircleIcon
|
||||
color="blue"
|
||||
className={styles.checkIcon}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { differenceInCalendarDays } from "date-fns"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { dt } from "@/lib/dt"
|
||||
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
@@ -36,12 +37,12 @@ export default function RateSummary({
|
||||
(pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM
|
||||
)
|
||||
|
||||
const petRoomPrice = petRoomPackage?.calculatedPrice ?? null
|
||||
const petRoomCurrency = petRoomPackage?.currency ?? null
|
||||
const petRoomPrice = petRoomPackage?.localPrice.totalPrice ?? null
|
||||
const petRoomCurrency = petRoomPackage?.localPrice.currency ?? null
|
||||
|
||||
const checkInDate = new Date(roomsAvailability.checkInDate)
|
||||
const checkOutDate = new Date(roomsAvailability.checkOutDate)
|
||||
const nights = differenceInCalendarDays(checkOutDate, checkInDate)
|
||||
const nights = dt(checkOutDate).diff(dt(checkInDate), "days")
|
||||
|
||||
return (
|
||||
<div className={styles.summary}>
|
||||
|
||||
@@ -18,12 +18,14 @@ import { getIconForFeatureCode } from "../../utils"
|
||||
import styles from "./roomCard.module.css"
|
||||
|
||||
import type { RoomCardProps } from "@/types/components/hotelReservation/selectRate/roomCard"
|
||||
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||
|
||||
export default function RoomCard({
|
||||
rateDefinitions,
|
||||
roomConfiguration,
|
||||
roomCategories,
|
||||
selectedPackages,
|
||||
packages,
|
||||
handleSelectRate,
|
||||
}: RoomCardProps) {
|
||||
const intl = useIntl()
|
||||
@@ -55,6 +57,10 @@ export default function RoomCard({
|
||||
?.generalTerms
|
||||
}
|
||||
|
||||
const petRoomPackage = packages.find(
|
||||
(pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM
|
||||
)
|
||||
|
||||
const selectedRoom = roomCategories.find(
|
||||
(room) => room.name === roomConfiguration.roomType
|
||||
)
|
||||
@@ -118,6 +124,7 @@ export default function RoomCard({
|
||||
roomType={roomConfiguration.roomType}
|
||||
roomTypeCode={roomConfiguration.roomTypeCode}
|
||||
features={roomConfiguration.features}
|
||||
petRoomPackage={petRoomPackage}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -72,6 +72,7 @@ export default function RoomSelection({
|
||||
roomCategories={roomCategories}
|
||||
handleSelectRate={setRateSummary}
|
||||
selectedPackages={selectedPackages}
|
||||
packages={packages}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
|
||||
@@ -488,7 +488,7 @@ export type HotelsAvailability = z.infer<typeof hotelsAvailabilitySchema>
|
||||
export type HotelsAvailabilityPrices =
|
||||
HotelsAvailability["data"][number]["attributes"]["bestPricePerNight"]
|
||||
|
||||
const priceSchema = z.object({
|
||||
export const priceSchema = z.object({
|
||||
pricePerNight: z.string(),
|
||||
pricePerStay: 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({
|
||||
code: z.string(),
|
||||
currency: z.nativeEnum(CurrencyEnum),
|
||||
description: z.string(),
|
||||
originalPrice: z.number().default(0),
|
||||
packagePrice: z.number(),
|
||||
localPrice: breakfastPackagePriceSchema,
|
||||
requestedPrice: breakfastPackagePriceSchema,
|
||||
packageType: z.enum([
|
||||
PackageTypeEnum.BreakfastAdult,
|
||||
PackageTypeEnum.BreakfastChildren,
|
||||
]),
|
||||
totalPrice: z.number(),
|
||||
})
|
||||
|
||||
export const breakfastPackagesSchema = z
|
||||
|
||||
@@ -913,11 +913,16 @@ export const hotelQueryRouter = router({
|
||||
const { hotelId, startDate, endDate, adults, children, packageCodes } =
|
||||
input
|
||||
|
||||
const { lang } = ctx
|
||||
|
||||
const apiLang = toApiLang(lang)
|
||||
|
||||
const searchParams = new URLSearchParams({
|
||||
startDate,
|
||||
endDate,
|
||||
adults: adults.toString(),
|
||||
children: children.toString(),
|
||||
language: apiLang,
|
||||
})
|
||||
|
||||
packageCodes.forEach((code) => {
|
||||
@@ -993,11 +998,17 @@ export const hotelQueryRouter = router({
|
||||
breakfast: safeProtectedServiceProcedure
|
||||
.input(getBreakfastPackageInputSchema)
|
||||
.query(async function ({ ctx, input }) {
|
||||
const { lang } = ctx
|
||||
|
||||
const apiLang = toApiLang(lang)
|
||||
|
||||
const params = {
|
||||
Adults: input.adults,
|
||||
EndDate: dt(input.toDate).format("YYYY-MM-DD"),
|
||||
StartDate: dt(input.fromDate).format("YYYY-MM-DD"),
|
||||
language: apiLang,
|
||||
}
|
||||
|
||||
const metricsData = { ...params, hotelId: input.hotelId }
|
||||
breakfastPackagesCounter.add(1, metricsData)
|
||||
console.info(
|
||||
@@ -1084,10 +1095,13 @@ export const hotelQueryRouter = router({
|
||||
const freeBreakfastPackage = breakfastPackages.data.find(
|
||||
(pkg) => pkg.code === BreakfastPackageEnum.FREE_MEMBER_BREAKFAST
|
||||
)
|
||||
if (freeBreakfastPackage) {
|
||||
if (originalBreakfastPackage) {
|
||||
freeBreakfastPackage.originalPrice =
|
||||
originalBreakfastPackage.packagePrice
|
||||
if (freeBreakfastPackage && freeBreakfastPackage.localPrice) {
|
||||
if (
|
||||
originalBreakfastPackage &&
|
||||
originalBreakfastPackage.localPrice
|
||||
) {
|
||||
freeBreakfastPackage.localPrice.price =
|
||||
originalBreakfastPackage.localPrice.price
|
||||
}
|
||||
return [freeBreakfastPackage]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
|
||||
import { CurrencyEnum } from "@/types/enums/currency"
|
||||
|
||||
export const getRoomPackagesInputSchema = z.object({
|
||||
hotelId: z.string(),
|
||||
@@ -11,33 +12,40 @@ export const getRoomPackagesInputSchema = z.object({
|
||||
packageCodes: z.array(z.string()).optional().default([]),
|
||||
})
|
||||
|
||||
const packagesSchema = z.array(
|
||||
z.object({
|
||||
code: z.enum([
|
||||
RoomPackageCodeEnum.PET_ROOM,
|
||||
RoomPackageCodeEnum.ALLERGY_ROOM,
|
||||
RoomPackageCodeEnum.ACCESSIBILITY_ROOM,
|
||||
]),
|
||||
itemCode: z.string(),
|
||||
description: z.string(),
|
||||
currency: z.string(),
|
||||
calculatedPrice: z.number(),
|
||||
inventories: z.array(
|
||||
z.object({
|
||||
date: z.string(),
|
||||
total: z.number(),
|
||||
available: z.number(),
|
||||
})
|
||||
),
|
||||
export const packagePriceSchema = z
|
||||
.object({
|
||||
currency: z.nativeEnum(CurrencyEnum),
|
||||
price: z.string(),
|
||||
totalPrice: z.string(),
|
||||
})
|
||||
)
|
||||
.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(),
|
||||
description: z.string(),
|
||||
localPrice: packagePriceSchema,
|
||||
requestedPrice: packagePriceSchema,
|
||||
inventories: z.array(
|
||||
z.object({
|
||||
date: z.string(),
|
||||
total: z.number(),
|
||||
available: z.number(),
|
||||
})
|
||||
),
|
||||
})
|
||||
|
||||
export const getRoomPackagesSchema = z
|
||||
.object({
|
||||
data: z.object({
|
||||
attributes: z.object({
|
||||
hotelId: z.number(),
|
||||
packages: packagesSchema,
|
||||
packages: z.array(packagesSchema),
|
||||
}),
|
||||
relationships: z
|
||||
.object({
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import {
|
||||
priceSchema,
|
||||
Product,
|
||||
productTypePriceSchema,
|
||||
RoomConfiguration,
|
||||
} from "@/server/routers/hotels/output"
|
||||
|
||||
import { RoomPackage } from "./roomFilter"
|
||||
import { Rate } from "./selectRate"
|
||||
|
||||
type ProductPrice = z.output<typeof productTypePriceSchema>
|
||||
export type RoomPriceSchema = z.output<typeof priceSchema>
|
||||
|
||||
export type FlexibilityOptionProps = {
|
||||
product: Product | undefined
|
||||
@@ -19,10 +22,12 @@ export type FlexibilityOptionProps = {
|
||||
roomType: RoomConfiguration["roomType"]
|
||||
roomTypeCode: RoomConfiguration["roomTypeCode"]
|
||||
features: RoomConfiguration["features"]
|
||||
petRoomPackage: RoomPackage | undefined
|
||||
handleSelectRate: (rate: Rate) => void
|
||||
}
|
||||
|
||||
export interface PriceListProps {
|
||||
publicPrice?: ProductPrice | Record<string, never>
|
||||
memberPrice?: ProductPrice | Record<string, never>
|
||||
petRoomPackage?: RoomPackage | undefined
|
||||
}
|
||||
|
||||
@@ -1,17 +1,34 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import {
|
||||
RateDefinition,
|
||||
RoomConfiguration,
|
||||
} from "@/server/routers/hotels/output"
|
||||
import { packagePriceSchema } from "@/server/routers/hotels/schemas/packages"
|
||||
|
||||
import { RoomPriceSchema } from "./flexibilityOption"
|
||||
import { Rate } from "./selectRate"
|
||||
|
||||
import type { RoomData } from "@/types/hotel"
|
||||
import type { RoomPackageCodes } from "./roomFilter"
|
||||
import type { RoomPackageCodes, RoomPackageData } from "./roomFilter"
|
||||
|
||||
export type RoomCardProps = {
|
||||
roomConfiguration: RoomConfiguration
|
||||
rateDefinitions: RateDefinition[]
|
||||
roomCategories: RoomData[]
|
||||
selectedPackages: RoomPackageCodes[]
|
||||
packages: RoomPackageData
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { getRoomPackagesSchema } from "@/server/routers/hotels/schemas/packages"
|
||||
import {
|
||||
getRoomPackagesSchema,
|
||||
packagesSchema,
|
||||
} from "@/server/routers/hotels/schemas/packages"
|
||||
|
||||
export enum RoomPackageCodeEnum {
|
||||
PET_ROOM = "PETR",
|
||||
@@ -17,3 +20,5 @@ export interface RoomPackageData
|
||||
extends z.output<typeof getRoomPackagesSchema> {}
|
||||
|
||||
export type RoomPackageCodes = RoomPackageData[number]["code"]
|
||||
|
||||
export type RoomPackage = z.output<typeof packagesSchema>
|
||||
|
||||
Reference in New Issue
Block a user