feat/SW-1546-list-ancillaries-my-stay (pull request #1259)

Feat/SW-1546 list ancillaries my stay

* feat(SW-1546): foundation for listing ancillaries

* feat(SW-1546): foundation for listing ancillaries

* feat(SW-1546): refactor type

* feat(SW-1546): fix date format

* feat(SW-1546): refactor usestate

* feat(SW-1546): refactor typing

* feat(SW-1546): refactor types

* feat(SW-1546): responsive width on modal

* feat(SW-1546): update design

* feat(SW-1546): rebase master

* feat(SW-1546): show points only if logged in

* feat(SW-1546): always show points

* feat(SW-1546): small fix

* feat(SW-1546): remove spread object

* feat(SW-1546): fix import order


Approved-by: Simon.Emanuelsson
This commit is contained in:
Bianca Widstam
2025-02-14 10:37:17 +00:00
parent f5e0214313
commit 224a41ec74
19 changed files with 420 additions and 7 deletions

View File

@@ -0,0 +1,86 @@
.container {
display: flex;
flex-direction: column;
gap: var(--Spacing-x2);
}
.title {
display: flex;
justify-content: space-between;
}
.modalContent {
width: 100%;
}
.tabs {
display: flex;
gap: var(--Spacing-x1);
padding: var(--Spacing-x3) 0;
flex-wrap: wrap;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(251px, 1fr));
gap: var(--Spacing-x2);
max-height: 417px;
overflow-y: auto;
padding-right: var(--Spacing-x1);
margin-top: var(--Spacing-x2);
}
.chip {
border-radius: 28px;
padding: var(--Spacing-x-one-and-half) var(--Spacing-x2);
border: none;
cursor: pointer;
background: var(--Base-Surface-Subtle-Normal);
}
.chip.selected {
background: var(--Base-Text-High-contrast);
}
.ancillaries {
display: none;
}
.mobileAncillaries {
display: flex;
gap: var(--Spacing-x2);
overflow: hidden;
overflow-x: auto;
scrollbar-width: none;
-ms-overflow-style: none;
flex-wrap: nowrap;
}
.modal {
display: none;
}
@media screen and (min-width: 768px) {
.modalContent {
width: 600px;
}
}
@media screen and (min-width: 1052px) {
.mobileAncillaries {
display: none;
}
.modalContent {
width: 833px;
}
.ancillaries {
display: grid;
grid-template-columns: repeat(4, minmax(251px, 1fr));
gap: var(--Spacing-x2);
}
.modal {
display: block;
}
}

View File

@@ -1,3 +1,111 @@
export function Ancillaries() { "use client"
return <div>Add Ancillaries</div> import { useState } from "react"
import { useIntl } from "react-intl"
import { ChevronRightSmallIcon } from "@/components/Icons"
import Modal from "@/components/Modal"
import { AncillaryCard } from "@/components/TempDesignSystem/AncillaryCard"
import Button from "@/components/TempDesignSystem/Button"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Title from "@/components/TempDesignSystem/Text/Title"
import styles from "./ancillaries.module.css"
import type {
Ancillaries,
AncillariesProps,
Ancillary,
} from "@/types/components/myPages/myStay/ancillaries"
export function Ancillaries({ ancillaries }: AncillariesProps) {
const intl = useIntl()
const [selectedCategory, setSelectedCategory] = useState<string | null>(
() => {
return ancillaries?.[0]?.categoryName ?? null
}
)
if (!ancillaries?.length) {
return null
}
function mergeAncillaries(
ancillaries: Ancillaries
): Ancillary["ancillaryContent"] {
const uniqueAncillaries = new Map(
ancillaries
.flatMap((category) => category.ancillaryContent)
.map((ancillary) => [ancillary.id, ancillary])
)
return [...uniqueAncillaries.values()]
}
const allAncillaries = mergeAncillaries(ancillaries)
return (
<div className={styles.container}>
<div className={styles.title}>
<Title as="h5">{intl.formatMessage({ id: "Upgrade your stay" })}</Title>
<div className={styles.modal}>
<Modal
trigger={
<Button theme="base" variant="icon" intent="text" size="small">
{intl.formatMessage({ id: "View all" })}
<ChevronRightSmallIcon
width={20}
height={20}
color="baseButtonTextOnFillNormal"
/>
</Button>
}
title={intl.formatMessage({ id: "Upgrade your stay" })}
>
<div className={styles.modalContent}>
<div className={styles.tabs}>
{ancillaries.map((category) => (
<button
key={category.categoryName}
className={`${styles.chip} ${category.categoryName === selectedCategory ? styles.selected : ""}`}
onClick={() => setSelectedCategory(category.categoryName)}
>
<Caption
color={
category.categoryName === selectedCategory
? "pale"
: "baseTextHighContrast"
}
>
{category.categoryName}
</Caption>
</button>
))}
</div>
<div className={styles.grid}>
{ancillaries
.find(
(category) => category.categoryName === selectedCategory
)
?.ancillaryContent.map(({ description, ...ancillary }) => (
<AncillaryCard key={ancillary.id} ancillary={ancillary} />
))}
</div>
</div>
</Modal>
</div>
</div>
<div className={styles.ancillaries}>
{allAncillaries.slice(0, 4).map(({ description, ...ancillary }) => (
<AncillaryCard key={ancillary.id} ancillary={ancillary} />
))}
</div>
<div className={styles.mobileAncillaries}>
{allAncillaries.map(({ description, ...ancillary }) => (
<AncillaryCard key={ancillary.id} ancillary={ancillary} />
))}
</div>
</div>
)
} }

View File

@@ -1,4 +1,8 @@
import { getBookingConfirmation } from "@/lib/trpc/memoizedRequests" import { dt } from "@/lib/dt"
import {
getAncillaryPackages,
getBookingConfirmation,
} from "@/lib/trpc/memoizedRequests"
import Divider from "@/components/TempDesignSystem/Divider" import Divider from "@/components/TempDesignSystem/Divider"
@@ -14,13 +18,21 @@ import styles from "./myStay.module.css"
export async function MyStay({ reservationId }: { reservationId: string }) { export async function MyStay({ reservationId }: { reservationId: string }) {
const { booking, hotel, room } = await getBookingConfirmation(reservationId) const { booking, hotel, room } = await getBookingConfirmation(reservationId)
const fromDate = dt(booking.checkInDate).format("YYYY-MM-DD")
const toDate = dt(booking.checkOutDate).format("YYYY-MM-DD")
const hotelId = hotel.operaId
const ancillaryInput = { fromDate, hotelId, toDate }
void getAncillaryPackages(ancillaryInput)
const ancillaryPackages = await getAncillaryPackages(ancillaryInput)
return ( return (
<main className={styles.main}> <main className={styles.main}>
<Header booking={booking} hotel={hotel} /> <Header booking={booking} hotel={hotel} />
<BookingActions /> <BookingActions />
{room && <Rooms booking={booking} mainRoom={room} />} {room && <Rooms booking={booking} mainRoom={room} />}
<Ancillaries /> {booking.showAncillaries && (
<Ancillaries ancillaries={ancillaryPackages} />
)}
<Divider color="primaryLightSubtle" /> <Divider color="primaryLightSubtle" />
<PaymentDetails booking={booking} /> <PaymentDetails booking={booking} />
<Divider color="primaryLightSubtle" /> <Divider color="primaryLightSubtle" />

View File

@@ -1,7 +1,8 @@
.ancillaryCard { .ancillaryCard {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
max-width: 280px; min-width: 251px;
max-width: 286px;
} }
.imageContainer { .imageContainer {

View File

@@ -584,6 +584,7 @@
"U-shape": "U-form", "U-shape": "U-form",
"Unlink accounts": "Unlink accounts", "Unlink accounts": "Unlink accounts",
"Upgrade expires {upgradeExpires, date, short}": "Upgrade expires {upgradeExpires, date, short}", "Upgrade expires {upgradeExpires, date, short}": "Upgrade expires {upgradeExpires, date, short}",
"Upgrade your stay": "Opgrader dit ophold",
"Use Bonus Cheque": "Brug Bonus Cheque", "Use Bonus Cheque": "Brug Bonus Cheque",
"Use bonus cheque": "Brug Bonus Cheque", "Use bonus cheque": "Brug Bonus Cheque",
"Use code/voucher": "Brug kode/voucher", "Use code/voucher": "Brug kode/voucher",
@@ -593,6 +594,7 @@
"VAT {vat}%": "Moms {vat}%", "VAT {vat}%": "Moms {vat}%",
"Valid through {expirationDate}": "Gyldig til og med {expirationDate}", "Valid through {expirationDate}": "Gyldig til og med {expirationDate}",
"Verification code": "Verification code", "Verification code": "Verification code",
"View all": "Vis alle",
"View all hotels in {country}": "Se alle hoteller i {country}", "View all hotels in {country}": "Se alle hoteller i {country}",
"View and buy add-ons": "View and buy add-ons", "View and buy add-ons": "View and buy add-ons",
"View as list": "Vis som liste", "View as list": "Vis som liste",

View File

@@ -584,6 +584,7 @@
"U-shape": "U-shape", "U-shape": "U-shape",
"Unlink accounts": "Unlink accounts", "Unlink accounts": "Unlink accounts",
"Upgrade expires {upgradeExpires, date, short}": "Upgrade expires {upgradeExpires, date, short}", "Upgrade expires {upgradeExpires, date, short}": "Upgrade expires {upgradeExpires, date, short}",
"Upgrade your stay": "Werten Sie Ihren Aufenthalt auf",
"Use Bonus Cheque": "Bonusscheck nutzen", "Use Bonus Cheque": "Bonusscheck nutzen",
"Use bonus cheque": "Bonusscheck nutzen", "Use bonus cheque": "Bonusscheck nutzen",
"Use code/voucher": "Code/Gutschein nutzen", "Use code/voucher": "Code/Gutschein nutzen",
@@ -593,6 +594,7 @@
"VAT {vat}%": "MwSt. {vat}%", "VAT {vat}%": "MwSt. {vat}%",
"Valid through {expirationDate}": "Gültig bis {expirationDate}", "Valid through {expirationDate}": "Gültig bis {expirationDate}",
"Verification code": "Verification code", "Verification code": "Verification code",
"View all": "Alle anzeigen",
"View all hotels in {country}": "Alle Hotels in {country} anzeigen", "View all hotels in {country}": "Alle Hotels in {country} anzeigen",
"View and buy add-ons": "View and buy add-ons", "View and buy add-ons": "View and buy add-ons",
"View as list": "Als Liste anzeigen", "View as list": "Als Liste anzeigen",

View File

@@ -589,6 +589,7 @@
"U-shape": "U-shape", "U-shape": "U-shape",
"Unlink accounts": "Unlink accounts", "Unlink accounts": "Unlink accounts",
"Upgrade expires {upgradeExpires, date, short}": "Upgrade expires {upgradeExpires, date, short}", "Upgrade expires {upgradeExpires, date, short}": "Upgrade expires {upgradeExpires, date, short}",
"Upgrade your stay": "Upgrade your stay",
"Use Bonus Cheque": "Use Bonus Cheque", "Use Bonus Cheque": "Use Bonus Cheque",
"Use bonus cheque": "Use bonus cheque", "Use bonus cheque": "Use bonus cheque",
"Use code/voucher": "Use code/voucher", "Use code/voucher": "Use code/voucher",
@@ -598,6 +599,7 @@
"VAT {vat}%": "VAT {vat}%", "VAT {vat}%": "VAT {vat}%",
"Valid through {expirationDate}": "Valid through {expirationDate}", "Valid through {expirationDate}": "Valid through {expirationDate}",
"Verification code": "Verification code", "Verification code": "Verification code",
"View all": "View all",
"View all hotels in {country}": "View all hotels in {country}", "View all hotels in {country}": "View all hotels in {country}",
"View and buy add-ons": "View and buy add-ons", "View and buy add-ons": "View and buy add-ons",
"View as list": "View as list", "View as list": "View as list",

View File

@@ -584,6 +584,7 @@
"U-shape": "U-muoto", "U-shape": "U-muoto",
"Unlink accounts": "Unlink accounts", "Unlink accounts": "Unlink accounts",
"Upgrade expires {upgradeExpires, date, short}": "Upgrade expires {upgradeExpires, date, short}", "Upgrade expires {upgradeExpires, date, short}": "Upgrade expires {upgradeExpires, date, short}",
"Upgrade your stay": "Päivitä oleskelusi",
"Use Bonus Cheque": "Käytä bonussekkiä", "Use Bonus Cheque": "Käytä bonussekkiä",
"Use bonus cheque": "Käytä bonussekkiä", "Use bonus cheque": "Käytä bonussekkiä",
"Use code/voucher": "Käytä koodia/voucheria", "Use code/voucher": "Käytä koodia/voucheria",
@@ -593,6 +594,7 @@
"VAT {vat}%": "ALV {vat}%", "VAT {vat}%": "ALV {vat}%",
"Valid through {expirationDate}": "Voimassa {expirationDate} asti", "Valid through {expirationDate}": "Voimassa {expirationDate} asti",
"Verification code": "Verification code", "Verification code": "Verification code",
"View all": "Näytä kaikki",
"View all hotels in {country}": "Näytä kaikki hotellit maassa {country}", "View all hotels in {country}": "Näytä kaikki hotellit maassa {country}",
"View and buy add-ons": "View and buy add-ons", "View and buy add-ons": "View and buy add-ons",
"View as list": "Näytä listana", "View as list": "Näytä listana",

View File

@@ -582,6 +582,7 @@
"U-shape": "U-form", "U-shape": "U-form",
"Unlink accounts": "Unlink accounts", "Unlink accounts": "Unlink accounts",
"Upgrade expires {upgradeExpires, date, short}": "Upgrade expires {upgradeExpires, date, short}", "Upgrade expires {upgradeExpires, date, short}": "Upgrade expires {upgradeExpires, date, short}",
"Upgrade your stay": "Oppgrader oppholdet ditt",
"Use Bonus Cheque": "Bruk bonussjekk", "Use Bonus Cheque": "Bruk bonussjekk",
"Use bonus cheque": "Bruk bonussjekk", "Use bonus cheque": "Bruk bonussjekk",
"Use code/voucher": "Bruk kode/voucher", "Use code/voucher": "Bruk kode/voucher",
@@ -591,6 +592,7 @@
"VAT {vat}%": "mva {vat}%", "VAT {vat}%": "mva {vat}%",
"Valid through {expirationDate}": "Gyldig til og med {expirationDate}", "Valid through {expirationDate}": "Gyldig til og med {expirationDate}",
"Verification code": "Verification code", "Verification code": "Verification code",
"View all": "Vis alle",
"View all hotels in {country}": "Se alle hotellene i {country}", "View all hotels in {country}": "Se alle hotellene i {country}",
"View and buy add-ons": "View and buy add-ons", "View and buy add-ons": "View and buy add-ons",
"View as list": "Vis som liste", "View as list": "Vis som liste",

View File

@@ -582,6 +582,7 @@
"U-shape": "U-form", "U-shape": "U-form",
"Unlink accounts": "Unlink accounts", "Unlink accounts": "Unlink accounts",
"Upgrade expires {upgradeExpires, date, short}": "Upgrade expires {upgradeExpires, date, short}", "Upgrade expires {upgradeExpires, date, short}": "Upgrade expires {upgradeExpires, date, short}",
"Upgrade your stay": "Uppgradera din vistelse",
"Use Bonus Cheque": "Använd bonuscheck", "Use Bonus Cheque": "Använd bonuscheck",
"Use bonus cheque": "Använd bonuscheck", "Use bonus cheque": "Använd bonuscheck",
"Use code/voucher": "Använd kod/voucher", "Use code/voucher": "Använd kod/voucher",
@@ -591,6 +592,7 @@
"VAT {vat}%": "Moms {vat}%", "VAT {vat}%": "Moms {vat}%",
"Valid through {expirationDate}": "Gäller till och med {expirationDate}", "Valid through {expirationDate}": "Gäller till och med {expirationDate}",
"Verification code": "Verification code", "Verification code": "Verification code",
"View all": "Visa alla",
"View all hotels in {country}": "Visa alla hotell i {country}", "View all hotels in {country}": "Visa alla hotell i {country}",
"View and buy add-ons": "View and buy add-ons", "View and buy add-ons": "View and buy add-ons",
"View as list": "Visa som lista", "View as list": "Visa som lista",

View File

@@ -4,6 +4,7 @@ import { serverClient } from "../server"
import type { Country } from "@/types/enums/country" import type { Country } from "@/types/enums/country"
import type { import type {
AncillaryPackagesInput,
BreackfastPackagesInput, BreackfastPackagesInput,
PackagesInput, PackagesInput,
} from "@/types/requests/packages" } from "@/types/requests/packages"
@@ -136,6 +137,12 @@ export const getBreakfastPackages = cache(
} }
) )
export const getAncillaryPackages = cache(
async function getMemoizedAncillaryPackages(input: AncillaryPackagesInput) {
return serverClient().hotel.packages.ancillary(input)
}
)
export const getPackages = cache(async function getMemoizedPackages( export const getPackages = cache(async function getMemoizedPackages(
input: PackagesInput input: PackagesInput
) { ) {

View File

@@ -167,9 +167,20 @@ export const bookingConfirmationSchema = z
}), }),
id: z.string(), id: z.string(),
type: z.literal("booking"), type: z.literal("booking"),
links: z.object({
addAncillary: z
.object({
href: z.string(),
meta: z.object({
method: z.string(),
}),
})
.nullable(),
}),
}), }),
}) })
.transform(({ data }) => ({ .transform(({ data }) => ({
...data.attributes, ...data.attributes,
extraBedTypes: data.attributes.childBedPreferences, extraBedTypes: data.attributes.childBedPreferences,
showAncillaries: !!data.links.addAncillary,
})) }))

View File

@@ -93,6 +93,15 @@ export const breakfastPackageInputSchema = z.object({
.pipe(z.coerce.date()), .pipe(z.coerce.date()),
}) })
export const ancillaryPackageInputSchema = z.object({
fromDate: z
.string()
.min(1, { message: "fromDate is required" })
.pipe(z.coerce.date()),
hotelId: z.string().min(1, { message: "hotelId is required" }),
toDate: z.string().pipe(z.coerce.date()).optional(),
})
export const roomPackagesInputSchema = z.object({ export const roomPackagesInputSchema = z.object({
hotelId: z.string(), hotelId: z.string(),
startDate: z.string(), startDate: z.string(),

View File

@@ -12,6 +12,11 @@ export const metrics = {
fail: meter.createCounter("trpc.package.breakfast-fail"), fail: meter.createCounter("trpc.package.breakfast-fail"),
success: meter.createCounter("trpc.package.breakfast-success"), success: meter.createCounter("trpc.package.breakfast-success"),
}, },
ancillaryPackage: {
counter: meter.createCounter("trpc.package.ancillary"),
fail: meter.createCounter("trpc.package.ancillary-fail"),
success: meter.createCounter("trpc.package.ancillary-success"),
},
hotel: { hotel: {
counter: meter.createCounter("trpc.hotel.get"), counter: meter.createCounter("trpc.hotel.get"),
fail: meter.createCounter("trpc.hotel.get-fail"), fail: meter.createCounter("trpc.hotel.get-fail"),

View File

@@ -12,7 +12,11 @@ import {
} from "./schemas/hotel" } from "./schemas/hotel"
import { locationCitySchema } from "./schemas/location/city" import { locationCitySchema } from "./schemas/location/city"
import { locationHotelSchema } from "./schemas/location/hotel" import { locationHotelSchema } from "./schemas/location/hotel"
import { breakfastPackageSchema, packageSchema } from "./schemas/packages" import {
ancillaryPackageSchema,
breakfastPackageSchema,
packageSchema,
} from "./schemas/packages"
import { rateSchema } from "./schemas/rate" import { rateSchema } from "./schemas/rate"
import { relationshipsSchema } from "./schemas/relationships" import { relationshipsSchema } from "./schemas/relationships"
import { roomConfigurationSchema } from "./schemas/roomAvailability/configuration" import { roomConfigurationSchema } from "./schemas/roomAvailability/configuration"
@@ -343,6 +347,35 @@ export const breakfastPackagesSchema = z
data.attributes.packages.filter((pkg) => pkg.code?.match(/^(BRF\d+)$/gm)) data.attributes.packages.filter((pkg) => pkg.code?.match(/^(BRF\d+)$/gm))
) )
export const ancillaryPackagesSchema = z
.object({
data: z.object({
attributes: z.object({
ancillaries: z.array(ancillaryPackageSchema),
}),
}),
})
.transform(({ data }) =>
data.attributes.ancillaries
.map((ancillary) => ({
categoryName: ancillary.categoryName,
ancillaryContent: ancillary.ancillaryContent
.filter((item) => item.status === "Available")
.map((item) => ({
id: item.id,
title: item.title,
description: item.descriptions.html,
imageUrl: item.images[0].imageSizes.small,
price: {
total: parseInt(item.variants.ancillary.price.totalPrice),
currency: item.variants.ancillary.price.currency,
},
points: item.variants.ancillaryLoyalty?.points,
})),
}))
.filter((ancillary) => ancillary.ancillaryContent.length > 0)
)
export const packagesSchema = z export const packagesSchema = z
.object({ .object({
data: z data: z

View File

@@ -21,6 +21,7 @@ import { getVerifiedUser, parsedUser } from "../user/query"
import { additionalDataSchema } from "./schemas/additionalData" import { additionalDataSchema } from "./schemas/additionalData"
import { meetingRoomsSchema } from "./schemas/meetingRoom" import { meetingRoomsSchema } from "./schemas/meetingRoom"
import { import {
ancillaryPackageInputSchema,
breakfastPackageInputSchema, breakfastPackageInputSchema,
cityCoordinatesInputSchema, cityCoordinatesInputSchema,
getAdditionalDataInputSchema, getAdditionalDataInputSchema,
@@ -39,6 +40,7 @@ import {
} from "./input" } from "./input"
import { metrics } from "./metrics" import { metrics } from "./metrics"
import { import {
ancillaryPackagesSchema,
breakfastPackagesSchema, breakfastPackagesSchema,
getNearbyHotelIdsSchema, getNearbyHotelIdsSchema,
hotelsAvailabilitySchema, hotelsAvailabilitySchema,
@@ -1622,7 +1624,7 @@ export const hotelQueryRouter = router({
}), }),
}) })
console.error( console.error(
"api.hotels.hotelsAvailability error", "api.package.breakfast error",
JSON.stringify({ JSON.stringify({
query: metricsData, query: metricsData,
error: { error: {
@@ -1683,5 +1685,90 @@ export const hotelQueryRouter = router({
(pkg) => pkg.code !== BreakfastPackageEnum.FREE_MEMBER_BREAKFAST (pkg) => pkg.code !== BreakfastPackageEnum.FREE_MEMBER_BREAKFAST
) )
}), }),
ancillary: safeProtectedServiceProcedure
.input(ancillaryPackageInputSchema)
.query(async function ({ ctx, input }) {
const { lang } = ctx
const apiLang = toApiLang(lang)
const params = {
EndDate: dt(input.toDate).format("YYYY-MM-DD"),
StartDate: dt(input.fromDate).format("YYYY-MM-DD"),
language: apiLang,
}
const metricsData = { ...params, hotelId: input.hotelId }
metrics.ancillaryPackage.counter.add(1, metricsData)
console.info(
"api.package.ancillary start",
JSON.stringify({ query: metricsData })
)
const apiResponse = await api.get(
api.endpoints.v1.Package.Ancillary.hotel(input.hotelId),
{
cache: undefined,
headers: {
Authorization: `Bearer ${ctx.serviceToken}`,
},
next: {
revalidate: 60,
},
},
params
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
metrics.ancillaryPackage.fail.add(1, {
...metricsData,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.package.ancillary start error",
JSON.stringify({
query: metricsData,
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
return null
}
const apiJson = await apiResponse.json()
const ancillaryPackages = ancillaryPackagesSchema.safeParse(apiJson)
if (!ancillaryPackages.success) {
metrics.ancillaryPackage.fail.add(1, {
...metricsData,
error_type: "validation_error",
error: JSON.stringify(ancillaryPackages.error),
})
console.error(
"api.package.ancillary validation error",
JSON.stringify({
query: metricsData,
error: ancillaryPackages.error,
})
)
return null
}
metrics.ancillaryPackage.success.add(1, metricsData)
console.info(
"api.package.ancillary success",
JSON.stringify({
query: metricsData,
})
)
return ancillaryPackages.data
}),
}), }),
}) })

View File

@@ -1,5 +1,7 @@
import { z } from "zod" import { z } from "zod"
import { imageSizesSchema } from "./image"
import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter"
import { PackageTypeEnum } from "@/types/enums/packages" import { PackageTypeEnum } from "@/types/enums/packages"
@@ -23,6 +25,18 @@ const inventorySchema = z.object({
available: z.number(), available: z.number(),
}) })
export const ancillaryContentSchema = z.object({
status: z.string(),
id: z.string(),
variants: z.object({
ancillary: z.object({ price: packagePriceSchema }),
ancillaryLoyalty: z.object({ points: z.number() }).optional(),
}),
title: z.string(),
descriptions: z.object({ html: z.string() }),
images: z.array(z.object({ imageSizes: imageSizesSchema })),
})
export const packageSchema = z.object({ export const packageSchema = z.object({
code: z.nativeEnum(RoomPackageCodeEnum), code: z.nativeEnum(RoomPackageCodeEnum),
description: z.string(), description: z.string(),
@@ -39,3 +53,8 @@ export const breakfastPackageSchema = z.object({
requestedPrice: packagePriceSchema, requestedPrice: packagePriceSchema,
packageType: z.literal(PackageTypeEnum.BreakfastAdult), packageType: z.literal(PackageTypeEnum.BreakfastAdult),
}) })
export const ancillaryPackageSchema = z.object({
categoryName: z.string(),
ancillaryContent: z.array(ancillaryContentSchema),
})

View File

@@ -0,0 +1,19 @@
import type { z } from "zod"
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
import type { ancillaryPackagesSchema } from "@/server/routers/hotels/output"
export type Ancillaries = z.output<typeof ancillaryPackagesSchema>
export type Ancillary = Ancillaries[number]
export interface AncillariesProps {
ancillaries: Ancillaries | null
}
export interface AncillaryProps {
ancillary: Ancillary["ancillaryContent"][number]
}
export interface MyStayProps extends BookingConfirmation {
ancillaries: Ancillaries | null
}

View File

@@ -1,6 +1,7 @@
import type { z } from "zod" import type { z } from "zod"
import type { import type {
ancillaryPackageInputSchema,
breakfastPackageInputSchema, breakfastPackageInputSchema,
roomPackagesInputSchema, roomPackagesInputSchema,
} from "@/server/routers/hotels/input" } from "@/server/routers/hotels/input"
@@ -9,6 +10,9 @@ import type { packagesSchema } from "@/server/routers/hotels/output"
export interface BreackfastPackagesInput export interface BreackfastPackagesInput
extends z.input<typeof breakfastPackageInputSchema> {} extends z.input<typeof breakfastPackageInputSchema> {}
export interface AncillaryPackagesInput
extends z.input<typeof ancillaryPackageInputSchema> {}
export interface PackagesInput export interface PackagesInput
extends z.input<typeof roomPackagesInputSchema> {} extends z.input<typeof roomPackagesInputSchema> {}