Files
web/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/AddAncillaryFlow/Summary/index.tsx
Matilda Landström 7ca366de57 Merged in feat/ancillaries-ui-fixes (pull request #3383)
feat: update ancillaries flow ui

* feat: update ancillaries flow ui


Approved-by: Matilda Haneling
2025-12-30 09:55:46 +00:00

286 lines
7.9 KiB
TypeScript

import { cx } from "class-variance-authority"
import { useState } from "react"
import { useFormContext, useWatch } from "react-hook-form"
import { type IntlShape, useIntl } from "react-intl"
import { Button } from "@scandic-hotels/design-system/Button"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import {
AncillaryStepEnum,
type BreakfastData,
useAddAncillaryStore,
} from "@/stores/my-stay/add-ancillary-flow"
import { trackAddAncillary } from "@/utils/tracking/myStay"
import { PaymentChoiceEnum } from "../schema"
import PriceDetails from "./PriceDetails"
import { PriceSummary } from "./PriceSummary"
import styles from "./summary.module.css"
import type { SelectedAncillary } from "@/types/components/myPages/myStay/ancillaries"
export default function Summary({ onSubmit }: { onSubmit: () => void }) {
const intl = useIntl()
const [isPriceDetailsOpen, setIsPriceDetailsOpen] = useState(false)
function togglePriceDetails() {
setIsPriceDetailsOpen((isOpen) => !isOpen)
}
const {
prevStep,
selectedAncillary,
isBreakfast,
breakfastData,
currentStep,
selectDeliveryTime,
selectQuantity,
} = useAddAncillaryStore((state) => ({
prevStep: state.prevStep,
currentStep: state.currentStep,
selectedAncillary: state.selectedAncillary,
isBreakfast: state.isBreakfast,
breakfastData: state.breakfastData,
selectQuantity: state.selectQuantity,
selectDeliveryTime: state.selectDeliveryTime,
}))
const {
trigger,
formState: { isSubmitting },
} = useFormContext()
const quantity = useWatch({ name: "quantity" }) as number
const paymentChoice: PaymentChoiceEnum = useWatch({ name: "paymentChoice" })
const isConfirmation = currentStep === AncillaryStepEnum.confirmation
async function handleNextStep() {
switch (currentStep) {
case AncillaryStepEnum.selectQuantity: {
const isValid = await trigger(["quantity", "paymentChoice"])
if (isValid) {
const quantityWithCard =
paymentChoice === PaymentChoiceEnum.Points ? 0 : quantity
const quantityWithPoints =
paymentChoice === PaymentChoiceEnum.Points ? quantity : 0
trackAddAncillary(
selectedAncillary,
quantityWithCard,
quantityWithPoints,
breakfastData
)
selectQuantity()
}
break
}
case AncillaryStepEnum.selectDelivery: {
const isValid = await trigger("deliveryTime")
if (isValid) {
selectDeliveryTime()
}
break
}
case AncillaryStepEnum.confirmation: {
onSubmit()
break
}
}
}
if (!selectedAncillary || (!breakfastData && isBreakfast)) {
return null
}
const isSingleItem = !selectedAncillary.requiresQuantity
const secondaryButtonLabel =
currentStep === AncillaryStepEnum.selectQuantity
? intl.formatMessage({
id: "common.cancel",
defaultMessage: "Cancel",
})
: intl.formatMessage({
id: "common.back",
defaultMessage: "Back",
})
function buttonLabel() {
switch (currentStep) {
case AncillaryStepEnum.selectQuantity:
return isSingleItem
? intl.formatMessage({
id: "common.reviewAndConfirm",
defaultMessage: "Review & Confirm",
})
: intl.formatMessage({
id: "addAncillaryFlowModal.proceedToDelivery",
defaultMessage: "Proceed to delivery",
})
case AncillaryStepEnum.selectDelivery:
return intl.formatMessage({
id: "common.reviewAndConfirm",
defaultMessage: "Review & Confirm",
})
case AncillaryStepEnum.confirmation:
return intl.formatMessage({
id: "addAncillaryFlowModal.addToBooking",
defaultMessage: "Add to booking",
})
}
}
const items = isBreakfast
? getBreakfastItems(intl, selectedAncillary, breakfastData)
: [
{
title: selectedAncillary.title,
totalPrice: selectedAncillary.price.total,
currency: selectedAncillary.price.currency,
points: selectedAncillary.points,
quantity,
},
]
const totalPrice = isBreakfast
? breakfastData!.totalPrice
: paymentChoice === PaymentChoiceEnum.Card && selectedAncillary
? selectedAncillary.price.total * quantity
: null
const totalPoints =
paymentChoice === PaymentChoiceEnum.Points && selectedAncillary?.points
? selectedAncillary.points * quantity
: null
return (
<div className={styles.summary}>
<div
className={cx({
[styles.backgroundBox]: isConfirmation || isSingleItem,
})}
>
{(isSingleItem || isConfirmation) && (
<PriceDetails
totalPrice={totalPrice}
totalPoints={totalPoints}
selectedAncillary={selectedAncillary}
/>
)}
{isConfirmation && isPriceDetailsOpen && (
<PriceSummary
totalPrice={totalPrice}
totalPoints={totalPoints}
items={items}
paymentChoice={paymentChoice}
/>
)}
<div
className={cx({
[styles.confirmButtons]: isConfirmation,
})}
>
{isConfirmation && (
<Button
type="button"
size="sm"
variant="Text"
onPress={togglePriceDetails}
className={styles.priceButton}
>
{intl.formatMessage({
id: "commonpriceDetails",
defaultMessage: "Price details",
})}
<MaterialIcon
icon={
isPriceDetailsOpen
? "keyboard_arrow_up"
: "keyboard_arrow_down"
}
size={20}
color="CurrentColor"
/>
</Button>
)}
<div className={styles.buttons}>
<Button
type="button"
variant="Text"
size="sm"
color="Primary"
wrapping={false}
onPress={prevStep}
>
{secondaryButtonLabel}
</Button>
<Button
size="sm"
isDisabled={isSubmitting}
isPending={isSubmitting}
onPress={handleNextStep}
variant="Primary"
>
{buttonLabel()}
</Button>
</div>
</div>
</div>
</div>
)
}
function getBreakfastItems(
intl: IntlShape,
selectedAncillary: SelectedAncillary,
breakfastData: BreakfastData | null
) {
if (!breakfastData) {
return []
}
const items = [
{
title: `${selectedAncillary.title} / ${intl.formatMessage({
id: "common.adult",
defaultMessage: "adult",
})}`,
totalPrice: breakfastData.priceAdult,
currency: breakfastData.currency,
quantity: breakfastData.nrOfAdults * breakfastData.nrOfNights,
},
]
if (breakfastData.nrOfPayingChildren > 0) {
items.push({
title: `${selectedAncillary.title} / ${intl.formatMessage({
id: "common.children",
defaultMessage: "Children",
})} 4-12`,
totalPrice: breakfastData.priceChild,
currency: breakfastData.currency,
quantity: breakfastData.nrOfPayingChildren * breakfastData.nrOfNights,
})
}
if (breakfastData.nrOfFreeChildren > 0) {
items.push({
title: `${selectedAncillary.title} / ${intl.formatMessage(
{
id: "common.childrenUnderAge",
defaultMessage: "Children under {age}",
},
{ age: 4 }
)}`,
totalPrice: 0,
currency: breakfastData.currency,
quantity: breakfastData.nrOfFreeChildren * breakfastData.nrOfNights,
})
}
return items
}