fix: unite all price details modals to one and align on ui

This commit is contained in:
Simon Emanuelsson
2025-04-15 15:04:11 +02:00
committed by Michael Zetterberg
parent 8152aea649
commit 1f94c581ae
54 changed files with 1926 additions and 746 deletions

View File

@@ -0,0 +1,25 @@
import { Typography } from "@scandic-hotels/design-system/Typography"
import styles from "./row.module.css"
interface RowProps {
label: string
value: string
}
export default function BoldRow({ label, value }: RowProps) {
return (
<tr className={styles.row}>
<td>
<Typography variant="Body/Supporting text (caption)/smBold">
<span>{label}</span>
</Typography>
</td>
<td className={styles.price}>
<Typography variant="Body/Supporting text (caption)/smBold">
<span>{value}</span>
</Typography>
</td>
</tr>
)
}

View File

@@ -0,0 +1,48 @@
"use client"
import { useIntl } from "react-intl"
import DiscountIcon from "@scandic-hotels/design-system/Icons/DiscountIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"
import IconChip from "@/components/TempDesignSystem/IconChip"
import styles from "./row.module.css"
interface BookingCodeRowProps {
bookingCode?: string
}
export default function BookingCodeRow({ bookingCode }: BookingCodeRowProps) {
const intl = useIntl()
if (!bookingCode) {
return null
}
const text = intl.formatMessage(
{ defaultMessage: "<strong>Booking code</strong>: {value}" },
{
value: bookingCode,
strong: (text) => (
<Typography variant="Body/Supporting text (caption)/smBold">
<strong>{text}</strong>
</Typography>
),
}
)
return (
<tr className={styles.row}>
<td colSpan={2} align="left">
<Typography variant="Body/Supporting text (caption)/smRegular">
<IconChip
color="blue"
icon={<DiscountIcon color="Icon/Feedback/Information" />}
>
{text}
</IconChip>
</Typography>
</td>
</tr>
)
}

View File

@@ -0,0 +1,51 @@
"use client"
import { useIntl } from "react-intl"
import { Typography } from "@scandic-hotels/design-system/Typography"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import { formatPrice } from "@/utils/numberFormatting"
import styles from "./row.module.css"
import type { CurrencyEnum } from "@/types/enums/currency"
import type { Package } from "@/types/requests/packages"
interface DiscountedRegularPriceRowProps {
currency: CurrencyEnum
packages: Package[]
regularPrice?: number
}
export default function DiscountedRegularPriceRow({
currency,
packages,
regularPrice,
}: DiscountedRegularPriceRowProps) {
const intl = useIntl()
if (!regularPrice) {
return null
}
const totalPackagesPrice = packages.reduce(
(total, pkg) => total + pkg.localPrice.totalPrice,
0
)
const price = formatPrice(intl, regularPrice + totalPackagesPrice, currency)
return (
<tr className={styles.row}>
<td></td>
<td className={styles.price}>
<Typography variant="Body/Supporting text (caption)/smRegular">
<span>
<s>{price}</s>
</span>
</Typography>
<Caption color="uiTextMediumContrast" striked></Caption>
</td>
</tr>
)
}

View File

@@ -0,0 +1,29 @@
import { Typography } from "@scandic-hotels/design-system/Typography"
interface TrProps {
subtitle?: string
title: string
}
export default function HeaderRow({ subtitle, title }: TrProps) {
return (
<>
<tr>
<th colSpan={2}>
<Typography variant="Body/Paragraph/mdRegular">
<span>{title}</span>
</Typography>
</th>
</tr>
{subtitle ? (
<tr>
<th colSpan={2}>
<Typography variant="Body/Paragraph/mdRegular">
<span>{subtitle}</span>
</Typography>
</th>
</tr>
) : null}
</>
)
}

View File

@@ -0,0 +1,25 @@
import { Typography } from "@scandic-hotels/design-system/Typography"
import styles from "./row.module.css"
interface RowProps {
label: string
value: string
}
export default function LargeRow({ label, value }: RowProps) {
return (
<tr className={styles.row}>
<td>
<Typography variant="Body/Paragraph/mdBold">
<span>{label}</span>
</Typography>
</td>
<td className={styles.price}>
<Typography variant="Body/Paragraph/mdBold">
<span>{value}</span>
</Typography>
</td>
</tr>
)
}

View File

@@ -0,0 +1,31 @@
"use client"
import { useIntl } from "react-intl"
import { formatPrice } from "@/utils/numberFormatting"
import RegularRow from "../Regular"
import type { BedTypeSchema } from "@/types/components/hotelReservation/enterDetails/bedType"
interface BedTypeRowProps {
bedType: BedTypeSchema | undefined
currency?: string
}
export default function BedTypeRow({
bedType,
currency = "",
}: BedTypeRowProps) {
const intl = useIntl()
if (!bedType) {
return null
}
return (
<RegularRow
label={bedType.description}
value={formatPrice(intl, 0, currency)}
/>
)
}

View File

@@ -0,0 +1,83 @@
"use client"
import { useIntl } from "react-intl"
import { sumPackages } from "@/components/HotelReservation/utils"
import { formatPrice } from "@/utils/numberFormatting"
import BoldRow from "../Bold"
import RegularRow from "../Regular"
import BedTypeRow from "./BedType"
import PackagesRow from "./Packages"
import { CurrencyEnum } from "@/types/enums/currency"
import type { SharedPriceRowProps } from "./price"
export interface CorporateChequePriceType {
corporateCheque?: {
additionalPricePerStay?: number
currency?: CurrencyEnum
numberOfCheques: number
}
}
interface CorporateChequePriceProps extends SharedPriceRowProps {
currency: string
nights: number
price: CorporateChequePriceType["corporateCheque"]
}
export default function CorporateChequePrice({
bedType,
currency,
nights,
packages,
price,
}: CorporateChequePriceProps) {
const intl = useIntl()
if (!price) {
return null
}
const averagePriceTitle = intl.formatMessage({
defaultMessage: "Average price per night",
})
const pkgsSum = sumPackages(packages)
const roomAdditionalPrice = price.additionalPricePerStay
let additionalPricePerStay
if (roomAdditionalPrice) {
additionalPricePerStay = roomAdditionalPrice + pkgsSum.price
} else if (pkgsSum.price) {
additionalPricePerStay = pkgsSum.price
}
const averageChequesPerNight = price.numberOfCheques / nights
const averageAdditionalPricePerNight = roomAdditionalPrice
? Math.ceil(roomAdditionalPrice / nights)
: null
const additionalCurrency = price.currency ?? pkgsSum.currency
let averagePricePerNight = `${averageChequesPerNight} ${CurrencyEnum.CC}`
if (averageAdditionalPricePerNight) {
averagePricePerNight = `${averagePricePerNight} + ${averageAdditionalPricePerNight} ${additionalCurrency}`
}
return (
<>
<RegularRow label={averagePriceTitle} value={averagePricePerNight} />
<BedTypeRow bedType={bedType} currency={currency} />
<PackagesRow packages={packages} />
<BoldRow
label={intl.formatMessage({ defaultMessage: "Room charge" })}
value={formatPrice(
intl,
price.numberOfCheques,
CurrencyEnum.CC,
additionalPricePerStay,
additionalCurrency
)}
/>
</>
)
}

View File

@@ -0,0 +1,32 @@
"use client"
import { useIntl } from "react-intl"
import { formatPrice } from "@/utils/numberFormatting"
import RegularRow from "../Regular"
import type { Packages as PackagesType } from "@/types/requests/packages"
interface PackagesProps {
packages: PackagesType | null
}
export default function PackagesRow({ packages }: PackagesProps) {
const intl = useIntl()
if (!packages || !packages.length) {
return null
}
return packages?.map((pkg) => (
<RegularRow
key={pkg.code}
label={pkg.description}
value={formatPrice(
intl,
+pkg.localPrice.totalPrice,
pkg.localPrice.currency
)}
/>
))
}

View File

@@ -0,0 +1,83 @@
"use client"
import { useIntl } from "react-intl"
import { sumPackages } from "@/components/HotelReservation/utils"
import { formatPrice } from "@/utils/numberFormatting"
import BoldRow from "../Bold"
import RegularRow from "../Regular"
import BedTypeRow from "./BedType"
import PackagesRow from "./Packages"
import { CurrencyEnum } from "@/types/enums/currency"
import type { SharedPriceRowProps } from "./price"
export interface RedemptionPriceType {
redemption?: {
additionalPricePerStay?: number
currency?: CurrencyEnum
pointsPerNight: number
pointsPerStay: number
}
}
interface RedemptionPriceProps extends SharedPriceRowProps {
currency: string
nights: number
price: RedemptionPriceType["redemption"]
}
export default function RedemptionPrice({
bedType,
currency,
nights,
packages,
price,
}: RedemptionPriceProps) {
const intl = useIntl()
if (!price) {
return null
}
const averagePriceTitle = intl.formatMessage({
defaultMessage: "Average price per night",
})
const pkgsSum = sumPackages(packages)
const roomAdditionalPrice = price.additionalPricePerStay
let additionalPricePerStay
if (roomAdditionalPrice) {
additionalPricePerStay = roomAdditionalPrice + pkgsSum.price
} else if (pkgsSum.price) {
additionalPricePerStay = pkgsSum.price
}
const averageAdditionalPricePerNight = roomAdditionalPrice
? Math.ceil(roomAdditionalPrice / nights)
: null
const additionalCurrency = price.currency ?? pkgsSum.currency
let averagePricePerNight = `${price.pointsPerNight} ${CurrencyEnum.POINTS}`
if (averageAdditionalPricePerNight) {
averagePricePerNight = `${averagePricePerNight} + ${averageAdditionalPricePerNight} ${additionalCurrency}`
}
return (
<>
<RegularRow label={averagePriceTitle} value={averagePricePerNight} />
<BedTypeRow bedType={bedType} currency={currency} />
<PackagesRow packages={packages} />
<BoldRow
label={intl.formatMessage({ defaultMessage: "Room charge" })}
value={formatPrice(
intl,
price.pointsPerStay,
CurrencyEnum.POINTS,
additionalPricePerStay,
additionalCurrency
)}
/>
</>
)
}

View File

@@ -0,0 +1,67 @@
"use client"
import { useIntl } from "react-intl"
import { sumPackages } from "@/components/HotelReservation/utils"
import { formatPrice } from "@/utils/numberFormatting"
import BoldRow from "../Bold"
import RegularRow from "../Regular"
import BedTypeRow from "./BedType"
import PackagesRow from "./Packages"
import type { CurrencyEnum } from "@/types/enums/currency"
import type { SharedPriceRowProps } from "./price"
export interface RegularPriceType {
regular?: {
currency: CurrencyEnum
pricePerNight: number
pricePerStay: number
}
}
interface RegularPriceProps extends SharedPriceRowProps {
price: RegularPriceType["regular"]
}
export default function RegularPrice({
bedType,
packages,
price,
}: RegularPriceProps) {
const intl = useIntl()
if (!price) {
return null
}
const averagePriceTitle = intl.formatMessage({
defaultMessage: "Average price per night",
})
const avgeragePricePerNight = formatPrice(
intl,
price.pricePerNight,
price.currency
)
const pkgs = sumPackages(packages)
const roomCharge = formatPrice(
intl,
price.pricePerStay + pkgs.price,
price.currency
)
return (
<>
<RegularRow label={averagePriceTitle} value={avgeragePricePerNight} />
<BedTypeRow bedType={bedType} currency={price.currency} />
<PackagesRow packages={packages} />
<BoldRow
label={intl.formatMessage({ defaultMessage: "Room charge" })}
value={roomCharge}
/>
</>
)
}

View File

@@ -0,0 +1,76 @@
"use client"
import { useIntl } from "react-intl"
import { sumPackages } from "@/components/HotelReservation/utils"
import { formatPrice } from "@/utils/numberFormatting"
import BoldRow from "../Bold"
import RegularRow from "../Regular"
import BedTypeRow from "./BedType"
import PackagesRow from "./Packages"
import { CurrencyEnum } from "@/types/enums/currency"
import type { SharedPriceRowProps } from "./price"
export interface VoucherPriceType {
voucher?: {
numberOfVouchers: number
}
}
interface VoucherPriceProps extends SharedPriceRowProps {
currency: string
nights: number
price: VoucherPriceType["voucher"]
}
export default function VoucherPrice({
bedType,
currency,
nights,
packages,
price,
}: VoucherPriceProps) {
const intl = useIntl()
if (!price) {
return null
}
const averagePriceTitle = intl.formatMessage({
defaultMessage: "Average price per night",
})
const pkgsSum = sumPackages(packages)
let additionalPricePerStay
if (pkgsSum.price) {
additionalPricePerStay = pkgsSum.price
}
const averageAdditionalPricePerNight = additionalPricePerStay
? Math.ceil(additionalPricePerStay / nights)
: null
let averagePricePerNight = `${price.numberOfVouchers} ${CurrencyEnum.Voucher}`
if (averageAdditionalPricePerNight) {
averagePricePerNight = `${averagePricePerNight} + ${averageAdditionalPricePerNight} ${pkgsSum.currency}`
}
return (
<>
<RegularRow label={averagePriceTitle} value={averagePricePerNight} />
<BedTypeRow bedType={bedType} currency={currency} />
<PackagesRow packages={packages} />
<BoldRow
label={intl.formatMessage({ defaultMessage: "Room charge" })}
value={formatPrice(
intl,
price.numberOfVouchers,
CurrencyEnum.Voucher,
additionalPricePerStay,
pkgsSum.currency
)}
/>
</>
)
}

View File

@@ -0,0 +1,7 @@
import type { BedTypeSchema } from "@/types/components/hotelReservation/enterDetails/bedType"
import type { Packages } from "@/types/requests/packages"
export interface SharedPriceRowProps {
bedType: BedTypeSchema | undefined
packages: Packages | null
}

View File

@@ -0,0 +1,25 @@
import { Typography } from "@scandic-hotels/design-system/Typography"
import styles from "./row.module.css"
interface RowProps {
label: string
value: string
}
export default function RegularRow({ label, value }: RowProps) {
return (
<tr className={styles.row}>
<td>
<Typography variant="Body/Supporting text (caption)/smRegular">
<span>{label}</span>
</Typography>
</td>
<td className={styles.price}>
<Typography variant="Body/Supporting text (caption)/smRegular">
<span>{value}</span>
</Typography>
</td>
</tr>
)
}

View File

@@ -0,0 +1,46 @@
"use client"
import { useIntl } from "react-intl"
import { formatPrice } from "@/utils/numberFormatting"
import RegularRow from "./Regular"
import type { Price } from "@/types/components/hotelReservation/price"
import { CurrencyEnum } from "@/types/enums/currency"
interface VatProps {
totalPrice: Price
vat: number
}
const noVatCurrencies = [
CurrencyEnum.CC,
CurrencyEnum.POINTS,
CurrencyEnum.Voucher,
]
export default function VatRow({ totalPrice, vat }: VatProps) {
const intl = useIntl()
if (noVatCurrencies.includes(totalPrice.local.currency)) {
return null
}
const vatPercentage = vat / 100
const vatAmount = totalPrice.local.price * vatPercentage
const priceExclVat = totalPrice.local.price - vatAmount
return (
<>
<RegularRow
label={intl.formatMessage({ defaultMessage: "Price excluding VAT" })}
value={formatPrice(intl, priceExclVat, totalPrice.local.currency)}
/>
<RegularRow
label={intl.formatMessage({ defaultMessage: "VAT {vat}%" }, { vat })}
value={formatPrice(intl, vatAmount, totalPrice.local.currency)}
/>
</>
)
}

View File

@@ -0,0 +1,8 @@
.row {
display: flex;
justify-content: space-between;
}
.price {
text-align: end;
}