Sync defaultMessage from lokalise * Enhance translation sync functionality and tests - Added logging for found component files during sync. - Introduced tests for handling complex components with replacements. - Updated regex in syncIntlFormatMessage to support optional second arguments. - Removed unused test files. * feat(syncDefaultMessage): add script for syncing default message with lokalise * feat(syncDefaultMessage): add script for syncing default message with lokalise Approved-by: Matilda Landström
358 lines
11 KiB
TypeScript
358 lines
11 KiB
TypeScript
"use client"
|
|
import { useIntl } from "react-intl"
|
|
|
|
import { IconForFeatureCode } from "@scandic-hotels/booking-flow/utils/SelectRate"
|
|
import { CancellationRuleEnum } from "@scandic-hotels/common/constants/booking"
|
|
import { changeOrCancelDateFormat } from "@scandic-hotels/common/constants/dateFormats"
|
|
import { RateEnum } from "@scandic-hotels/common/constants/rate"
|
|
import { dt } from "@scandic-hotels/common/dt"
|
|
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
|
|
import { Divider } from "@scandic-hotels/design-system/Divider"
|
|
import { IconButton } from "@scandic-hotels/design-system/IconButton"
|
|
import IconChip from "@scandic-hotels/design-system/IconChip"
|
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
|
import Image from "@scandic-hotels/design-system/Image"
|
|
import ImageFallback from "@scandic-hotels/design-system/ImageFallback"
|
|
import Modal from "@scandic-hotels/design-system/Modal"
|
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
|
import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter"
|
|
|
|
import useRateTitles from "@/hooks/booking/useRateTitles"
|
|
import useLang from "@/hooks/useLang"
|
|
|
|
import PriceType from "../../PriceType"
|
|
import { hasModifiableRate } from "../../utils"
|
|
import RoomDetailsSidePeek from "./RoomDetailsSidePeek"
|
|
|
|
import styles from "./room.module.css"
|
|
|
|
import type { Room } from "@/types/stores/my-stay"
|
|
import type { SafeUser } from "@/types/user"
|
|
|
|
interface RoomProps {
|
|
booking: Room
|
|
roomNr: number
|
|
user: SafeUser
|
|
}
|
|
|
|
export default function Room({ booking, roomNr, user }: RoomProps) {
|
|
const intl = useIntl()
|
|
const lang = useLang()
|
|
|
|
const {
|
|
adults,
|
|
breakfast,
|
|
cancellationNumber,
|
|
checkInDate,
|
|
cheques,
|
|
childrenAges,
|
|
confirmationNumber,
|
|
currencyCode,
|
|
packages,
|
|
rateDefinition,
|
|
room,
|
|
roomName,
|
|
totalPoints,
|
|
isCancelled,
|
|
priceType,
|
|
vouchers,
|
|
totalPrice,
|
|
} = booking
|
|
|
|
const fromDate = dt(checkInDate).locale(lang)
|
|
|
|
const adultsMsg = intl.formatMessage(
|
|
{
|
|
id: "booking.numberOfAdults",
|
|
defaultMessage: "{adults, plural, one {# adult} other {# adults}}",
|
|
},
|
|
{
|
|
adults: adults,
|
|
}
|
|
)
|
|
|
|
const childrenMsg = intl.formatMessage(
|
|
{
|
|
id: "booking.numberOfChildren",
|
|
defaultMessage: "{children, plural, one {# child} other {# children}}",
|
|
},
|
|
{
|
|
children: childrenAges.length,
|
|
}
|
|
)
|
|
|
|
const adultsOnlyMsg = adultsMsg
|
|
const adultsAndChildrenMsg = [adultsMsg, childrenMsg].join(", ")
|
|
|
|
const formattedTotalPrice = formatPrice(intl, totalPrice, currencyCode)
|
|
|
|
let breakfastPrice = intl.formatMessage({
|
|
id: "common.noBreakfast",
|
|
defaultMessage: "No breakfast",
|
|
})
|
|
if (rateDefinition.breakfastIncluded) {
|
|
breakfastPrice = intl.formatMessage({
|
|
id: "common.included",
|
|
defaultMessage: "Included",
|
|
})
|
|
} else if (breakfast) {
|
|
breakfastPrice = formatPrice(
|
|
intl,
|
|
breakfast.localPrice.totalPrice,
|
|
breakfast.localPrice.currency
|
|
)
|
|
}
|
|
|
|
const rateTitles = useRateTitles()
|
|
let rateTerm: { paymentTerm: string; title: string }
|
|
switch (rateDefinition.cancellationRule) {
|
|
case CancellationRuleEnum.CancellableBefore6PM:
|
|
rateTerm = rateTitles[RateEnum.flex]
|
|
break
|
|
case CancellationRuleEnum.Changeable:
|
|
rateTerm = rateTitles[RateEnum.change]
|
|
break
|
|
default:
|
|
rateTerm = rateTitles[RateEnum.save]
|
|
}
|
|
|
|
return (
|
|
<article className={styles.multiRoom}>
|
|
<Typography variant="Title/smLowCase">
|
|
<h3 className={styles.roomName}>{roomName}</h3>
|
|
</Typography>
|
|
<div className={styles.roomHeader}>
|
|
{isCancelled ? (
|
|
<IconChip
|
|
color={"red"}
|
|
icon={
|
|
<MaterialIcon
|
|
icon="cancel"
|
|
size={20}
|
|
color="Icon/Feedback/Error"
|
|
/>
|
|
}
|
|
>
|
|
<Typography variant="Body/Supporting text (caption)/smBold">
|
|
<span>
|
|
{intl.formatMessage({
|
|
id: "common.cancelled",
|
|
defaultMessage: "Cancelled",
|
|
})}
|
|
</span>
|
|
</Typography>
|
|
</IconChip>
|
|
) : (
|
|
<div className={styles.chip}>
|
|
<Typography variant="Tag/sm">
|
|
<span>
|
|
{intl.formatMessage(
|
|
{
|
|
id: "booking.roomIndex",
|
|
defaultMessage: "Room {roomIndex}",
|
|
},
|
|
{
|
|
roomIndex: roomNr,
|
|
}
|
|
)}
|
|
</span>
|
|
</Typography>
|
|
</div>
|
|
)}
|
|
<div className={styles.reference}>
|
|
<Typography variant="Body/Supporting text (caption)/smBold">
|
|
{isCancelled ? (
|
|
<span>
|
|
{intl.formatMessage({
|
|
id: "booking.cancellationNo",
|
|
defaultMessage: "Cancellation no",
|
|
})}
|
|
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
|
{":"}
|
|
</span>
|
|
) : (
|
|
<span>
|
|
{intl.formatMessage({
|
|
id: "common.bookingNumber",
|
|
defaultMessage: "Booking number",
|
|
})}
|
|
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
|
{":"}
|
|
</span>
|
|
)}
|
|
</Typography>
|
|
<Typography variant="Body/Supporting text (caption)/smRegular">
|
|
{isCancelled ? (
|
|
<span className={styles.cancellationNumber}>
|
|
{cancellationNumber}
|
|
</span>
|
|
) : (
|
|
<span>{confirmationNumber}</span>
|
|
)}
|
|
</Typography>
|
|
</div>
|
|
<div className={styles.toggleSidePeek}>
|
|
<RoomDetailsSidePeek booking={booking} user={user} />
|
|
</div>
|
|
</div>
|
|
<div
|
|
className={`${styles.multiRoomCard} ${isCancelled ? styles.cancelled : ""}`}
|
|
>
|
|
{packages?.some((item) =>
|
|
Object.values(RoomPackageCodeEnum).includes(
|
|
item.code as RoomPackageCodeEnum
|
|
)
|
|
) && (
|
|
<div className={styles.packages}>
|
|
{packages
|
|
.filter((item) =>
|
|
Object.values(RoomPackageCodeEnum).includes(
|
|
item.code as RoomPackageCodeEnum
|
|
)
|
|
)
|
|
.map((item) => {
|
|
return (
|
|
<span className={styles.package} key={item.code}>
|
|
<IconForFeatureCode
|
|
featureCode={item.code}
|
|
size={16}
|
|
color="Icon/Interactive/Default"
|
|
/>
|
|
</span>
|
|
)
|
|
})}
|
|
</div>
|
|
)}
|
|
<div className={styles.imageContainer}>
|
|
{room?.images[0]?.src ? (
|
|
<Image src={room.images[0].src} alt={roomName} fill />
|
|
) : (
|
|
<ImageFallback />
|
|
)}
|
|
</div>
|
|
<div className={styles.details}>
|
|
<div className={styles.row}>
|
|
<Typography variant="Body/Paragraph/mdBold">
|
|
<p>
|
|
{intl.formatMessage({
|
|
id: "booking.guests",
|
|
defaultMessage: "Guests",
|
|
})}
|
|
</p>
|
|
</Typography>
|
|
<Typography variant="Body/Paragraph/mdRegular">
|
|
<p>
|
|
{childrenAges.length > 0 ? adultsAndChildrenMsg : adultsOnlyMsg}
|
|
</p>
|
|
</Typography>
|
|
</div>
|
|
{
|
|
<div className={styles.row}>
|
|
<Typography variant="Body/Paragraph/mdBold">
|
|
<p>
|
|
{intl.formatMessage({
|
|
id: "booking.terms",
|
|
defaultMessage: "Terms",
|
|
})}
|
|
</p>
|
|
</Typography>
|
|
<div className={styles.termsLabel}>
|
|
<Typography variant="Body/Paragraph/mdRegular">
|
|
<span>{rateTerm.title}</span>
|
|
</Typography>
|
|
<Modal
|
|
title={rateTerm.title}
|
|
subtitle={rateTerm.paymentTerm}
|
|
trigger={
|
|
<IconButton
|
|
theme="Black"
|
|
style="Muted"
|
|
className={styles.termsInfoIcon}
|
|
>
|
|
<MaterialIcon
|
|
icon="info"
|
|
color="Icon/Default"
|
|
size={20}
|
|
/>
|
|
</IconButton>
|
|
}
|
|
>
|
|
<div className={styles.terms}>
|
|
{rateDefinition.generalTerms.map((term) => (
|
|
<Typography key={term} variant="Body/Paragraph/mdRegular">
|
|
<p className={styles.term}>
|
|
<MaterialIcon
|
|
icon="check"
|
|
color="Icon/Feedback/Success"
|
|
size={20}
|
|
/>
|
|
{term}
|
|
</p>
|
|
</Typography>
|
|
))}
|
|
</div>
|
|
</Modal>
|
|
</div>
|
|
</div>
|
|
}
|
|
{hasModifiableRate(rateDefinition.cancellationRule) && (
|
|
<div className={styles.row}>
|
|
<Typography variant="Body/Paragraph/mdBold">
|
|
<p>
|
|
{intl.formatMessage({
|
|
id: "myStay.modifyBy",
|
|
defaultMessage: "Modify by",
|
|
})}
|
|
</p>
|
|
</Typography>
|
|
|
|
<Typography variant="Body/Paragraph/mdRegular">
|
|
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
|
<p>18:00, {fromDate.format(changeOrCancelDateFormat[lang])}</p>
|
|
</Typography>
|
|
</div>
|
|
)}
|
|
{breakfastPrice !== null && (
|
|
<div className={styles.row}>
|
|
<Typography variant="Body/Paragraph/mdBold">
|
|
<p>
|
|
{intl.formatMessage({
|
|
id: "common.breakfast",
|
|
defaultMessage: "Breakfast",
|
|
})}
|
|
</p>
|
|
</Typography>
|
|
|
|
<Typography variant="Body/Paragraph/mdRegular">
|
|
<p>{breakfastPrice}</p>
|
|
</Typography>
|
|
</div>
|
|
)}
|
|
<Divider />
|
|
<div className={styles.row}>
|
|
<Typography variant="Body/Lead text">
|
|
<p>
|
|
{intl.formatMessage({
|
|
id: "booking.roomTotal",
|
|
defaultMessage: "Room total",
|
|
})}
|
|
</p>
|
|
</Typography>
|
|
<PriceType
|
|
cheques={cheques}
|
|
formattedTotalPrice={formattedTotalPrice}
|
|
isCancelled={isCancelled}
|
|
currencyCode={currencyCode}
|
|
priceType={priceType}
|
|
rateDefinition={rateDefinition}
|
|
totalPoints={totalPoints}
|
|
totalPrice={totalPrice}
|
|
vouchers={vouchers}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
)
|
|
}
|