From 224a41ec7408611952d0b6652f5abd57b589bfd2 Mon Sep 17 00:00:00 2001 From: Bianca Widstam Date: Fri, 14 Feb 2025 10:37:17 +0000 Subject: [PATCH] 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 --- .../MyStay/Ancillaries/ancillaries.module.css | 86 ++++++++++++++ .../MyStay/MyStay/Ancillaries/index.tsx | 112 +++++++++++++++++- .../HotelReservation/MyStay/MyStay/index.tsx | 16 ++- .../AncillaryCard/ancillaryCard.module.css | 3 +- i18n/dictionaries/da.json | 2 + i18n/dictionaries/de.json | 2 + i18n/dictionaries/en.json | 2 + i18n/dictionaries/fi.json | 2 + i18n/dictionaries/no.json | 2 + i18n/dictionaries/sv.json | 2 + lib/trpc/memoizedRequests/index.ts | 7 ++ server/routers/booking/output.ts | 11 ++ server/routers/hotels/input.ts | 9 ++ server/routers/hotels/metrics.ts | 5 + server/routers/hotels/output.ts | 35 +++++- server/routers/hotels/query.ts | 89 +++++++++++++- server/routers/hotels/schemas/packages.ts | 19 +++ .../components/myPages/myStay/ancillaries.ts | 19 +++ types/requests/packages.ts | 4 + 19 files changed, 420 insertions(+), 7 deletions(-) create mode 100644 components/HotelReservation/MyStay/MyStay/Ancillaries/ancillaries.module.css create mode 100644 types/components/myPages/myStay/ancillaries.ts diff --git a/components/HotelReservation/MyStay/MyStay/Ancillaries/ancillaries.module.css b/components/HotelReservation/MyStay/MyStay/Ancillaries/ancillaries.module.css new file mode 100644 index 000000000..341597b4e --- /dev/null +++ b/components/HotelReservation/MyStay/MyStay/Ancillaries/ancillaries.module.css @@ -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; + } +} diff --git a/components/HotelReservation/MyStay/MyStay/Ancillaries/index.tsx b/components/HotelReservation/MyStay/MyStay/Ancillaries/index.tsx index 2f9c83bd5..7bca0c836 100644 --- a/components/HotelReservation/MyStay/MyStay/Ancillaries/index.tsx +++ b/components/HotelReservation/MyStay/MyStay/Ancillaries/index.tsx @@ -1,3 +1,111 @@ -export function Ancillaries() { - return
Add Ancillaries
+"use client" +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( + () => { + 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 ( +
+
+ {intl.formatMessage({ id: "Upgrade your stay" })} +
+ + {intl.formatMessage({ id: "View all" })} + + + } + title={intl.formatMessage({ id: "Upgrade your stay" })} + > +
+
+ {ancillaries.map((category) => ( + + ))} +
+ +
+ {ancillaries + .find( + (category) => category.categoryName === selectedCategory + ) + ?.ancillaryContent.map(({ description, ...ancillary }) => ( + + ))} +
+
+
+
+
+ +
+ {allAncillaries.slice(0, 4).map(({ description, ...ancillary }) => ( + + ))} +
+ +
+ {allAncillaries.map(({ description, ...ancillary }) => ( + + ))} +
+
+ ) } diff --git a/components/HotelReservation/MyStay/MyStay/index.tsx b/components/HotelReservation/MyStay/MyStay/index.tsx index 8471784de..47f0bb70d 100644 --- a/components/HotelReservation/MyStay/MyStay/index.tsx +++ b/components/HotelReservation/MyStay/MyStay/index.tsx @@ -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" @@ -14,13 +18,21 @@ import styles from "./myStay.module.css" export async function MyStay({ reservationId }: { reservationId: string }) { 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 (
{room && } - + {booking.showAncillaries && ( + + )} diff --git a/components/TempDesignSystem/AncillaryCard/ancillaryCard.module.css b/components/TempDesignSystem/AncillaryCard/ancillaryCard.module.css index d2aa43334..9a4374145 100644 --- a/components/TempDesignSystem/AncillaryCard/ancillaryCard.module.css +++ b/components/TempDesignSystem/AncillaryCard/ancillaryCard.module.css @@ -1,7 +1,8 @@ .ancillaryCard { display: flex; flex-direction: column; - max-width: 280px; + min-width: 251px; + max-width: 286px; } .imageContainer { diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index 30ab1b878..c80aa0eb9 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -584,6 +584,7 @@ "U-shape": "U-form", "Unlink accounts": "Unlink accounts", "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 code/voucher": "Brug kode/voucher", @@ -593,6 +594,7 @@ "VAT {vat}%": "Moms {vat}%", "Valid through {expirationDate}": "Gyldig til og med {expirationDate}", "Verification code": "Verification code", + "View all": "Vis alle", "View all hotels in {country}": "Se alle hoteller i {country}", "View and buy add-ons": "View and buy add-ons", "View as list": "Vis som liste", diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index 29deb12c3..684bba2bd 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -584,6 +584,7 @@ "U-shape": "U-shape", "Unlink accounts": "Unlink accounts", "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 code/voucher": "Code/Gutschein nutzen", @@ -593,6 +594,7 @@ "VAT {vat}%": "MwSt. {vat}%", "Valid through {expirationDate}": "Gültig bis {expirationDate}", "Verification code": "Verification code", + "View all": "Alle anzeigen", "View all hotels in {country}": "Alle Hotels in {country} anzeigen", "View and buy add-ons": "View and buy add-ons", "View as list": "Als Liste anzeigen", diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index 5ee79d127..7aa68c6a9 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -589,6 +589,7 @@ "U-shape": "U-shape", "Unlink accounts": "Unlink accounts", "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 code/voucher": "Use code/voucher", @@ -598,6 +599,7 @@ "VAT {vat}%": "VAT {vat}%", "Valid through {expirationDate}": "Valid through {expirationDate}", "Verification code": "Verification code", + "View all": "View all", "View all hotels in {country}": "View all hotels in {country}", "View and buy add-ons": "View and buy add-ons", "View as list": "View as list", diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index 99a02c2da..60d39ce42 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -584,6 +584,7 @@ "U-shape": "U-muoto", "Unlink accounts": "Unlink accounts", "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 code/voucher": "Käytä koodia/voucheria", @@ -593,6 +594,7 @@ "VAT {vat}%": "ALV {vat}%", "Valid through {expirationDate}": "Voimassa {expirationDate} asti", "Verification code": "Verification code", + "View all": "Näytä kaikki", "View all hotels in {country}": "Näytä kaikki hotellit maassa {country}", "View and buy add-ons": "View and buy add-ons", "View as list": "Näytä listana", diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index 71d702ab8..2727ceaa0 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -582,6 +582,7 @@ "U-shape": "U-form", "Unlink accounts": "Unlink accounts", "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 code/voucher": "Bruk kode/voucher", @@ -591,6 +592,7 @@ "VAT {vat}%": "mva {vat}%", "Valid through {expirationDate}": "Gyldig til og med {expirationDate}", "Verification code": "Verification code", + "View all": "Vis alle", "View all hotels in {country}": "Se alle hotellene i {country}", "View and buy add-ons": "View and buy add-ons", "View as list": "Vis som liste", diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index d09333710..7446a9c73 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -582,6 +582,7 @@ "U-shape": "U-form", "Unlink accounts": "Unlink accounts", "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 code/voucher": "Använd kod/voucher", @@ -591,6 +592,7 @@ "VAT {vat}%": "Moms {vat}%", "Valid through {expirationDate}": "Gäller till och med {expirationDate}", "Verification code": "Verification code", + "View all": "Visa alla", "View all hotels in {country}": "Visa alla hotell i {country}", "View and buy add-ons": "View and buy add-ons", "View as list": "Visa som lista", diff --git a/lib/trpc/memoizedRequests/index.ts b/lib/trpc/memoizedRequests/index.ts index 1f4fbd466..3d4f51e22 100644 --- a/lib/trpc/memoizedRequests/index.ts +++ b/lib/trpc/memoizedRequests/index.ts @@ -4,6 +4,7 @@ import { serverClient } from "../server" import type { Country } from "@/types/enums/country" import type { + AncillaryPackagesInput, BreackfastPackagesInput, PackagesInput, } 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( input: PackagesInput ) { diff --git a/server/routers/booking/output.ts b/server/routers/booking/output.ts index b5a23fb86..9223ed7c6 100644 --- a/server/routers/booking/output.ts +++ b/server/routers/booking/output.ts @@ -167,9 +167,20 @@ export const bookingConfirmationSchema = z }), id: z.string(), type: z.literal("booking"), + links: z.object({ + addAncillary: z + .object({ + href: z.string(), + meta: z.object({ + method: z.string(), + }), + }) + .nullable(), + }), }), }) .transform(({ data }) => ({ ...data.attributes, extraBedTypes: data.attributes.childBedPreferences, + showAncillaries: !!data.links.addAncillary, })) diff --git a/server/routers/hotels/input.ts b/server/routers/hotels/input.ts index 3a12a77a4..08bbd5cbf 100644 --- a/server/routers/hotels/input.ts +++ b/server/routers/hotels/input.ts @@ -93,6 +93,15 @@ export const breakfastPackageInputSchema = z.object({ .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({ hotelId: z.string(), startDate: z.string(), diff --git a/server/routers/hotels/metrics.ts b/server/routers/hotels/metrics.ts index dba77efe9..2f8a10e37 100644 --- a/server/routers/hotels/metrics.ts +++ b/server/routers/hotels/metrics.ts @@ -12,6 +12,11 @@ export const metrics = { fail: meter.createCounter("trpc.package.breakfast-fail"), 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: { counter: meter.createCounter("trpc.hotel.get"), fail: meter.createCounter("trpc.hotel.get-fail"), diff --git a/server/routers/hotels/output.ts b/server/routers/hotels/output.ts index 619596e49..d103abba3 100644 --- a/server/routers/hotels/output.ts +++ b/server/routers/hotels/output.ts @@ -12,7 +12,11 @@ import { } from "./schemas/hotel" import { locationCitySchema } from "./schemas/location/city" 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 { relationshipsSchema } from "./schemas/relationships" 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)) ) +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 .object({ data: z diff --git a/server/routers/hotels/query.ts b/server/routers/hotels/query.ts index 4ec9913a3..fe48f7c1b 100644 --- a/server/routers/hotels/query.ts +++ b/server/routers/hotels/query.ts @@ -21,6 +21,7 @@ import { getVerifiedUser, parsedUser } from "../user/query" import { additionalDataSchema } from "./schemas/additionalData" import { meetingRoomsSchema } from "./schemas/meetingRoom" import { + ancillaryPackageInputSchema, breakfastPackageInputSchema, cityCoordinatesInputSchema, getAdditionalDataInputSchema, @@ -39,6 +40,7 @@ import { } from "./input" import { metrics } from "./metrics" import { + ancillaryPackagesSchema, breakfastPackagesSchema, getNearbyHotelIdsSchema, hotelsAvailabilitySchema, @@ -1622,7 +1624,7 @@ export const hotelQueryRouter = router({ }), }) console.error( - "api.hotels.hotelsAvailability error", + "api.package.breakfast error", JSON.stringify({ query: metricsData, error: { @@ -1683,5 +1685,90 @@ export const hotelQueryRouter = router({ (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 + }), }), }) diff --git a/server/routers/hotels/schemas/packages.ts b/server/routers/hotels/schemas/packages.ts index 0f32f11a5..694263cd0 100644 --- a/server/routers/hotels/schemas/packages.ts +++ b/server/routers/hotels/schemas/packages.ts @@ -1,5 +1,7 @@ import { z } from "zod" +import { imageSizesSchema } from "./image" + import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" import { PackageTypeEnum } from "@/types/enums/packages" @@ -23,6 +25,18 @@ const inventorySchema = z.object({ 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({ code: z.nativeEnum(RoomPackageCodeEnum), description: z.string(), @@ -39,3 +53,8 @@ export const breakfastPackageSchema = z.object({ requestedPrice: packagePriceSchema, packageType: z.literal(PackageTypeEnum.BreakfastAdult), }) + +export const ancillaryPackageSchema = z.object({ + categoryName: z.string(), + ancillaryContent: z.array(ancillaryContentSchema), +}) diff --git a/types/components/myPages/myStay/ancillaries.ts b/types/components/myPages/myStay/ancillaries.ts new file mode 100644 index 000000000..c5b916cb9 --- /dev/null +++ b/types/components/myPages/myStay/ancillaries.ts @@ -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 +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 +} diff --git a/types/requests/packages.ts b/types/requests/packages.ts index f0d501332..ee663f034 100644 --- a/types/requests/packages.ts +++ b/types/requests/packages.ts @@ -1,6 +1,7 @@ import type { z } from "zod" import type { + ancillaryPackageInputSchema, breakfastPackageInputSchema, roomPackagesInputSchema, } from "@/server/routers/hotels/input" @@ -9,6 +10,9 @@ import type { packagesSchema } from "@/server/routers/hotels/output" export interface BreackfastPackagesInput extends z.input {} +export interface AncillaryPackagesInput + extends z.input {} + export interface PackagesInput extends z.input {}