fix: make sure ancillaries also listen to invalidate requests

This commit is contained in:
Simon Emanuelsson
2025-05-14 17:45:05 +02:00
committed by Simon.Emanuelsson
parent 623495a176
commit 0b960200b9
12 changed files with 165 additions and 225 deletions

View File

@@ -204,7 +204,6 @@ export default async function MyStay({
{booking.showAncillaries && ancillaryPackagesPromise && ( {booking.showAncillaries && ancillaryPackagesPromise && (
<Ancillaries <Ancillaries
ancillariesPromise={ancillaryPackagesPromise} ancillariesPromise={ancillaryPackagesPromise}
booking={booking}
packages={breakfastPackages} packages={breakfastPackages}
user={user} user={user}
savedCreditCards={savedCreditCards} savedCreditCards={savedCreditCards}

View File

@@ -207,7 +207,6 @@ export default async function MyStay({
{booking.showAncillaries && ancillaryPackagesPromise && ( {booking.showAncillaries && ancillaryPackagesPromise && (
<Ancillaries <Ancillaries
ancillariesPromise={ancillaryPackagesPromise} ancillariesPromise={ancillaryPackagesPromise}
booking={booking}
packages={breakfastPackages} packages={breakfastPackages}
user={user} user={user}
savedCreditCards={savedCreditCards} savedCreditCards={savedCreditCards}

View File

@@ -163,8 +163,9 @@ export default function BookingWidgetClient({
useEffect(() => { useEffect(() => {
const observer = new ResizeObserver( const observer = new ResizeObserver(
debounce(([entry]) => { debounce(([entry]) => {
if (entry.contentRect.width > 1366) { if (entry.contentRect.width > 768) {
closeMobileSearch() setIsOpen(false)
document.body.style.removeProperty("overflow-y")
} }
}) })
) )

View File

@@ -11,10 +11,8 @@ import { toast } from "@/components/TempDesignSystem/Toasts"
import useLang from "@/hooks/useLang" import useLang from "@/hooks/useLang"
import { trackRemoveAncillary } from "@/utils/tracking/myStay" import { trackRemoveAncillary } from "@/utils/tracking/myStay"
import type { import type { Room } from "@/types/stores/my-stay"
BookingConfirmation, import type { PackageSchema } from "@/types/trpc/routers/booking/confirmation"
PackageSchema,
} from "@/types/trpc/routers/booking/confirmation"
export default function RemoveButton({ export default function RemoveButton({
refId, refId,
@@ -26,7 +24,7 @@ export default function RemoveButton({
refId: string refId: string
codes: string[] codes: string[]
title?: string title?: string
booking: BookingConfirmation["booking"] booking: Room
ancillary: PackageSchema ancillary: PackageSchema
}) { }) {
const lang = useLang() const lang = useLang()

View File

@@ -16,10 +16,8 @@ import styles from "./addedAncillaries.module.css"
import type { AddedAncillariesProps } from "@/types/components/myPages/myStay/ancillaries" import type { AddedAncillariesProps } from "@/types/components/myPages/myStay/ancillaries"
import { BreakfastPackageEnum } from "@/types/enums/breakfast" import { BreakfastPackageEnum } from "@/types/enums/breakfast"
import type { import type { Room } from "@/types/stores/my-stay"
BookingConfirmation, import type { PackageSchema } from "@/types/trpc/routers/booking/confirmation"
PackageSchema,
} from "@/types/trpc/routers/booking/confirmation"
export function AddedAncillaries({ export function AddedAncillaries({
ancillaries, ancillaries,
@@ -28,7 +26,7 @@ export function AddedAncillaries({
const intl = useIntl() const intl = useIntl()
const addedBreakfastPackages = getBreakfastPackagesFromAncillaryFlow( const addedBreakfastPackages = getBreakfastPackagesFromAncillaryFlow(
booking.packages booking.originalPackages
) )
const addedAncillaries = getAddedAncillaries(booking, addedBreakfastPackages) const addedAncillaries = getAddedAncillaries(booking, addedBreakfastPackages)
@@ -226,7 +224,7 @@ export function AddedAncillaries({
* not in the booking flow. * not in the booking flow.
*/ */
function getAddedAncillaries( function getAddedAncillaries(
booking: BookingConfirmation["booking"], booking: Room,
addedBreakfastPackages: PackageSchema[] | undefined addedBreakfastPackages: PackageSchema[] | undefined
) { ) {
if (!addedBreakfastPackages?.length) { if (!addedBreakfastPackages?.length) {

View File

@@ -1,7 +1,9 @@
"use client" "use client"
import { use, useMemo } from "react" import { use } from "react"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { useMyStayStore } from "@/stores/my-stay"
import { Carousel } from "@/components/Carousel" import { Carousel } from "@/components/Carousel"
import Title from "@/components/TempDesignSystem/Text/Title" import Title from "@/components/TempDesignSystem/Text/Title"
import { AddAncillaryProvider } from "@/providers/AddAncillaryProvider" import { AddAncillaryProvider } from "@/providers/AddAncillaryProvider"
@@ -10,82 +12,39 @@ import AddAncillaryFlowModal from "./AddAncillaryFlow/AddAncillaryFlowModal"
import AncillaryFlowModalWrapper from "./AddAncillaryFlow/AncillaryFlowModalWrapper" import AncillaryFlowModalWrapper from "./AddAncillaryFlow/AncillaryFlowModalWrapper"
import WrappedAncillaryCard from "./AddAncillaryFlow/WrappedAncillaryCard" import WrappedAncillaryCard from "./AddAncillaryFlow/WrappedAncillaryCard"
import { AddedAncillaries } from "./AddedAncillaries" import { AddedAncillaries } from "./AddedAncillaries"
import { generateUniqueAncillaries, mapAncillaries } from "./utils"
import ViewAllAncillaries from "./ViewAllAncillaries" import ViewAllAncillaries from "./ViewAllAncillaries"
import styles from "./ancillaries.module.css" import styles from "./ancillaries.module.css"
import type { import type {
Ancillaries,
AncillariesProps, AncillariesProps,
Ancillary,
SelectedAncillary, SelectedAncillary,
} from "@/types/components/myPages/myStay/ancillaries" } from "@/types/components/myPages/myStay/ancillaries"
import { BreakfastPackageEnum } from "@/types/enums/breakfast" import { BreakfastPackageEnum } from "@/types/enums/breakfast"
import type { User } from "@/types/user"
function filterPoints(ancillaries: Ancillaries, user: User | null) {
return ancillaries.map((ancillary) => {
return {
...ancillary,
ancillaryContent: ancillary.ancillaryContent.map(
({ points, ...ancillary }) => ({
...ancillary,
points: user ? points : undefined,
})
),
}
})
}
function generateUniqueAncillaries(
ancillaries: Ancillaries
): Ancillary["ancillaryContent"] {
const uniqueAncillaries = new Map(
ancillaries.flatMap((a) => {
return a.ancillaryContent.map((ancillary) => [ancillary.id, ancillary])
})
)
return [...uniqueAncillaries.values()]
}
/**
* Adds the breakfast package to the ancillaries
*
* Returns the ancillaries array with the breakfast package added to the
* specified category. If the category doesn't exist it's created.
*/
function addBreakfastPackage(
ancillaries: Ancillaries,
breakfast: SelectedAncillary | undefined,
categoryName: string
): Ancillaries {
if (!breakfast) return ancillaries
const category = ancillaries.find((a) => a.categoryName === categoryName)
if (category) {
const newCategory = {
...category,
ancillaryContent: [breakfast, ...category.ancillaryContent],
}
return ancillaries.map((ancillary) =>
ancillary.categoryName === categoryName ? newCategory : ancillary
)
}
return [{ categoryName, ancillaryContent: [breakfast] }, ...ancillaries]
}
export function Ancillaries({ export function Ancillaries({
ancillariesPromise, ancillariesPromise,
booking,
packages, packages,
user,
savedCreditCards, savedCreditCards,
user,
}: AncillariesProps) { }: AncillariesProps) {
const intl = useIntl() const intl = useIntl()
const ancillaries = use(ancillariesPromise) const ancillaries = use(ancillariesPromise)
const bookedRoom = useMyStayStore((state) => state.bookedRoom)
if (!bookedRoom || bookedRoom.isCancelled || !bookedRoom.showAncillaries) {
return null
}
const alreadyHasBreakfast =
bookedRoom.rateDefinition.breakfastIncluded || bookedRoom.breakfast
const breakfastPackageAdults = alreadyHasBreakfast
? undefined
: packages?.find(
(p) => p.code === BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST
)
/** /**
* A constructed ancillary for breakfast * A constructed ancillary for breakfast
@@ -94,70 +53,31 @@ export function Ancillaries({
* ancillary in the system. This makes it play nicely with the add ancillary * ancillary in the system. This makes it play nicely with the add ancillary
* flow. If the user shouldn't be able to add breakfast this will be `undefined`. * flow. If the user shouldn't be able to add breakfast this will be `undefined`.
*/ */
const breakfastAncillary = useMemo(() => { const breakfastAncillary: SelectedAncillary | undefined =
// This is the logic deciding if breakfast should be addable or not breakfastPackageAdults
if ( ? {
booking.rateDefinition.breakfastIncluded || description: intl.formatMessage({
booking.packages.some((p) => defaultMessage: "Buffet",
Object.values(BreakfastPackageEnum).includes( }),
p.code as unknown as BreakfastPackageEnum id: breakfastPackageAdults.code,
) title: intl.formatMessage({
) defaultMessage: "Breakfast",
) { }),
return undefined price: {
} currency: breakfastPackageAdults.localPrice.currency,
total: breakfastPackageAdults.localPrice.totalPrice,
},
imageUrl:
"https://images.scandichotels.com/publishedmedia/inyre69evkpzgtygjnvp/Breakfast_-_Scandic_Sweden_-_Free_to_use.jpg",
requiresDeliveryTime: false,
loyaltyCode: undefined,
points: undefined,
hotelId: Number(bookedRoom.hotelId),
categoryName: "Food",
}
: undefined
const breakfastPackageAdults = packages?.find( const allAncillaries = mapAncillaries(ancillaries, breakfastAncillary, user)
(p) => p.code === BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST
)
const breakfastAncillary: SelectedAncillary | undefined =
breakfastPackageAdults
? {
description: intl.formatMessage({
defaultMessage: "Buffet",
}),
id: breakfastPackageAdults.code,
title: intl.formatMessage({
defaultMessage: "Breakfast",
}),
price: {
currency: breakfastPackageAdults.localPrice.currency,
total: breakfastPackageAdults.localPrice.totalPrice,
},
imageUrl:
"https://images.scandichotels.com/publishedmedia/inyre69evkpzgtygjnvp/Breakfast_-_Scandic_Sweden_-_Free_to_use.jpg",
requiresDeliveryTime: false,
loyaltyCode: undefined,
points: undefined,
hotelId: Number(booking.hotelId),
categoryName: "Food",
}
: undefined
return breakfastAncillary
}, [
booking.packages,
booking.rateDefinition.breakfastIncluded,
intl,
packages,
booking.hotelId,
])
const allAncillaries = useMemo(() => {
const withBreakfastPopular = addBreakfastPackage(
ancillaries ?? [],
breakfastAncillary,
"Popular"
)
const withBreakfastFood = addBreakfastPackage(
withBreakfastPopular,
breakfastAncillary,
"Food"
)
const filtered = filterPoints(withBreakfastFood, user)
return filtered
}, [ancillaries, breakfastAncillary, user])
if (!allAncillaries.length) { if (!allAncillaries.length) {
return null return null
@@ -166,9 +86,9 @@ export function Ancillaries({
const uniqueAncillaries = generateUniqueAncillaries(allAncillaries) const uniqueAncillaries = generateUniqueAncillaries(allAncillaries)
return ( return (
<AddAncillaryProvider booking={booking} ancillaries={allAncillaries}> <AddAncillaryProvider booking={bookedRoom} ancillaries={allAncillaries}>
<div className={styles.container}> <div className={styles.container}>
{uniqueAncillaries.length > 0 && booking.canModifyAncillaries && ( {uniqueAncillaries.length > 0 && bookedRoom.canModifyAncillaries && (
<> <>
<div className={styles.title}> <div className={styles.title}>
<Title as="h5"> <Title as="h5">
@@ -209,12 +129,15 @@ export function Ancillaries({
</> </>
)} )}
<AddedAncillaries booking={booking} ancillaries={uniqueAncillaries} /> <AddedAncillaries
booking={bookedRoom}
ancillaries={uniqueAncillaries}
/>
<AncillaryFlowModalWrapper> <AncillaryFlowModalWrapper>
<AddAncillaryFlowModal <AddAncillaryFlowModal
user={user} user={user}
booking={booking} booking={bookedRoom}
packages={packages} packages={packages}
savedCreditCards={savedCreditCards} savedCreditCards={savedCreditCards}
/> />

View File

@@ -0,0 +1,79 @@
import type {
Ancillaries,
Ancillary,
SelectedAncillary,
} from "@/types/components/myPages/myStay/ancillaries"
import type { User } from "@/types/user"
function filterPoints(ancillaries: Ancillaries, user: User | null) {
return ancillaries.map((ancillary) => {
return {
...ancillary,
ancillaryContent: ancillary.ancillaryContent.map(
({ points, ...ancillary }) => ({
...ancillary,
points: user ? points : undefined,
})
),
}
})
}
export function generateUniqueAncillaries(
ancillaries: Ancillaries
): Ancillary["ancillaryContent"] {
const uniqueAncillaries = new Map(
ancillaries.flatMap((a) => {
return a.ancillaryContent.map((ancillary) => [ancillary.id, ancillary])
})
)
return [...uniqueAncillaries.values()]
}
/**
* Adds the breakfast package to the ancillaries
*
* Returns the ancillaries array with the breakfast package added to the
* specified category. If the category doesn't exist it's created.
*/
function addBreakfastPackage(
ancillaries: Ancillaries,
breakfast: SelectedAncillary | undefined,
categoryName: string
): Ancillaries {
if (!breakfast) return ancillaries
const category = ancillaries.find((a) => a.categoryName === categoryName)
if (category) {
const newCategory = {
...category,
ancillaryContent: [breakfast, ...category.ancillaryContent],
}
return ancillaries.map((ancillary) =>
ancillary.categoryName === categoryName ? newCategory : ancillary
)
}
return [{ categoryName, ancillaryContent: [breakfast] }, ...ancillaries]
}
export function mapAncillaries(
ancillaries: Ancillaries | null,
breakfastAncillary: SelectedAncillary | undefined,
user: User | null
) {
const withBreakfastPopular = addBreakfastPackage(
ancillaries ?? [],
breakfastAncillary,
"Popular"
)
const withBreakfastFood = addBreakfastPackage(
withBreakfastPopular,
breakfastAncillary,
"Food"
)
return filterPoints(withBreakfastFood, user)
}

View File

@@ -64,17 +64,17 @@ export function mapRoomDetails({
) )
// We don't get `requestedPrice` in packages // We don't get `requestedPrice` in packages
const breakfastChildren: Omit<BreakfastPackage, "requestedPrice"> | null = const breakfastChildren: Omit<BreakfastPackage, "requestedPrice"> | null =
(breakfastPackageChildren && !booking.rateDefinition.breakfastIncluded) breakfastPackageChildren && !booking.rateDefinition.breakfastIncluded
? { ? {
code: breakfastPackageChildren.code, code: breakfastPackageChildren.code,
description: breakfastPackageChildren.description, description: breakfastPackageChildren.description,
localPrice: { localPrice: {
currency: breakfastPackageChildren.currency, currency: breakfastPackageChildren.currency,
price: breakfastPackageChildren.unitPrice, price: breakfastPackageChildren.unitPrice,
totalPrice: breakfastPackageChildren.totalPrice, totalPrice: breakfastPackageChildren.totalPrice,
}, },
packageType: PackageTypeEnum.BreakfastChildren, packageType: PackageTypeEnum.BreakfastChildren,
} }
: null : null
const isCancelled = booking.reservationStatus === BookingStatusEnum.Cancelled const isCancelled = booking.reservationStatus === BookingStatusEnum.Cancelled
@@ -135,43 +135,23 @@ export function mapRoomDetails({
})) }))
return { return {
adults: booking.adults, ...booking,
bedType: { bedType: {
description: room?.bedType.mainBed.description ?? "", description: room?.bedType.mainBed.description ?? "",
roomTypeCode: room?.bedType.code ?? "", roomTypeCode: room?.bedType.code ?? "",
}, },
bookingCode: booking.bookingCode,
breakfast, breakfast,
breakfastChildren, breakfastChildren,
canChangeDate: booking.canChangeDate,
cancellationNumber: booking.cancellationNumber,
checkInDate: booking.checkInDate,
checkOutDate: booking.checkOutDate,
cheques: booking.cheques,
childrenAges: booking.childrenAges,
childrenAsString, childrenAsString,
childrenInRoom, childrenInRoom,
confirmationNumber: booking.confirmationNumber,
createDateTime: booking.createDateTime,
currencyCode: booking.currencyCode,
guaranteeInfo: booking.guaranteeInfo,
guest: booking.guest,
hotelId: booking.hotelId,
isCancelable: booking.isCancelable,
isCancelled, isCancelled,
linkedReservations: booking.linkedReservations, originalPackages: booking.packages,
mainRoom: booking.mainRoom,
multiRoom: booking.multiRoom,
packages, packages,
priceType, priceType,
rate, rate,
rateDefinition: booking.rateDefinition,
refId: booking.refId,
reservationStatus: booking.reservationStatus,
room, room,
roomName: room?.name ?? "", roomName: room?.name ?? "",
roomNumber, roomNumber,
roomPoints: booking.roomPoints,
roomPrice: { roomPrice: {
perNight: { perNight: {
local: { local: {
@@ -188,13 +168,6 @@ export function mapRoomDetails({
requested: undefined, requested: undefined,
}, },
}, },
roomTypeCode: booking.roomTypeCode,
terms: booking.rateDefinition.cancellationText, terms: booking.rateDefinition.cancellationText,
totalPoints: booking.totalPoints,
totalPrice: booking.totalPrice,
totalPriceExVat: booking.totalPriceExVat,
vatAmount: booking.vatAmount,
vatPercentage: booking.vatPercentage,
vouchers: booking.vouchers,
} }
} }

View File

@@ -11,7 +11,7 @@ import { AddAncillaryContext } from "@/contexts/AddAncillary"
import type { Ancillaries } from "@/types/components/myPages/myStay/ancillaries" import type { Ancillaries } from "@/types/components/myPages/myStay/ancillaries"
import type { AddAncillaryStore } from "@/types/contexts/add-ancillary" import type { AddAncillaryStore } from "@/types/contexts/add-ancillary"
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation" import type { Room } from "@/types/stores/my-stay"
export function AddAncillaryProvider({ export function AddAncillaryProvider({
ancillaries, ancillaries,
@@ -19,7 +19,7 @@ export function AddAncillaryProvider({
children, children,
}: { }: {
ancillaries: Ancillaries ancillaries: Ancillaries
booking: BookingConfirmation["booking"] booking: Room
children: React.ReactNode children: React.ReactNode
}) { }) {
const storeRef = useRef<AddAncillaryStore>() const storeRef = useRef<AddAncillaryStore>()

View File

@@ -11,7 +11,7 @@ import type {
SelectedAncillary, SelectedAncillary,
} from "@/types/components/myPages/myStay/ancillaries" } from "@/types/components/myPages/myStay/ancillaries"
import { BreakfastPackageEnum } from "@/types/enums/breakfast" import { BreakfastPackageEnum } from "@/types/enums/breakfast"
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation" import type { Room } from "@/types/stores/my-stay"
export enum AncillaryStepEnum { export enum AncillaryStepEnum {
selectAncillary = 0, selectAncillary = 0,
@@ -43,7 +43,7 @@ export type BreakfastData = {
export interface AddAncillaryState { export interface AddAncillaryState {
currentStep: number currentStep: number
steps: Steps steps: Steps
booking: BookingConfirmation["booking"] booking: Room
ancillaries: Ancillaries ancillaries: Ancillaries
categories: Ancillary["categoryName"][] categories: Ancillary["categoryName"][]
selectedCategory: string selectedCategory: string
@@ -75,7 +75,7 @@ function findAncillaryByCategory(
} }
export const createAddAncillaryStore = ( export const createAddAncillaryStore = (
booking: BookingConfirmation["booking"], booking: Room,
ancillaries: Ancillaries ancillaries: Ancillaries
) => { ) => {
const selectedCategory = ancillaries[0].categoryName const selectedCategory = ancillaries[0].categoryName

View File

@@ -1,6 +1,6 @@
import type { z } from "zod" import type { z } from "zod"
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation" import type { Room } from "@/types/stores/my-stay"
import type { CreditCard, User } from "@/types/user" import type { CreditCard, User } from "@/types/user"
import type { import type {
ancillaryPackagesSchema, ancillaryPackagesSchema,
@@ -12,33 +12,29 @@ export type Ancillary = Ancillaries[number]
export type SelectedAncillary = Ancillary["ancillaryContent"][number] export type SelectedAncillary = Ancillary["ancillaryContent"][number]
export type Packages = z.output<typeof packagesSchema> export type Packages = z.output<typeof packagesSchema>
export interface AncillariesProps extends Pick<BookingConfirmation, "booking"> { export interface AncillariesProps {
ancillariesPromise: Promise<Ancillaries | null> ancillariesPromise: Promise<Ancillaries | null>
packages: Packages | null packages: Packages | null
user: User | null
savedCreditCards: CreditCard[] | null savedCreditCards: CreditCard[] | null
user: User | null
} }
export interface AddedAncillariesProps { export interface AddedAncillariesProps {
ancillaries: Ancillary["ancillaryContent"][number][] | null ancillaries: Ancillary["ancillaryContent"][number][] | null
booking: BookingConfirmation["booking"] booking: Room
} }
export interface AncillaryProps { export interface AncillaryProps {
ancillary: Ancillary["ancillaryContent"][number] ancillary: Ancillary["ancillaryContent"][number]
} }
export interface MyStayProps extends BookingConfirmation {
ancillaries: Ancillaries | null
}
export interface AncillaryGridModalProps { export interface AncillaryGridModalProps {
ancillaries: Ancillaries ancillaries: Ancillaries
user: User | null user: User | null
} }
export interface AddAncillaryFlowModalProps export interface AddAncillaryFlowModalProps {
extends Pick<BookingConfirmation, "booking"> { booking: Room
packages: Packages | null packages: Packages | null
user: User | null user: User | null
savedCreditCards: CreditCard[] | null savedCreditCards: CreditCard[] | null

View File

@@ -10,36 +10,9 @@ import type { Hotel, Room as HotelRoom, RoomCategories } from "@/types/hotel"
import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation" import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
import type { CreditCard } from "@/types/user" import type { CreditCard } from "@/types/user"
export type Room = Pick< export type Room = Omit<
BookingConfirmation["booking"], BookingConfirmation["booking"],
| "adults" "packages" | "roomPrice"
| "bookingCode"
| "canChangeDate"
| "cancellationNumber"
| "checkInDate"
| "checkOutDate"
| "cheques"
| "childrenAges"
| "confirmationNumber"
| "createDateTime"
| "currencyCode"
| "guaranteeInfo"
| "guest"
| "hotelId"
| "isCancelable"
| "linkedReservations"
| "multiRoom"
| "rateDefinition"
| "refId"
| "reservationStatus"
| "roomPoints"
| "roomTypeCode"
| "totalPoints"
| "totalPrice"
| "totalPriceExVat"
| "vatAmount"
| "vatPercentage"
| "vouchers"
> & { > & {
bedType: BedTypeSchema bedType: BedTypeSchema
breakfast: Omit<BreakfastPackage, "requestedPrice"> | undefined | false breakfast: Omit<BreakfastPackage, "requestedPrice"> | undefined | false
@@ -48,6 +21,7 @@ export type Room = Pick<
childrenInRoom: Child[] childrenInRoom: Child[]
isCancelled: boolean isCancelled: boolean
mainRoom: boolean mainRoom: boolean
originalPackages: BookingConfirmation["booking"]["packages"]
packages: Packages | null packages: Packages | null
priceType: PriceTypeEnum priceType: PriceTypeEnum
rate: string rate: string