diff --git a/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/AllAncillariesModal/SelectAncillaryStep/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/AllAncillariesModal/SelectAncillaryStep/index.tsx index c1baef079..fa08d76d9 100644 --- a/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/AllAncillariesModal/SelectAncillaryStep/index.tsx +++ b/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/AllAncillariesModal/SelectAncillaryStep/index.tsx @@ -1,6 +1,6 @@ import { useIntl } from "react-intl" -import { Typography } from "@scandic-hotels/design-system/Typography" +import { ChipButton } from "@scandic-hotels/design-system/ChipButton" import { useAddAncillaryStore } from "@/stores/my-stay/add-ancillary-flow" @@ -30,22 +30,20 @@ export default function SelectAncillaryStep({
{categories.map((categoryName) => ( - + {categoryName + ? categoryName + : intl.formatMessage({ + id: "common.other", + defaultMessage: "Other", + })} + ))}
diff --git a/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/AllAncillariesModal/input.tsx b/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/AllAncillariesModal/index.tsx similarity index 100% rename from apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/AllAncillariesModal/input.tsx rename to apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/AllAncillariesModal/index.tsx diff --git a/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/index.tsx b/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/index.tsx index 3938b6f91..8c6508960 100644 --- a/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/index.tsx +++ b/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/index.tsx @@ -11,8 +11,8 @@ import { AddAncillaryProvider } from "@/providers/AddAncillaryProvider" import AddAncillaryFlowModal from "./AddAncillaryFlow/AddAncillaryFlowModal" import AncillaryFlowModalWrapper from "./AddAncillaryFlow/AncillaryFlowModalWrapper" -import AllAncillariesModal from "./AllAncillariesModal/input" import { AddedAncillaries } from "./AddedAncillaries" +import AllAncillariesModal from "./AllAncillariesModal" import WrappedAncillaryCard from "./Card" import styles from "./ancillaries.module.css" @@ -28,60 +28,69 @@ export function Ancillaries({ const intl = useIntl() const bookedRoom = useMyStayStore((state) => state.bookedRoom) - const ancillaries = useAncillaries(ancillariesPromise, packages, user) + const ancillaries = useAncillaries( + ancillariesPromise, + packages, + user, + bookedRoom.ancillaries.map((a) => a.code) + ) if (!ancillaries || !bookedRoom) { return null } return ( - +
- {ancillaries.unique.length > 0 && bookedRoom.canModifyAncillaries && ( - <> -
- -

- {intl.formatMessage({ - id: "ancillaries.upgradeYourStay", - defaultMessage: "Upgrade your stay", - })} -

-
-
- + {ancillaries.availableUnique.length > 0 && + bookedRoom.canModifyAncillaries && ( + <> +
+ +

+ {intl.formatMessage({ + id: "ancillaries.upgradeYourStay", + defaultMessage: "Upgrade your stay", + })} +

+
+
+ +
-
-
- {ancillaries.unique.slice(0, 4).map((ancillary) => ( - - ))} -
+
+ {ancillaries.availableUnique.slice(0, 4).map((ancillary) => ( + + ))} +
-
- - - {ancillaries.unique.map((ancillary) => { - return ( - - - - ) - })} - - - -
- - )} +
+ + + {ancillaries.availableUnique.map((ancillary) => { + return ( + + + + ) + })} + + + +
+ + )} diff --git a/apps/scandic-web/hooks/useAncillaries.ts b/apps/scandic-web/hooks/useAncillaries.ts index 595cbe2c7..9cdd06e53 100644 --- a/apps/scandic-web/hooks/useAncillaries.ts +++ b/apps/scandic-web/hooks/useAncillaries.ts @@ -17,7 +17,8 @@ import type { export function useAncillaries( ancillariesPromise: Promise, packages: Packages | null, - user: User | null + user: User | null, + alreadyAcquiredAncillaryCodes: string[] ) { const intl = useIntl() const bookedRoom = useMyStayStore((state) => state.bookedRoom) @@ -86,9 +87,23 @@ export function useAncillaries( return null } - const uniqueAncillaries = generateUniqueAncillaries(allAncillaries) + const allUniqueAncillaries = generateUniqueAncillaries(allAncillaries) - return { all: allAncillaries, unique: uniqueAncillaries } + const availableByCategory = alreadyAcquiredAncillaryCodes.length + ? filterOutAlreadyAcquiredAncillaries( + allAncillaries, + alreadyAcquiredAncillaryCodes + ) + : allAncillaries + + const availableUniqueAncillaries = + generateUniqueAncillaries(availableByCategory) + + return { + availableByCategory, + allUnique: allUniqueAncillaries, + availableUnique: availableUniqueAncillaries, + } } function mapAncillaries( @@ -183,3 +198,19 @@ function addBreakfastPackage( ...ancillaries, ] } + +function filterOutAlreadyAcquiredAncillaries( + ancillaries: Ancillaries, + alreadyAcquiredAncillaryCodes: string[] +): Ancillaries { + return ancillaries.map((cat) => ({ + ...cat, + ancillaryContent: cat.ancillaryContent.filter((ancillary) => + ancillary.requiresQuantity + ? true + : !alreadyAcquiredAncillaryCodes.includes( + ancillary.loyaltyCode || ancillary.id + ) + ), + })) +} diff --git a/apps/scandic-web/stores/my-stay/add-ancillary-flow.ts b/apps/scandic-web/stores/my-stay/add-ancillary-flow.ts index 950c0caaa..9a4ab1ce2 100644 --- a/apps/scandic-web/stores/my-stay/add-ancillary-flow.ts +++ b/apps/scandic-web/stores/my-stay/add-ancillary-flow.ts @@ -83,9 +83,9 @@ export const createAddAncillaryStore = ( ancillaries, selectedCategory ) - const categories = ancillaries.map( - (ancillary) => ancillary.translatedCategoryName - ) + const categories = ancillaries + .filter((anc) => !!anc.ancillaryContent.length) + .map((ancillary) => ancillary.translatedCategoryName) const steps = { [AncillaryStepEnum.selectQuantity]: { step: AncillaryStepEnum.selectQuantity, diff --git a/packages/trpc/lib/routers/hotels/output.ts b/packages/trpc/lib/routers/hotels/output.ts index ad20e4ed5..14f025e42 100644 --- a/packages/trpc/lib/routers/hotels/output.ts +++ b/packages/trpc/lib/routers/hotels/output.ts @@ -444,14 +444,28 @@ export const breakfastPackagesSchema = z data.attributes.packages.filter((pkg) => pkg.code?.match(/^(BRF\d+)$/gm)) ) -// Determine if ancillary requires quantity based on unit name. These ancillaries are special -// since they are 1 per booking, but we have no other way than string matching on unit name -// to determine this from the API at the moment. -function getRequiresQuantity(unitName?: string) { - return (unitName && unitName === "Late check-out") || - unitName === "Early check-in" - ? false - : true +enum SingleUseAncillaryIds { + EarlyCheckIn = "0060", + LateCheckOut = "0061", + EarlyCheckinPilot = "0060999", + LateCheckoutPilot = "0061999", +} + +// Determine if ancillary requires quantity based on ID. These ancillaries are special since they +// are 1 per booking. The agreement is to use the same last digits in the ID for both early check-in +// and late check-out ancillaries in order to identify them here regardless of language or market. +// During the Pilot phase, the IDs are different but the same logic applies. +function getRequiresQuantity(id: string) { + const code = id.split("_").pop() + + if (code) { + return Object.values(SingleUseAncillaryIds).includes( + code as SingleUseAncillaryIds + ) + ? false + : true + } + return true } export const ancillaryPackagesSchema = z @@ -485,7 +499,7 @@ export const ancillaryPackagesSchema = z requiresDeliveryTime: item.requiresDeliveryTime, translatedCategoryName: ancillary.categoryName, internalCategoryName: ancillary.internalCategoryName, - requiresQuantity: getRequiresQuantity(item.unitName), + requiresQuantity: getRequiresQuantity(item.id), })), })) .filter((ancillary) => ancillary.ancillaryContent.length > 0) diff --git a/packages/trpc/lib/routers/hotels/schemas/packages.ts b/packages/trpc/lib/routers/hotels/schemas/packages.ts index 907ea3d95..0603f5340 100644 --- a/packages/trpc/lib/routers/hotels/schemas/packages.ts +++ b/packages/trpc/lib/routers/hotels/schemas/packages.ts @@ -66,6 +66,6 @@ export const breakfastPackageSchema = z.object({ export const ancillaryPackageSchema = z.object({ categoryName: z.string(), - internalCategoryName: z.string(), + internalCategoryName: z.string().optional(), ancillaryContent: z.array(ancillaryContentSchema), })