Merged in fix/STAY-131-hide-ancillaries (pull request #3299)
fix: fix logic for identifying single use ancillaries * fix: fix logic for identifying single use ancillaries * fix: filter out empty categories of ancillaries Approved-by: Erik Tiekstra
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { useIntl } from "react-intl"
|
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"
|
import { useAddAncillaryStore } from "@/stores/my-stay/add-ancillary-flow"
|
||||||
|
|
||||||
@@ -30,22 +30,20 @@ export default function SelectAncillaryStep({
|
|||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.tabs}>
|
<div className={styles.tabs}>
|
||||||
{categories.map((categoryName) => (
|
{categories.map((categoryName) => (
|
||||||
<button
|
<ChipButton
|
||||||
|
onPress={() => selectCategory(categoryName)}
|
||||||
key={categoryName}
|
key={categoryName}
|
||||||
className={`${styles.chip} ${categoryName === selectedCategory ? styles.selected : ""}`}
|
selected={categoryName === selectedCategory}
|
||||||
onClick={() => selectCategory(categoryName)}
|
variant="FilterRounded"
|
||||||
|
size="Large"
|
||||||
>
|
>
|
||||||
<Typography variant="Body/Supporting text (caption)/smRegular">
|
{categoryName
|
||||||
<p>
|
? categoryName
|
||||||
{categoryName
|
: intl.formatMessage({
|
||||||
? categoryName
|
id: "common.other",
|
||||||
: intl.formatMessage({
|
defaultMessage: "Other",
|
||||||
id: "common.other",
|
})}
|
||||||
defaultMessage: "Other",
|
</ChipButton>
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
</Typography>
|
|
||||||
</button>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import { AddAncillaryProvider } from "@/providers/AddAncillaryProvider"
|
|||||||
|
|
||||||
import AddAncillaryFlowModal from "./AddAncillaryFlow/AddAncillaryFlowModal"
|
import AddAncillaryFlowModal from "./AddAncillaryFlow/AddAncillaryFlowModal"
|
||||||
import AncillaryFlowModalWrapper from "./AddAncillaryFlow/AncillaryFlowModalWrapper"
|
import AncillaryFlowModalWrapper from "./AddAncillaryFlow/AncillaryFlowModalWrapper"
|
||||||
import AllAncillariesModal from "./AllAncillariesModal/input"
|
|
||||||
import { AddedAncillaries } from "./AddedAncillaries"
|
import { AddedAncillaries } from "./AddedAncillaries"
|
||||||
|
import AllAncillariesModal from "./AllAncillariesModal"
|
||||||
import WrappedAncillaryCard from "./Card"
|
import WrappedAncillaryCard from "./Card"
|
||||||
|
|
||||||
import styles from "./ancillaries.module.css"
|
import styles from "./ancillaries.module.css"
|
||||||
@@ -28,60 +28,69 @@ export function Ancillaries({
|
|||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const bookedRoom = useMyStayStore((state) => state.bookedRoom)
|
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) {
|
if (!ancillaries || !bookedRoom) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AddAncillaryProvider booking={bookedRoom} ancillaries={ancillaries.all}>
|
<AddAncillaryProvider
|
||||||
|
booking={bookedRoom}
|
||||||
|
ancillaries={ancillaries.availableByCategory}
|
||||||
|
>
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
{ancillaries.unique.length > 0 && bookedRoom.canModifyAncillaries && (
|
{ancillaries.availableUnique.length > 0 &&
|
||||||
<>
|
bookedRoom.canModifyAncillaries && (
|
||||||
<div className={styles.title}>
|
<>
|
||||||
<Typography variant="Title/Subtitle/lg">
|
<div className={styles.title}>
|
||||||
<h2>
|
<Typography variant="Title/Subtitle/lg">
|
||||||
{intl.formatMessage({
|
<h2>
|
||||||
id: "ancillaries.upgradeYourStay",
|
{intl.formatMessage({
|
||||||
defaultMessage: "Upgrade your stay",
|
id: "ancillaries.upgradeYourStay",
|
||||||
})}
|
defaultMessage: "Upgrade your stay",
|
||||||
</h2>
|
})}
|
||||||
</Typography>
|
</h2>
|
||||||
<div className={styles.viewAllLink}>
|
</Typography>
|
||||||
<AllAncillariesModal />
|
<div className={styles.viewAllLink}>
|
||||||
|
<AllAncillariesModal />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.ancillaries}>
|
<div className={styles.ancillaries}>
|
||||||
{ancillaries.unique.slice(0, 4).map((ancillary) => (
|
{ancillaries.availableUnique.slice(0, 4).map((ancillary) => (
|
||||||
<WrappedAncillaryCard
|
<WrappedAncillaryCard
|
||||||
ancillary={ancillary}
|
ancillary={ancillary}
|
||||||
key={ancillary.id}
|
key={ancillary.id}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.mobileAncillaries}>
|
<div className={styles.mobileAncillaries}>
|
||||||
<Carousel>
|
<Carousel>
|
||||||
<Carousel.Content>
|
<Carousel.Content>
|
||||||
{ancillaries.unique.map((ancillary) => {
|
{ancillaries.availableUnique.map((ancillary) => {
|
||||||
return (
|
return (
|
||||||
<Carousel.Item key={ancillary.id}>
|
<Carousel.Item key={ancillary.id}>
|
||||||
<WrappedAncillaryCard ancillary={ancillary} />
|
<WrappedAncillaryCard ancillary={ancillary} />
|
||||||
</Carousel.Item>
|
</Carousel.Item>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</Carousel.Content>
|
</Carousel.Content>
|
||||||
<Carousel.Dots />
|
<Carousel.Dots />
|
||||||
</Carousel>
|
</Carousel>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<AddedAncillaries
|
<AddedAncillaries
|
||||||
booking={bookedRoom}
|
booking={bookedRoom}
|
||||||
ancillaries={ancillaries.unique}
|
ancillaries={ancillaries.allUnique}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AncillaryFlowModalWrapper>
|
<AncillaryFlowModalWrapper>
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ import type {
|
|||||||
export function useAncillaries(
|
export function useAncillaries(
|
||||||
ancillariesPromise: Promise<Ancillaries | null>,
|
ancillariesPromise: Promise<Ancillaries | null>,
|
||||||
packages: Packages | null,
|
packages: Packages | null,
|
||||||
user: User | null
|
user: User | null,
|
||||||
|
alreadyAcquiredAncillaryCodes: string[]
|
||||||
) {
|
) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const bookedRoom = useMyStayStore((state) => state.bookedRoom)
|
const bookedRoom = useMyStayStore((state) => state.bookedRoom)
|
||||||
@@ -86,9 +87,23 @@ export function useAncillaries(
|
|||||||
return null
|
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(
|
function mapAncillaries(
|
||||||
@@ -183,3 +198,19 @@ function addBreakfastPackage(
|
|||||||
...ancillaries,
|
...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
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|||||||
@@ -83,9 +83,9 @@ export const createAddAncillaryStore = (
|
|||||||
ancillaries,
|
ancillaries,
|
||||||
selectedCategory
|
selectedCategory
|
||||||
)
|
)
|
||||||
const categories = ancillaries.map(
|
const categories = ancillaries
|
||||||
(ancillary) => ancillary.translatedCategoryName
|
.filter((anc) => !!anc.ancillaryContent.length)
|
||||||
)
|
.map((ancillary) => ancillary.translatedCategoryName)
|
||||||
const steps = {
|
const steps = {
|
||||||
[AncillaryStepEnum.selectQuantity]: {
|
[AncillaryStepEnum.selectQuantity]: {
|
||||||
step: AncillaryStepEnum.selectQuantity,
|
step: AncillaryStepEnum.selectQuantity,
|
||||||
|
|||||||
@@ -444,14 +444,28 @@ 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))
|
||||||
)
|
)
|
||||||
|
|
||||||
// Determine if ancillary requires quantity based on unit name. These ancillaries are special
|
enum SingleUseAncillaryIds {
|
||||||
// since they are 1 per booking, but we have no other way than string matching on unit name
|
EarlyCheckIn = "0060",
|
||||||
// to determine this from the API at the moment.
|
LateCheckOut = "0061",
|
||||||
function getRequiresQuantity(unitName?: string) {
|
EarlyCheckinPilot = "0060999",
|
||||||
return (unitName && unitName === "Late check-out") ||
|
LateCheckoutPilot = "0061999",
|
||||||
unitName === "Early check-in"
|
}
|
||||||
? false
|
|
||||||
: true
|
// 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
|
export const ancillaryPackagesSchema = z
|
||||||
@@ -485,7 +499,7 @@ export const ancillaryPackagesSchema = z
|
|||||||
requiresDeliveryTime: item.requiresDeliveryTime,
|
requiresDeliveryTime: item.requiresDeliveryTime,
|
||||||
translatedCategoryName: ancillary.categoryName,
|
translatedCategoryName: ancillary.categoryName,
|
||||||
internalCategoryName: ancillary.internalCategoryName,
|
internalCategoryName: ancillary.internalCategoryName,
|
||||||
requiresQuantity: getRequiresQuantity(item.unitName),
|
requiresQuantity: getRequiresQuantity(item.id),
|
||||||
})),
|
})),
|
||||||
}))
|
}))
|
||||||
.filter((ancillary) => ancillary.ancillaryContent.length > 0)
|
.filter((ancillary) => ancillary.ancillaryContent.length > 0)
|
||||||
|
|||||||
@@ -66,6 +66,6 @@ export const breakfastPackageSchema = z.object({
|
|||||||
|
|
||||||
export const ancillaryPackageSchema = z.object({
|
export const ancillaryPackageSchema = z.object({
|
||||||
categoryName: z.string(),
|
categoryName: z.string(),
|
||||||
internalCategoryName: z.string(),
|
internalCategoryName: z.string().optional(),
|
||||||
ancillaryContent: z.array(ancillaryContentSchema),
|
ancillaryContent: z.array(ancillaryContentSchema),
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user