Files
web/apps/scandic-web/components/HotelReservation/MyStay/Ancillaries/AddedAncillaries/index.tsx
Bianca Widstam 1b9273136a Merged in chore/BOOK-701-replace-subtitle-component (pull request #3398)
chore(BOOK-701): replace subtitle with typography

* chore(BOOK-701): replace subtitle with typography

* chore(BOOK-701): align center

* chore(BOOK-701): change token

* chore(BOOK-701): change text color

* fix(BOOK-704): revert pricechange dialog changes

* chore(BOOK-701): remove subtitle from package.json


Approved-by: Matilda Landström
2026-01-12 07:40:30 +00:00

297 lines
11 KiB
TypeScript

import { Fragment } from "react"
import { useIntl } from "react-intl"
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
import Accordion from "@scandic-hotels/design-system/Accordion"
import AccordionItem from "@scandic-hotels/design-system/Accordion/AccordionItem"
import { Divider } from "@scandic-hotels/design-system/Divider"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { BreakfastPackageEnum } from "@scandic-hotels/trpc/enums/breakfast"
import { getBreakfastPackagesFromAncillaryFlow } from "../../utils/hasBreakfastPackage"
import RemoveButton from "./RemoveButton"
import styles from "./addedAncillaries.module.css"
import type { PackageSchema } from "@scandic-hotels/trpc/types/bookingConfirmation"
import type { AddedAncillariesProps } from "@/types/components/myPages/myStay/ancillaries"
import type { Room } from "@/types/stores/my-stay"
export function AddedAncillaries({
ancillaries,
booking,
}: AddedAncillariesProps) {
const intl = useIntl()
const addedBreakfastPackages = getBreakfastPackagesFromAncillaryFlow(
booking.originalPackages
)
const addedAncillaries = getAddedAncillaries(booking, addedBreakfastPackages)
if (addedAncillaries.length === 0) {
return null
}
return (
<div className={styles.container}>
<div className={styles.header}>
<Typography variant="Title/Subtitle/lg">
<p>
{intl.formatMessage({
defaultMessage: "My extras",
id: "myStay.addedAncillaries.title",
})}
</p>
</Typography>
{booking.ancillary?.deliveryTime && (
<Typography variant="Body/Paragraph/mdBold">
<div className={styles.deliveryTime}>
<p>
{intl.formatMessage({
id: "ancillaries.deliveredAt",
defaultMessage: "Delivered at:",
})}
</p>
<p>{booking.ancillary?.deliveryTime}</p>
</div>
</Typography>
)}
</div>
{addedAncillaries.map((ancillary) => {
const ancillaryTitle = `${
ancillary.code === BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST
? intl.formatMessage({
id: "common.breakfast",
defaultMessage: "Breakfast",
})
: (ancillaries?.find(
(a) =>
a.id === ancillary.code || a.loyaltyCode === ancillary.code
)?.title ?? "")
} X${ancillary.totalUnit}`
return (
<Fragment key={ancillary.code}>
<Accordion className={styles.ancillaryMobile}>
<AccordionItem
title={ancillaryTitle}
icon={
<MaterialIcon
icon="check_circle"
color="Icon/Feedback/Success"
/>
}
>
<div>
{ancillary.comment && (
<>
<div className={styles.commentMobile}>
<Typography variant="Body/Paragraph/mdBold">
<p>
{intl.formatMessage({
id: "common.otherRequests",
defaultMessage: "Other requests",
})}
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
{":"}
</p>
</Typography>
<Typography
variant="Body/Paragraph/mdBold"
className={styles.ancillaryComment}
>
<p>{ancillary.comment}</p>
</Typography>
</div>
</>
)}
<div className={styles.paymentMobileWrapper}>
<div className={styles.paymentMobile}>
<Typography variant="Body/Paragraph/mdRegular">
<p>
{intl.formatMessage({
id: "common.total",
defaultMessage: "Total",
})}
</p>
</Typography>
<Typography variant="Body/Paragraph/mdBold">
<p>
{ancillary.currency.toLowerCase() === "points"
? intl.formatMessage(
{
id: "common.numberOfPoints",
defaultMessage:
"{points, plural, one {# point} other {# points}}",
},
{
points: ancillary.totalPrice,
}
)
: formatPrice(
intl,
ancillary.totalPrice,
ancillary.currency
)}
</p>
</Typography>
</div>
</div>
</div>
<div className={styles.footerMobile}>
{booking.confirmationNumber && ancillary.code ? (
<div className={styles.actions}>
<RemoveButton
refId={booking.refId}
codes={
ancillary.code ===
BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST
? addedBreakfastPackages!.map((p) => p.code)
: [ancillary.code]
}
title={ancillaryTitle}
booking={booking}
ancillary={ancillary}
addedBreakfastPackages={addedBreakfastPackages}
/>
</div>
) : null}
</div>
</AccordionItem>
</Accordion>
<div className={styles.ancillaryDesktop}>
<div className={styles.specification}>
<div className={styles.name}>
<MaterialIcon
icon="check_circle"
color="Icon/Feedback/Success"
/>
<Typography variant="Body/Paragraph/mdBold">
<p>{ancillaryTitle}</p>
</Typography>
</div>
<div className={styles.payment}>
<Typography variant="Body/Paragraph/mdRegular">
<p>
{intl.formatMessage({
id: "common.total",
defaultMessage: "Total",
})}
</p>
</Typography>
<Typography variant="Body/Paragraph/mdBold">
<p>
{ancillary.currency.toLowerCase() === "points"
? intl.formatMessage(
{
id: "common.numberOfPoints",
defaultMessage:
"{points, plural, one {# point} other {# points}}",
},
{
points: ancillary.totalPrice,
}
)
: formatPrice(
intl,
ancillary.totalPrice,
ancillary.currency
)}
</p>
</Typography>
</div>
</div>
<Divider />
<div className={styles.footer}>
<div className={styles.comment}>
{ancillary.comment && (
<>
<Typography variant="Body/Paragraph/mdRegular">
<p>
{intl.formatMessage({
id: "common.otherRequests",
defaultMessage: "Other requests",
})}
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
{":"}
</p>
</Typography>
<Typography variant="Body/Paragraph/mdRegular">
<p>{ancillary.comment}</p>
</Typography>
</>
)}
</div>
{booking.confirmationNumber &&
ancillary.code &&
booking.canModifyAncillaries ? (
<div className={styles.actions}>
<RemoveButton
refId={booking.refId}
codes={
ancillary.code ===
BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST
? addedBreakfastPackages!.map((p) => p.code)
: [ancillary.code]
}
title={ancillaryTitle}
booking={booking}
ancillary={ancillary}
addedBreakfastPackages={addedBreakfastPackages}
/>
</div>
) : null}
</div>
</div>
</Fragment>
)
})}
</div>
)
}
/**
* All ancillaries that are added to the booking
*
* Adding a special ancillary for breakfast, calculated from
* all breakfast packages that has been added as ancillaries,
* not in the booking flow.
*/
function getAddedAncillaries(
booking: Room,
addedBreakfastPackages: PackageSchema[] | undefined
) {
if (!addedBreakfastPackages?.length) {
return booking.ancillaries
}
const combinedBreakfastPackageAsAncillary: PackageSchema = {
code: BreakfastPackageEnum.ANCILLARY_REGULAR_BREAKFAST,
unitPrice: 0,
currency: addedBreakfastPackages[0].currency,
type: addedBreakfastPackages[0].type,
description: addedBreakfastPackages[0].description,
comment: addedBreakfastPackages[0].comment,
totalPrice: addedBreakfastPackages.reduce(
(acc, curr) => acc + curr.totalPrice,
0
),
unit: addedBreakfastPackages.reduce((acc, curr) => acc + curr.unit, 0),
totalUnit: addedBreakfastPackages.reduce(
(acc, curr) => acc + curr.totalUnit,
0
),
}
return [combinedBreakfastPackageAsAncillary, ...booking.ancillaries]
}