Merged in fix/SW-1631-rate-terms-modal (pull request #1699)
fix(SW-1631): add rate terms modal * fix(SW-1631): add rate terms modal Approved-by: Simon.Emanuelsson
This commit is contained in:
@@ -60,6 +60,24 @@ export default function Campaign({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rateTermDetails = product.rateDefinitionMember
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
title: product.rateDefinition.title,
|
||||||
|
terms: product.rateDefinition.generalTerms,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: product.rateDefinitionMember.title,
|
||||||
|
terms: product.rateDefinition.generalTerms,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
title: product.rateDefinition.title,
|
||||||
|
terms: product.rateDefinition.generalTerms,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
const isSelected = isSelectedPriceProduct(
|
const isSelected = isSelectedPriceProduct(
|
||||||
product,
|
product,
|
||||||
selectedRate,
|
selectedRate,
|
||||||
@@ -149,6 +167,7 @@ export default function Campaign({
|
|||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
rateTermDetails={rateTermDetails}
|
||||||
value={product.public.rateCode}
|
value={product.public.rateCode}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -43,6 +43,23 @@ export default function Code({
|
|||||||
} else {
|
} else {
|
||||||
bannerText = `${bookingCode} ∙ ${intl.formatMessage({ id: "Breakfast excluded" })}`
|
bannerText = `${bookingCode} ∙ ${intl.formatMessage({ id: "Breakfast excluded" })}`
|
||||||
}
|
}
|
||||||
|
const rateTermDetails = product.rateDefinitionMember
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
title: product.rateDefinition.title,
|
||||||
|
terms: product.rateDefinition.generalTerms,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: product.rateDefinitionMember.title,
|
||||||
|
terms: product.rateDefinition.generalTerms,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
title: product.rateDefinition.title,
|
||||||
|
terms: product.rateDefinition.generalTerms,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
if ("corporateCheque" in product) {
|
if ("corporateCheque" in product) {
|
||||||
const { localPrice, rateCode } = product.corporateCheque
|
const { localPrice, rateCode } = product.corporateCheque
|
||||||
@@ -71,6 +88,7 @@ export default function Code({
|
|||||||
unit: localPrice.currency ?? "",
|
unit: localPrice.currency ?? "",
|
||||||
}}
|
}}
|
||||||
rateTitle={rateTitles[product.rate].title}
|
rateTitle={rateTitles[product.rate].title}
|
||||||
|
rateTermDetails={rateTermDetails}
|
||||||
value={rateCode}
|
value={rateCode}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -93,6 +111,7 @@ export default function Code({
|
|||||||
unit: intl.formatMessage({ id: "Voucher" }).toUpperCase(),
|
unit: intl.formatMessage({ id: "Voucher" }).toUpperCase(),
|
||||||
}}
|
}}
|
||||||
rateTitle={rateTitles[product.rate].title}
|
rateTitle={rateTitles[product.rate].title}
|
||||||
|
rateTermDetails={rateTermDetails}
|
||||||
value={rateCode}
|
value={rateCode}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -151,6 +170,7 @@ export default function Code({
|
|||||||
unit: `${localPrice.currency}/${night}`,
|
unit: `${localPrice.currency}/${night}`,
|
||||||
}}
|
}}
|
||||||
rateTitle={rateTitles[product.rate].title}
|
rateTitle={rateTitles[product.rate].title}
|
||||||
|
rateTermDetails={rateTermDetails}
|
||||||
value={rateCode}
|
value={rateCode}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -76,6 +76,13 @@ export default function Redemptions({
|
|||||||
? `${rewardNight} ∙ ${breakfastIncluded}`
|
? `${rewardNight} ∙ ${breakfastIncluded}`
|
||||||
: `${rewardNight} ∙ ${breakfastExcluded}`
|
: `${rewardNight} ∙ ${breakfastExcluded}`
|
||||||
|
|
||||||
|
const rateTermDetails = [
|
||||||
|
{
|
||||||
|
title: rateTitles[firstRedemption.rate].title,
|
||||||
|
terms: firstRedemption.rateDefinition.generalTerms,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PointsRateCard
|
<PointsRateCard
|
||||||
key={firstRedemption.rate}
|
key={firstRedemption.rate}
|
||||||
@@ -84,6 +91,7 @@ export default function Redemptions({
|
|||||||
paymentTerm={rateTitles[firstRedemption.rate].paymentTerm}
|
paymentTerm={rateTitles[firstRedemption.rate].paymentTerm}
|
||||||
rates={rates}
|
rates={rates}
|
||||||
rateTitle={rateTitles[firstRedemption.rate].title}
|
rateTitle={rateTitles[firstRedemption.rate].title}
|
||||||
|
rateTermDetails={rateTermDetails}
|
||||||
selectedRate={selectedRateCode}
|
selectedRate={selectedRateCode}
|
||||||
isNotEnoughPoints={notEnoughPoints}
|
isNotEnoughPoints={notEnoughPoints}
|
||||||
notEnoughPointsText={intl.formatMessage({ id: "Not enough points" })}
|
notEnoughPointsText={intl.formatMessage({ id: "Not enough points" })}
|
||||||
|
|||||||
@@ -145,6 +145,24 @@ export default function Regular({
|
|||||||
roomTypeCode
|
roomTypeCode
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const rateTermDetails = product.rateDefinitionMember
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
title: product.rateDefinition.title,
|
||||||
|
terms: product.rateDefinition.generalTerms,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: product.rateDefinitionMember.title,
|
||||||
|
terms: product.rateDefinition.generalTerms,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
title: product.rateDefinition.title,
|
||||||
|
terms: product.rateDefinition.generalTerms,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RegularRateCard
|
<RegularRateCard
|
||||||
{...rates}
|
{...rates}
|
||||||
@@ -157,6 +175,7 @@ export default function Regular({
|
|||||||
paymentTerm={rateTitles[product.rate].paymentTerm}
|
paymentTerm={rateTitles[product.rate].paymentTerm}
|
||||||
rateTitle={rateTitles[product.rate].title}
|
rateTitle={rateTitles[product.rate].title}
|
||||||
value={rateCode}
|
value={rateCode}
|
||||||
|
rateTermDetails={rateTermDetails}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ export default function Rates({
|
|||||||
petRoomPackageSelected && petRoomPackage ? petRoomPackage : undefined,
|
petRoomPackageSelected && petRoomPackage ? petRoomPackage : undefined,
|
||||||
roomTypeCode,
|
roomTypeCode,
|
||||||
}
|
}
|
||||||
|
|
||||||
const showAllRates = selectedFilter === BookingCodeFilterEnum.All
|
const showAllRates = selectedFilter === BookingCodeFilterEnum.All
|
||||||
const hasBookingCodeRates = !!(campaign.length || code.length)
|
const hasBookingCodeRates = !!(campaign.length || code.length)
|
||||||
const hasRegularRates = !!regular.length
|
const hasRegularRates = !!regular.length
|
||||||
|
|||||||
@@ -338,12 +338,15 @@ export const roomsAvailabilitySchema = z
|
|||||||
...product,
|
...product,
|
||||||
public: null,
|
public: null,
|
||||||
})
|
})
|
||||||
if (rateDetailsMember) {
|
|
||||||
breakfastIncludedMember.push(
|
|
||||||
rateDetailsMember.breakfastIncluded
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (rateDetails && rateCode) {
|
if (rateDetails && rateCode) {
|
||||||
|
if (rateDetailsMember) {
|
||||||
|
breakfastIncludedMember.push(
|
||||||
|
rateDetailsMember.breakfastIncluded
|
||||||
|
)
|
||||||
|
rateDetails.rateDefinitionMember =
|
||||||
|
rateDetailsMember.rateDefinition
|
||||||
|
}
|
||||||
const rateDefinition = findRateDefintion(rateCode)
|
const rateDefinition = findRateDefintion(rateCode)
|
||||||
if (rateDefinition) {
|
if (rateDefinition) {
|
||||||
switch (rateDefinition.rateType) {
|
switch (rateDefinition.rateType) {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const baseProductSchema = z.object({
|
|||||||
breakfastIncluded: z.boolean().default(false),
|
breakfastIncluded: z.boolean().default(false),
|
||||||
// Used to set the rate that we use to chose titles etc.
|
// Used to set the rate that we use to chose titles etc.
|
||||||
rate: z.enum(["change", "flex", "save"]).default("save"),
|
rate: z.enum(["change", "flex", "save"]).default("save"),
|
||||||
rateDefinition: rateDefinitionSchema.nullish().transform((val) =>
|
rateDefinition: rateDefinitionSchema.optional().transform((val) =>
|
||||||
val
|
val
|
||||||
? val
|
? val
|
||||||
: {
|
: {
|
||||||
@@ -36,6 +36,7 @@ const baseProductSchema = z.object({
|
|||||||
title: "",
|
title: "",
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
rateDefinitionMember: rateDefinitionSchema.optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
function mapBaseProduct(baseProduct: typeof baseProductSchema._type) {
|
function mapBaseProduct(baseProduct: typeof baseProductSchema._type) {
|
||||||
@@ -44,6 +45,7 @@ function mapBaseProduct(baseProduct: typeof baseProductSchema._type) {
|
|||||||
breakfastIncluded: baseProduct.breakfastIncluded,
|
breakfastIncluded: baseProduct.breakfastIncluded,
|
||||||
rate: baseProduct.rate,
|
rate: baseProduct.rate,
|
||||||
rateDefinition: baseProduct.rateDefinition,
|
rateDefinition: baseProduct.rateDefinition,
|
||||||
|
rateDefinitionMember: baseProduct.rateDefinitionMember,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,12 +100,14 @@ export const redemptionsProduct = z
|
|||||||
breakfastIncluded,
|
breakfastIncluded,
|
||||||
rate,
|
rate,
|
||||||
rateDefinition,
|
rateDefinition,
|
||||||
|
rateDefinitionMember,
|
||||||
...redemption
|
...redemption
|
||||||
}) => ({
|
}) => ({
|
||||||
bookingCode,
|
bookingCode,
|
||||||
breakfastIncluded,
|
breakfastIncluded,
|
||||||
rate,
|
rate,
|
||||||
rateDefinition,
|
rateDefinition,
|
||||||
|
rateDefinitionMember,
|
||||||
redemption,
|
redemption,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { nullableStringValidator } from "@/utils/zod/stringValidator"
|
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { nullableStringValidator } from "@/utils/zod/stringValidator"
|
||||||
|
|
||||||
export const rateDefinitionSchema = z.object({
|
export const rateDefinitionSchema = z.object({
|
||||||
breakfastIncluded: z.boolean(),
|
breakfastIncluded: z.boolean(),
|
||||||
cancellationRule: z.string(),
|
cancellationRule: z.string(),
|
||||||
|
|||||||
@@ -8,11 +8,9 @@ export interface RatesProps {
|
|||||||
roomConfiguration: RoomConfiguration
|
roomConfiguration: RoomConfiguration
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SharedRateCardProps extends Pick<
|
export interface SharedRateCardProps
|
||||||
RoomConfiguration,
|
extends Pick<RoomConfiguration, "roomTypeCode"> {
|
||||||
"roomTypeCode"
|
|
||||||
> {
|
|
||||||
handleSelectRate: (product: Product) => void
|
handleSelectRate: (product: Product) => void
|
||||||
nights: number
|
nights: number
|
||||||
petRoomPackage: NonNullable<Packages>[number] | undefined
|
petRoomPackage: NonNullable<Packages>[number] | undefined
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const meta: Meta<typeof CampaignRateCard> = {
|
|||||||
omnibusRate: { control: 'object' },
|
omnibusRate: { control: 'object' },
|
||||||
comparisonRate: { control: 'object' },
|
comparisonRate: { control: 'object' },
|
||||||
approximateRate: { control: 'object' },
|
approximateRate: { control: 'object' },
|
||||||
|
rateTermDetails: { control: 'object' },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,6 +48,12 @@ export const Default: Story = {
|
|||||||
label: 'Lowest past price (last 30 days)',
|
label: 'Lowest past price (last 30 days)',
|
||||||
unit: 'EUR',
|
unit: 'EUR',
|
||||||
},
|
},
|
||||||
|
rateTermDetails: [
|
||||||
|
{
|
||||||
|
title: 'Rate definition 1',
|
||||||
|
terms: ['term 1', 'term 2', 'term 3'],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,6 +72,12 @@ export const Package: Story = {
|
|||||||
label: 'Approx.',
|
label: 'Approx.',
|
||||||
unit: 'EUR',
|
unit: 'EUR',
|
||||||
},
|
},
|
||||||
|
rateTermDetails: [
|
||||||
|
{
|
||||||
|
title: 'Rate definition 1',
|
||||||
|
terms: ['term 1', 'term 2', 'term 3'],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +101,12 @@ export const CampaignLoggedIn: Story = {
|
|||||||
unit: 'EUR/NIGHT',
|
unit: 'EUR/NIGHT',
|
||||||
},
|
},
|
||||||
isHighlightedRate: true,
|
isHighlightedRate: true,
|
||||||
|
rateTermDetails: [
|
||||||
|
{
|
||||||
|
title: 'Rate definition 1',
|
||||||
|
terms: ['term 1', 'term 2', 'term 3'],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,5 +135,11 @@ export const CampaignOmnibus: Story = {
|
|||||||
label: 'Lowest past price (last 30 days)',
|
label: 'Lowest past price (last 30 days)',
|
||||||
unit: 'EUR',
|
unit: 'EUR',
|
||||||
},
|
},
|
||||||
|
rateTermDetails: [
|
||||||
|
{
|
||||||
|
title: 'Rate definition 1',
|
||||||
|
terms: ['term 1', 'term 2', 'term 3'],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { Typography } from '../../Typography'
|
import { Typography } from '../../Typography'
|
||||||
import { Rate } from '../types'
|
import { Rate, RateTermDetails } from '../types'
|
||||||
|
|
||||||
import styles from '../rate-card.module.css'
|
import styles from '../rate-card.module.css'
|
||||||
import { Button } from '../../Button'
|
import { Button } from '../../Button'
|
||||||
import { variants } from '../variants'
|
import { variants } from '../variants'
|
||||||
import { MaterialIcon } from '../../Icons'
|
import { MaterialIcon } from '../../Icons'
|
||||||
|
import Modal from '../Modal'
|
||||||
|
|
||||||
interface CampaignRateCardProps {
|
interface CampaignRateCardProps {
|
||||||
name: string
|
name: string
|
||||||
@@ -21,6 +22,7 @@ interface CampaignRateCardProps {
|
|||||||
approximateRate?: Rate
|
approximateRate?: Rate
|
||||||
handleChange: () => void
|
handleChange: () => void
|
||||||
handleTermsClick?: () => void
|
handleTermsClick?: () => void
|
||||||
|
rateTermDetails: RateTermDetails[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CampaignRateCard({
|
export default function CampaignRateCard({
|
||||||
@@ -37,7 +39,7 @@ export default function CampaignRateCard({
|
|||||||
bannerText,
|
bannerText,
|
||||||
isHighlightedRate,
|
isHighlightedRate,
|
||||||
handleChange,
|
handleChange,
|
||||||
handleTermsClick,
|
rateTermDetails,
|
||||||
}: CampaignRateCardProps) {
|
}: CampaignRateCardProps) {
|
||||||
const classNames = variants({
|
const classNames = variants({
|
||||||
variant: 'Campaign',
|
variant: 'Campaign',
|
||||||
@@ -61,14 +63,40 @@ export default function CampaignRateCard({
|
|||||||
<header>
|
<header>
|
||||||
<Typography variant="Tag/sm">
|
<Typography variant="Tag/sm">
|
||||||
<h3 className={styles.title}>
|
<h3 className={styles.title}>
|
||||||
<Button
|
<Modal
|
||||||
variant="Icon"
|
title={rateTitle}
|
||||||
color="IconDefault"
|
subtitle={paymentTerm}
|
||||||
size="Small"
|
trigger={
|
||||||
onPress={handleTermsClick}
|
<Button variant="Icon" color="IconDefault" size="Small">
|
||||||
|
<MaterialIcon
|
||||||
|
icon="info"
|
||||||
|
size={20}
|
||||||
|
color="CurrentColor"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<MaterialIcon icon="info" size={20} color="CurrentColor" />
|
{rateTermDetails.map((termGroup) => (
|
||||||
</Button>
|
<div key={termGroup.title} className={styles.terms}>
|
||||||
|
<Typography variant={'Body/Paragraph/mdBold'}>
|
||||||
|
<p>{termGroup.title}</p>
|
||||||
|
</Typography>
|
||||||
|
{termGroup.terms.map((term) => (
|
||||||
|
<Typography key={term}>
|
||||||
|
<p className={styles.term}>
|
||||||
|
<MaterialIcon
|
||||||
|
icon="check"
|
||||||
|
color="Icon/Feedback/Success"
|
||||||
|
size={20}
|
||||||
|
className={styles.termsIcon}
|
||||||
|
/>
|
||||||
|
{term}
|
||||||
|
</p>
|
||||||
|
</Typography>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Modal>
|
||||||
{rateTitle}
|
{rateTitle}
|
||||||
<span className={styles.textSecondary}>
|
<span className={styles.textSecondary}>
|
||||||
{` / ${paymentTerm}`}
|
{` / ${paymentTerm}`}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const meta: Meta<typeof CodeRateCard> = {
|
|||||||
paymentTerm: { control: 'text' },
|
paymentTerm: { control: 'text' },
|
||||||
rate: { control: 'object' },
|
rate: { control: 'object' },
|
||||||
approximateRate: { control: 'object' },
|
approximateRate: { control: 'object' },
|
||||||
|
rateTermDetails: { contorlr: 'object' },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,6 +39,12 @@ export const Default: Story = {
|
|||||||
label: 'Approx.',
|
label: 'Approx.',
|
||||||
unit: 'EUR',
|
unit: 'EUR',
|
||||||
},
|
},
|
||||||
|
rateTermDetails: [
|
||||||
|
{
|
||||||
|
title: 'Rate definition 1',
|
||||||
|
terms: ['term 1', 'term 2', 'term 3'],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,6 +58,12 @@ export const Voucher: Story = {
|
|||||||
price: '1',
|
price: '1',
|
||||||
unit: 'VOUCHER',
|
unit: 'VOUCHER',
|
||||||
},
|
},
|
||||||
|
rateTermDetails: [
|
||||||
|
{
|
||||||
|
title: 'Rate definition 1',
|
||||||
|
terms: ['term 1', 'term 2', 'term 3'],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +82,12 @@ export const CorporateCheck: Story = {
|
|||||||
label: 'Approx.',
|
label: 'Approx.',
|
||||||
unit: 'EUR',
|
unit: 'EUR',
|
||||||
},
|
},
|
||||||
|
rateTermDetails: [
|
||||||
|
{
|
||||||
|
title: 'Rate definition 1',
|
||||||
|
terms: ['term 1', 'term 2', 'term 3'],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,6 +106,12 @@ export const DNumberDefault: Story = {
|
|||||||
label: 'Approx.',
|
label: 'Approx.',
|
||||||
unit: 'EUR',
|
unit: 'EUR',
|
||||||
},
|
},
|
||||||
|
rateTermDetails: [
|
||||||
|
{
|
||||||
|
title: 'Rate definition 1',
|
||||||
|
terms: ['term 1', 'term 2', 'term 3'],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,6 +131,12 @@ export const DNumberHighlightedRate: Story = {
|
|||||||
unit: 'EUR',
|
unit: 'EUR',
|
||||||
},
|
},
|
||||||
isHighlightedRate: true,
|
isHighlightedRate: true,
|
||||||
|
rateTermDetails: [
|
||||||
|
{
|
||||||
|
title: 'Rate definition 1',
|
||||||
|
terms: ['term 1', 'term 2', 'term 3'],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,6 +155,12 @@ export const LNumberDefault: Story = {
|
|||||||
label: 'Approx.',
|
label: 'Approx.',
|
||||||
unit: 'EUR',
|
unit: 'EUR',
|
||||||
},
|
},
|
||||||
|
rateTermDetails: [
|
||||||
|
{
|
||||||
|
title: 'Rate definition 1',
|
||||||
|
terms: ['term 1', 'term 2', 'term 3'],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,5 +183,11 @@ export const LNumberStrikethrough: Story = {
|
|||||||
label: 'Approx.',
|
label: 'Approx.',
|
||||||
unit: 'EUR',
|
unit: 'EUR',
|
||||||
},
|
},
|
||||||
|
rateTermDetails: [
|
||||||
|
{
|
||||||
|
title: 'Rate definition 1',
|
||||||
|
terms: ['term 1', 'term 2', 'term 3'],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { Rate } from '../types'
|
import { Rate, RateTermDetails } from '../types'
|
||||||
|
|
||||||
import styles from '../rate-card.module.css'
|
import styles from '../rate-card.module.css'
|
||||||
import { Typography } from '../../Typography'
|
import { Typography } from '../../Typography'
|
||||||
import { Button } from '../../Button'
|
import { Button } from '../../Button'
|
||||||
import { variants } from '../variants'
|
import { variants } from '../variants'
|
||||||
import { MaterialIcon } from '../../Icons'
|
import { MaterialIcon } from '../../Icons'
|
||||||
|
import Modal from '../Modal'
|
||||||
|
|
||||||
interface CodeRateCardProps {
|
interface CodeRateCardProps {
|
||||||
name: string
|
name: string
|
||||||
@@ -19,6 +20,7 @@ interface CodeRateCardProps {
|
|||||||
isHighlightedRate?: boolean
|
isHighlightedRate?: boolean
|
||||||
handleChange: () => void
|
handleChange: () => void
|
||||||
handleTermsClick?: () => void
|
handleTermsClick?: () => void
|
||||||
|
rateTermDetails: RateTermDetails[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CodeRateCard({
|
export default function CodeRateCard({
|
||||||
@@ -33,7 +35,7 @@ export default function CodeRateCard({
|
|||||||
bannerText,
|
bannerText,
|
||||||
isHighlightedRate,
|
isHighlightedRate,
|
||||||
handleChange,
|
handleChange,
|
||||||
handleTermsClick,
|
rateTermDetails,
|
||||||
}: CodeRateCardProps) {
|
}: CodeRateCardProps) {
|
||||||
const classNames = variants({
|
const classNames = variants({
|
||||||
variant: 'Code',
|
variant: 'Code',
|
||||||
@@ -57,14 +59,40 @@ export default function CodeRateCard({
|
|||||||
<header>
|
<header>
|
||||||
<Typography variant="Tag/sm">
|
<Typography variant="Tag/sm">
|
||||||
<h3 className={styles.title}>
|
<h3 className={styles.title}>
|
||||||
<Button
|
<Modal
|
||||||
variant="Icon"
|
title={rateTitle}
|
||||||
color="IconDefault"
|
subtitle={paymentTerm}
|
||||||
size="Small"
|
trigger={
|
||||||
onPress={handleTermsClick}
|
<Button variant="Icon" color="IconDefault" size="Small">
|
||||||
|
<MaterialIcon
|
||||||
|
icon="info"
|
||||||
|
size={20}
|
||||||
|
color="CurrentColor"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<MaterialIcon icon="info" size={20} color="CurrentColor" />
|
{rateTermDetails.map((termGroup) => (
|
||||||
</Button>
|
<div key={termGroup.title} className={styles.terms}>
|
||||||
|
<Typography variant={'Body/Paragraph/mdBold'}>
|
||||||
|
<p>{termGroup.title}</p>
|
||||||
|
</Typography>
|
||||||
|
{termGroup.terms.map((term) => (
|
||||||
|
<Typography key={term}>
|
||||||
|
<p className={styles.term}>
|
||||||
|
<MaterialIcon
|
||||||
|
icon="check"
|
||||||
|
color="Icon/Feedback/Success"
|
||||||
|
size={20}
|
||||||
|
className={styles.termsIcon}
|
||||||
|
/>
|
||||||
|
{term}
|
||||||
|
</p>
|
||||||
|
</Typography>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Modal>
|
||||||
{rateTitle}
|
{rateTitle}
|
||||||
<span className={styles.textSecondary}>
|
<span className={styles.textSecondary}>
|
||||||
{` / ${paymentTerm}`}
|
{` / ${paymentTerm}`}
|
||||||
|
|||||||
174
packages/design-system/lib/components/RateCard/Modal/index.tsx
Normal file
174
packages/design-system/lib/components/RateCard/Modal/index.tsx
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { motion } from 'framer-motion'
|
||||||
|
import { type PropsWithChildren, useEffect, useState } from 'react'
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogTrigger,
|
||||||
|
Modal as AriaModal,
|
||||||
|
ModalOverlay,
|
||||||
|
} from 'react-aria-components'
|
||||||
|
|
||||||
|
import {
|
||||||
|
type AnimationState,
|
||||||
|
AnimationStateEnum,
|
||||||
|
type InnerModalProps,
|
||||||
|
type ModalProps,
|
||||||
|
} from './modal'
|
||||||
|
import { fade, slideInOut } from './motionVariants'
|
||||||
|
|
||||||
|
import styles from './modal.module.css'
|
||||||
|
import { Typography } from '../../Typography'
|
||||||
|
import { MaterialIcon } from '../../Icons'
|
||||||
|
|
||||||
|
const MotionOverlay = motion(ModalOverlay)
|
||||||
|
const MotionModal = motion(AriaModal)
|
||||||
|
|
||||||
|
function InnerModal({
|
||||||
|
animation,
|
||||||
|
onAnimationComplete = () => undefined,
|
||||||
|
setAnimation,
|
||||||
|
onToggle,
|
||||||
|
isOpen,
|
||||||
|
children,
|
||||||
|
title,
|
||||||
|
subtitle,
|
||||||
|
hideHeader,
|
||||||
|
}: PropsWithChildren<InnerModalProps>) {
|
||||||
|
function modalStateHandler(newAnimationState: AnimationState) {
|
||||||
|
setAnimation((currentAnimationState) =>
|
||||||
|
newAnimationState === AnimationStateEnum.hidden &&
|
||||||
|
currentAnimationState === AnimationStateEnum.hidden
|
||||||
|
? AnimationStateEnum.unmounted
|
||||||
|
: currentAnimationState
|
||||||
|
)
|
||||||
|
if (newAnimationState === AnimationStateEnum.visible) {
|
||||||
|
onAnimationComplete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onOpenChange(state: boolean) {
|
||||||
|
onToggle!(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MotionOverlay
|
||||||
|
animate={animation}
|
||||||
|
className={styles.overlay}
|
||||||
|
initial={'hidden'}
|
||||||
|
isDismissable
|
||||||
|
isExiting={animation === AnimationStateEnum.hidden}
|
||||||
|
onAnimationComplete={modalStateHandler}
|
||||||
|
variants={fade}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onOpenChange={onOpenChange}
|
||||||
|
>
|
||||||
|
<MotionModal
|
||||||
|
className={styles.modal}
|
||||||
|
variants={slideInOut}
|
||||||
|
animate={animation}
|
||||||
|
initial={'hidden'}
|
||||||
|
>
|
||||||
|
<Dialog className={styles.dialog}>
|
||||||
|
{({ close }) => (
|
||||||
|
<>
|
||||||
|
{!hideHeader && (
|
||||||
|
<header className={styles.header}>
|
||||||
|
<div>
|
||||||
|
{title && (
|
||||||
|
<Typography variant="Title/Subtitle/lg">
|
||||||
|
<p>{title}</p>
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
{subtitle && (
|
||||||
|
<Typography variant="Body/Lead text">
|
||||||
|
<span>{subtitle}</span>
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={close}
|
||||||
|
type="button"
|
||||||
|
className={styles.close}
|
||||||
|
>
|
||||||
|
<MaterialIcon icon="close" color="Icon/Feedback/Neutral" />
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<section className={styles.content}>{children}</section>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Dialog>
|
||||||
|
</MotionModal>
|
||||||
|
</MotionOverlay>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Modal({
|
||||||
|
onAnimationComplete = () => undefined,
|
||||||
|
trigger,
|
||||||
|
isOpen,
|
||||||
|
onToggle,
|
||||||
|
title,
|
||||||
|
subtitle,
|
||||||
|
children,
|
||||||
|
withActions = false,
|
||||||
|
hideHeader = false,
|
||||||
|
}: PropsWithChildren<ModalProps>) {
|
||||||
|
const [animation, setAnimation] = useState<AnimationState>(
|
||||||
|
AnimationStateEnum.visible
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof isOpen === 'boolean') {
|
||||||
|
setAnimation(
|
||||||
|
isOpen ? AnimationStateEnum.visible : AnimationStateEnum.hidden
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (isOpen === undefined) {
|
||||||
|
setAnimation(AnimationStateEnum.unmounted)
|
||||||
|
}
|
||||||
|
}, [isOpen])
|
||||||
|
|
||||||
|
if (!trigger) {
|
||||||
|
return (
|
||||||
|
<InnerModal
|
||||||
|
onAnimationComplete={onAnimationComplete}
|
||||||
|
animation={animation}
|
||||||
|
setAnimation={setAnimation}
|
||||||
|
onToggle={onToggle}
|
||||||
|
isOpen={isOpen}
|
||||||
|
title={title}
|
||||||
|
subtitle={subtitle}
|
||||||
|
withActions={withActions}
|
||||||
|
hideHeader={hideHeader}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</InnerModal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogTrigger
|
||||||
|
onOpenChange={(isOpen) =>
|
||||||
|
setAnimation(
|
||||||
|
isOpen ? AnimationStateEnum.visible : AnimationStateEnum.hidden
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{trigger}
|
||||||
|
<InnerModal
|
||||||
|
onAnimationComplete={onAnimationComplete}
|
||||||
|
animation={animation}
|
||||||
|
setAnimation={setAnimation}
|
||||||
|
title={title}
|
||||||
|
subtitle={subtitle}
|
||||||
|
withActions={withActions}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</InnerModal>
|
||||||
|
</DialogTrigger>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
:root {
|
||||||
|
--max-width-navigation: 89.5rem;
|
||||||
|
|
||||||
|
--max-width-spacing: calc(var(--Space-x2) * 2);
|
||||||
|
--max-width-page: min(
|
||||||
|
calc(100dvw - var(--max-width-spacing)),
|
||||||
|
var(--max-width-navigation)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
.overlay {
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
height: var(--visual-viewport-height);
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
background-color: var(--Neutral-0);
|
||||||
|
border-radius: var(--Corner-radius-md) var(--Corner-radius-md) 0 0;
|
||||||
|
box-shadow: 0px 4px 24px 0px rgba(38, 32, 30, 0.08);
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 101;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
/* For removing focus outline when modal opens first time */
|
||||||
|
outline: 0 none;
|
||||||
|
|
||||||
|
/* for supporting animations within content */
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
max-height: 100dvh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
box-sizing: content-box;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: var(--Space-x3);
|
||||||
|
justify-content: space-between;
|
||||||
|
min-height: min-content;
|
||||||
|
position: relative;
|
||||||
|
padding: var(--Space-x3) var(--Space-x3) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
/* align-items: center; */
|
||||||
|
gap: var(--Space-x2);
|
||||||
|
overflow: auto;
|
||||||
|
padding: var(--Space-x3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
:root {
|
||||||
|
--max-width-spacing: calc(var(--Space-x3) * 2);
|
||||||
|
}
|
||||||
|
.overlay {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
left: auto;
|
||||||
|
bottom: auto;
|
||||||
|
width: auto;
|
||||||
|
border-radius: var(--Corner-radius-md);
|
||||||
|
max-width: var(--max-width-page);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog {
|
||||||
|
max-height: 90dvh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 1367px) {
|
||||||
|
:root {
|
||||||
|
--max-width-spacing: calc(var(--Space-x5) * 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import type { Dispatch, SetStateAction } from 'react'
|
||||||
|
|
||||||
|
export enum AnimationStateEnum {
|
||||||
|
unmounted = 'unmounted',
|
||||||
|
hidden = 'hidden',
|
||||||
|
visible = 'visible',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AnimationState = keyof typeof AnimationStateEnum
|
||||||
|
|
||||||
|
export type ModalProps = {
|
||||||
|
onAnimationComplete?: VoidFunction
|
||||||
|
title?: string
|
||||||
|
subtitle?: string
|
||||||
|
withActions?: boolean
|
||||||
|
hideHeader?: boolean
|
||||||
|
} & (
|
||||||
|
| { trigger: JSX.Element; isOpen?: never; onToggle?: never }
|
||||||
|
| {
|
||||||
|
trigger?: never
|
||||||
|
isOpen: boolean
|
||||||
|
onToggle: (open: boolean) => void
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export type InnerModalProps = Omit<ModalProps, 'trigger'> & {
|
||||||
|
animation: AnimationState
|
||||||
|
setAnimation: Dispatch<SetStateAction<AnimationState>>
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
export const fade = {
|
||||||
|
hidden: {
|
||||||
|
opacity: 0,
|
||||||
|
transition: { duration: 0.4, ease: 'easeInOut' },
|
||||||
|
},
|
||||||
|
visible: {
|
||||||
|
opacity: 1,
|
||||||
|
transition: { duration: 0.4, ease: 'easeInOut' },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const slideInOut = {
|
||||||
|
hidden: {
|
||||||
|
opacity: 0,
|
||||||
|
y: 32,
|
||||||
|
transition: { duration: 0.4, ease: 'easeInOut' },
|
||||||
|
},
|
||||||
|
visible: {
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
transition: { duration: 0.4, ease: 'easeInOut' },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const slideFromTop = {
|
||||||
|
hidden: {
|
||||||
|
opacity: 0,
|
||||||
|
y: -32,
|
||||||
|
transition: { duration: 0.4, ease: 'easeInOut' },
|
||||||
|
},
|
||||||
|
visible: {
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
transition: { duration: 0.4, ease: 'easeInOut' },
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -10,7 +10,6 @@ interface NoRateAvailableCardProps {
|
|||||||
paymentTerm: string
|
paymentTerm: string
|
||||||
bannerText?: string
|
bannerText?: string
|
||||||
noPricesAvailableText: string
|
noPricesAvailableText: string
|
||||||
handleTermsClick?: () => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function NoRateAvailableCard({
|
export default function NoRateAvailableCard({
|
||||||
@@ -19,7 +18,6 @@ export default function NoRateAvailableCard({
|
|||||||
paymentTerm,
|
paymentTerm,
|
||||||
bannerText,
|
bannerText,
|
||||||
noPricesAvailableText,
|
noPricesAvailableText,
|
||||||
handleTermsClick,
|
|
||||||
}: NoRateAvailableCardProps) {
|
}: NoRateAvailableCardProps) {
|
||||||
const classNames = variants({
|
const classNames = variants({
|
||||||
variant,
|
variant,
|
||||||
@@ -36,12 +34,7 @@ export default function NoRateAvailableCard({
|
|||||||
<header>
|
<header>
|
||||||
<Typography variant="Tag/sm">
|
<Typography variant="Tag/sm">
|
||||||
<h3 className={`${styles.title} ${styles.textDisabled}`}>
|
<h3 className={`${styles.title} ${styles.textDisabled}`}>
|
||||||
<Button
|
<Button variant="Icon" color="IconDefault" size="Small">
|
||||||
variant="Icon"
|
|
||||||
color="IconDefault"
|
|
||||||
size="Small"
|
|
||||||
onPress={handleTermsClick}
|
|
||||||
>
|
|
||||||
<MaterialIcon icon="info" size={20} color="CurrentColor" />
|
<MaterialIcon icon="info" size={20} color="CurrentColor" />
|
||||||
</Button>
|
</Button>
|
||||||
{`${rateTitle} / ${paymentTerm}`}
|
{`${rateTitle} / ${paymentTerm}`}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const meta: Meta<typeof PointsRateCard> = {
|
|||||||
onRateSelect: { action: 'onRateSelect' },
|
onRateSelect: { action: 'onRateSelect' },
|
||||||
isNotEnoughPoints: { control: 'boolean' },
|
isNotEnoughPoints: { control: 'boolean' },
|
||||||
notEnoughPointsText: { control: 'text' },
|
notEnoughPointsText: { control: 'text' },
|
||||||
|
rateTermDetails: { control: 'object' },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,6 +61,12 @@ export const Default: Story = {
|
|||||||
],
|
],
|
||||||
selectedRate: undefined,
|
selectedRate: undefined,
|
||||||
onRateSelect: (value) => console.log(value),
|
onRateSelect: (value) => console.log(value),
|
||||||
|
rateTermDetails: [
|
||||||
|
{
|
||||||
|
title: 'Rate definition 1',
|
||||||
|
terms: ['term 1', 'term 2', 'term 3'],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,6 +104,12 @@ export const WithDisabledRates: Story = {
|
|||||||
],
|
],
|
||||||
selectedRate: '2',
|
selectedRate: '2',
|
||||||
onRateSelect: (value) => console.log(value),
|
onRateSelect: (value) => console.log(value),
|
||||||
|
rateTermDetails: [
|
||||||
|
{
|
||||||
|
title: 'Rate definition 1',
|
||||||
|
terms: ['term 1', 'term 2', 'term 3'],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,5 +147,11 @@ export const NotEnoughPoints: Story = {
|
|||||||
isNotEnoughPoints: true,
|
isNotEnoughPoints: true,
|
||||||
notEnoughPointsText: 'Not enough points',
|
notEnoughPointsText: 'Not enough points',
|
||||||
onRateSelect: (value) => console.log(value),
|
onRateSelect: (value) => console.log(value),
|
||||||
|
rateTermDetails: [
|
||||||
|
{
|
||||||
|
title: 'Rate definition 1',
|
||||||
|
terms: ['term 1', 'term 2', 'term 3'],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Typography } from '../../Typography'
|
import { Typography } from '../../Typography'
|
||||||
import { RatePointsOption } from '../types'
|
import { RatePointsOption, RateTermDetails } from '../types'
|
||||||
|
|
||||||
import styles from '../rate-card.module.css'
|
import styles from '../rate-card.module.css'
|
||||||
import { Button } from '../../Button'
|
import { Button } from '../../Button'
|
||||||
@@ -7,6 +7,7 @@ import { Radio } from '../../Radio'
|
|||||||
import { RadioGroup } from 'react-aria-components'
|
import { RadioGroup } from 'react-aria-components'
|
||||||
import { variants } from '../variants'
|
import { variants } from '../variants'
|
||||||
import { MaterialIcon } from '../../Icons'
|
import { MaterialIcon } from '../../Icons'
|
||||||
|
import Modal from '../Modal'
|
||||||
|
|
||||||
interface PointsRateCardProps {
|
interface PointsRateCardProps {
|
||||||
rateTitle: string
|
rateTitle: string
|
||||||
@@ -17,7 +18,7 @@ interface PointsRateCardProps {
|
|||||||
onRateSelect: (value: string) => void
|
onRateSelect: (value: string) => void
|
||||||
isNotEnoughPoints?: boolean
|
isNotEnoughPoints?: boolean
|
||||||
notEnoughPointsText?: string
|
notEnoughPointsText?: string
|
||||||
handleTermsClick?: () => void
|
rateTermDetails: RateTermDetails[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PointsRateCard({
|
export default function PointsRateCard({
|
||||||
@@ -29,7 +30,7 @@ export default function PointsRateCard({
|
|||||||
isNotEnoughPoints,
|
isNotEnoughPoints,
|
||||||
notEnoughPointsText,
|
notEnoughPointsText,
|
||||||
onRateSelect,
|
onRateSelect,
|
||||||
handleTermsClick,
|
rateTermDetails,
|
||||||
}: PointsRateCardProps) {
|
}: PointsRateCardProps) {
|
||||||
const classNames = variants({
|
const classNames = variants({
|
||||||
variant: 'Points',
|
variant: 'Points',
|
||||||
@@ -44,14 +45,36 @@ export default function PointsRateCard({
|
|||||||
<header>
|
<header>
|
||||||
<Typography variant="Tag/sm">
|
<Typography variant="Tag/sm">
|
||||||
<h3 className={styles.title}>
|
<h3 className={styles.title}>
|
||||||
<Button
|
<Modal
|
||||||
variant="Icon"
|
title={rateTitle}
|
||||||
color="IconDefault"
|
subtitle={paymentTerm}
|
||||||
size="Small"
|
trigger={
|
||||||
onPress={handleTermsClick}
|
<Button variant="Icon" color="IconDefault" size="Small">
|
||||||
|
<MaterialIcon icon="info" size={20} color="CurrentColor" />
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<MaterialIcon icon="info" size={20} color="CurrentColor" />
|
{rateTermDetails.map((termGroup) => (
|
||||||
</Button>
|
<div key={termGroup.title} className={styles.terms}>
|
||||||
|
<Typography variant={'Body/Paragraph/mdBold'}>
|
||||||
|
<p>{termGroup.title}</p>
|
||||||
|
</Typography>
|
||||||
|
{termGroup.terms.map((term) => (
|
||||||
|
<Typography key={term}>
|
||||||
|
<p className={styles.term}>
|
||||||
|
<MaterialIcon
|
||||||
|
icon="check"
|
||||||
|
color="Icon/Feedback/Success"
|
||||||
|
size={20}
|
||||||
|
className={styles.termsIcon}
|
||||||
|
/>
|
||||||
|
{term}
|
||||||
|
</p>
|
||||||
|
</Typography>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Modal>
|
||||||
{rateTitle}
|
{rateTitle}
|
||||||
<span className={styles.textSecondary}>
|
<span className={styles.textSecondary}>
|
||||||
{` / ${paymentTerm}`}
|
{` / ${paymentTerm}`}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ const meta: Meta<typeof RegularRateCard> = {
|
|||||||
memberRate: { control: 'object' },
|
memberRate: { control: 'object' },
|
||||||
omnibusRate: { control: 'object' },
|
omnibusRate: { control: 'object' },
|
||||||
approximateRate: { control: 'object' },
|
approximateRate: { control: 'object' },
|
||||||
|
rateTermDetails: { control: 'object' },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,6 +52,12 @@ export const Default: Story = {
|
|||||||
price: '169',
|
price: '169',
|
||||||
unit: 'EUR',
|
unit: 'EUR',
|
||||||
},
|
},
|
||||||
|
rateTermDetails: [
|
||||||
|
{
|
||||||
|
title: 'Rate definition 1',
|
||||||
|
terms: ['term 1', 'term 2', 'term 3'],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,6 +83,12 @@ export const Selected: Story = {
|
|||||||
label: 'Approx.',
|
label: 'Approx.',
|
||||||
unit: 'EUR',
|
unit: 'EUR',
|
||||||
},
|
},
|
||||||
|
rateTermDetails: [
|
||||||
|
{
|
||||||
|
title: 'Rate definition 1',
|
||||||
|
terms: ['term 1', 'term 2', 'term 3'],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,5 +114,11 @@ export const HidePublicRate: Story = {
|
|||||||
unit: 'EUR',
|
unit: 'EUR',
|
||||||
},
|
},
|
||||||
hidePublicRate: true,
|
hidePublicRate: true,
|
||||||
|
rateTermDetails: [
|
||||||
|
{
|
||||||
|
title: 'Rate definition 1',
|
||||||
|
terms: ['term 1', 'term 2', 'term 3'],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { Rate } from '../types'
|
import { Rate, RateTermDetails } from '../types'
|
||||||
|
|
||||||
import styles from '../rate-card.module.css'
|
import styles from '../rate-card.module.css'
|
||||||
import { Typography } from '../../Typography'
|
import { Typography } from '../../Typography'
|
||||||
import { Button } from '../../Button'
|
import { Button } from '../../Button'
|
||||||
import { variants } from '../variants'
|
import { variants } from '../variants'
|
||||||
import { MaterialIcon } from '../../Icons'
|
import { MaterialIcon } from '../../Icons'
|
||||||
|
import Modal from '../Modal'
|
||||||
|
|
||||||
interface RegularRateCardProps {
|
interface RegularRateCardProps {
|
||||||
name: string
|
name: string
|
||||||
@@ -18,7 +19,7 @@ interface RegularRateCardProps {
|
|||||||
approximateRate?: Rate
|
approximateRate?: Rate
|
||||||
hidePublicRate?: boolean
|
hidePublicRate?: boolean
|
||||||
handleChange: () => void
|
handleChange: () => void
|
||||||
handleTermsClick?: () => void
|
rateTermDetails: RateTermDetails[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RegularRateCard({
|
export default function RegularRateCard({
|
||||||
@@ -33,7 +34,7 @@ export default function RegularRateCard({
|
|||||||
memberRate,
|
memberRate,
|
||||||
hidePublicRate,
|
hidePublicRate,
|
||||||
handleChange,
|
handleChange,
|
||||||
handleTermsClick,
|
rateTermDetails,
|
||||||
}: RegularRateCardProps) {
|
}: RegularRateCardProps) {
|
||||||
const classNames = variants({ variant: 'Regular' })
|
const classNames = variants({ variant: 'Regular' })
|
||||||
return (
|
return (
|
||||||
@@ -51,14 +52,40 @@ export default function RegularRateCard({
|
|||||||
<header>
|
<header>
|
||||||
<Typography variant="Tag/sm">
|
<Typography variant="Tag/sm">
|
||||||
<h3 className={styles.title}>
|
<h3 className={styles.title}>
|
||||||
<Button
|
<Modal
|
||||||
variant="Icon"
|
title={rateTitle}
|
||||||
color="IconDefault"
|
subtitle={paymentTerm}
|
||||||
size="Small"
|
trigger={
|
||||||
onPress={handleTermsClick}
|
<Button variant="Icon" color="IconDefault" size="Small">
|
||||||
|
<MaterialIcon
|
||||||
|
icon="info"
|
||||||
|
size={20}
|
||||||
|
color="CurrentColor"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<MaterialIcon icon="info" color="CurrentColor" />
|
{rateTermDetails.map((termGroup) => (
|
||||||
</Button>
|
<div key={termGroup.title} className={styles.terms}>
|
||||||
|
<Typography variant={'Body/Paragraph/mdBold'}>
|
||||||
|
<p>{termGroup.title}</p>
|
||||||
|
</Typography>
|
||||||
|
{termGroup.terms.map((term) => (
|
||||||
|
<Typography key={term}>
|
||||||
|
<p className={styles.term}>
|
||||||
|
<MaterialIcon
|
||||||
|
icon="check"
|
||||||
|
color="Icon/Feedback/Success"
|
||||||
|
size={20}
|
||||||
|
className={styles.termsIcon}
|
||||||
|
/>
|
||||||
|
{term}
|
||||||
|
</p>
|
||||||
|
</Typography>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Modal>
|
||||||
{rateTitle}
|
{rateTitle}
|
||||||
<span className={styles.textSecondary}>
|
<span className={styles.textSecondary}>
|
||||||
{` / ${paymentTerm}`}
|
{` / ${paymentTerm}`}
|
||||||
|
|||||||
@@ -134,6 +134,17 @@
|
|||||||
gap: var(--Space-x05);
|
gap: var(--Space-x05);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.terms {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--Space-x1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.term {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--Space-x1);
|
||||||
|
}
|
||||||
.variant-campaign {
|
.variant-campaign {
|
||||||
background-color: var(--Surface-Brand-Primary-1-Default);
|
background-color: var(--Surface-Brand-Primary-1-Default);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,3 +16,7 @@ type AdditionalPrice = {
|
|||||||
price: string
|
price: string
|
||||||
currency: string
|
currency: string
|
||||||
}
|
}
|
||||||
|
export type RateTermDetails = {
|
||||||
|
title: string
|
||||||
|
terms: string[]
|
||||||
|
}
|
||||||
|
|||||||
@@ -78,6 +78,7 @@
|
|||||||
"eslint-plugin-react-hooks": "^5.1.0",
|
"eslint-plugin-react-hooks": "^5.1.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.18",
|
"eslint-plugin-react-refresh": "^0.4.18",
|
||||||
"eslint-plugin-storybook": "^0.11.2",
|
"eslint-plugin-storybook": "^0.11.2",
|
||||||
|
"framer-motion": "^11.3.28",
|
||||||
"glob": "^11.0.1",
|
"glob": "^11.0.1",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"jiti": "^1",
|
"jiti": "^1",
|
||||||
|
|||||||
@@ -6200,6 +6200,7 @@ __metadata:
|
|||||||
eslint-plugin-react-hooks: "npm:^5.1.0"
|
eslint-plugin-react-hooks: "npm:^5.1.0"
|
||||||
eslint-plugin-react-refresh: "npm:^0.4.18"
|
eslint-plugin-react-refresh: "npm:^0.4.18"
|
||||||
eslint-plugin-storybook: "npm:^0.11.2"
|
eslint-plugin-storybook: "npm:^0.11.2"
|
||||||
|
framer-motion: "npm:^11.3.28"
|
||||||
glob: "npm:^11.0.1"
|
glob: "npm:^11.0.1"
|
||||||
husky: "npm:^9.1.7"
|
husky: "npm:^9.1.7"
|
||||||
jiti: "npm:^1"
|
jiti: "npm:^1"
|
||||||
|
|||||||
Reference in New Issue
Block a user