feat: optimize the onboarding subscription experience.

This commit is contained in:
Ahmed Bouhuolia
2024-04-15 12:48:16 +02:00
parent 9321db2a3a
commit 47d82ce591
28 changed files with 426 additions and 661 deletions

View File

@@ -48,7 +48,7 @@ const GroupStyled = styled(Box)`
box-sizing: border-box;
display: flex;
flex-direction: row;
align-items: center;
align-items: ${(props: GroupProps) => (props.align || 'center')};
flex-wrap: ${(props: GroupProps) => (props.noWrap ? 'nowrap' : 'wrap')};
justify-content: ${(props: GroupProps) =>
GROUP_POSITIONS[props.position || 'left']};

View File

@@ -0,0 +1,58 @@
.root{
border-radius: 5px;
padding: 40px 15px;
position: relative;
border: 1px solid #D8DEE4;
padding-top: 45px;
&.isFeatured {
background-color: #F5F6F8;
border: 0;
}
}
.featuredBox {
background-color: #A3ACBA;
height: 30px;
line-height: 30px;
position: absolute;
top: 0;
left: 0;
right: 0;
border-radius: 5px 5px 0 0;
color: #fff;
text-align: center;
font-size: 12px;
}
.label {
font-size: 14px;
font-weight: 600;
color: #2F343C;
}
.description{
font-size: 14px;
color: #687385;
line-height: 1.5;
}
.buttonCTA {
min-height: 34px;
border-radius: 5px;
}
.features {
margin-top: 1rem;
}
.priceRoot{
padding-bottom: 10px;
}
.price {
font-size: 18px;
line-height: 1;
font-weight: 500;
color: #404854;
}
.pricePer{
color: #738091;
font-size: 12px;
line-height: 1;
}

View File

@@ -0,0 +1,125 @@
import { Button, ButtonProps, Intent } from '@blueprintjs/core';
import clsx from 'classnames';
import { Box, Group, Stack } from '../Layout';
import styles from './PricingPlan.module.scss';
import { CheckCircled } from '@/icons/CheckCircled';
export interface PricingPlanProps {
featured?: boolean;
children: React.ReactNode;
}
/**
* Displays a pricing plan.
* @param featured - Whether the plan is featured.
* @param children - The content of the plan.
*/
export const PricingPlan = ({ featured, children }: PricingPlanProps) => {
return (
<Stack
spacing={8}
className={clsx(styles.root, { [styles.isFeatured]: featured })}
>
<>{children}</>
</Stack>
);
};
/**
* Displays a featured section within a pricing plan.
* @param children - The content of the featured section.
*/
PricingPlan.Featured = ({ children }: { children: React.ReactNode }) => {
return <Box className={styles.featuredBox}>{children}</Box>;
};
export interface PricingHeaderProps {
label: string;
description: string;
}
/**
* Displays the header of a pricing plan.
* @param label - The label of the plan.
* @param description - The description of the plan.
*/
PricingPlan.Header = ({ label, description }: PricingHeaderProps) => {
return (
<Stack spacing={4}>
<h4 className={styles.label}>{label}</h4>
{description && <p className={styles.description}>{description}</p>}
</Stack>
);
};
export interface PricingPriceProps {
price: string;
subPrice: string;
}
/**
* Displays the price of a pricing plan.
* @param price - The main price of the plan.
* @param subPrice - The sub-price of the plan.
*/
PricingPlan.Price = ({ price, subPrice }: PricingPriceProps) => {
return (
<Stack spacing={6} className={styles.priceRoot}>
<h4 className={styles.price}>{price}</h4>
<span className={styles.pricePer}>{subPrice}</span>
</Stack>
);
};
export interface PricingBuyButtonProps extends ButtonProps {}
/**
* Displays a buy button within a pricing plan.
* @param children - The content of the button.
* @param props - Additional button props.
*/
PricingPlan.BuyButton = ({ children, ...props }: PricingBuyButtonProps) => {
return (
<Button
intent={Intent.PRIMARY}
{...props}
fill={true}
className={styles.buttonCTA}
>
{children}
</Button>
);
};
export interface PricingFeaturesProps {
children: React.ReactNode;
}
/**
* Displays a list of features within a pricing plan.
* @param children - The list of features.
*/
PricingPlan.Features = ({ children }: PricingFeaturesProps) => {
return (
<Stack spacing={10} className={styles.features}>
{children}
</Stack>
);
};
export interface PricingFeatureLineProps {
children: React.ReactNode;
}
/**
* Displays a single feature line within a list of features.
* @param children - The content of the feature line.
*/
PricingPlan.FeatureLine = ({ children }: PricingFeatureLineProps) => {
return (
<Group noWrap spacing={12}>
<CheckCircled height={12} width={12} />
<Box className={styles.featureItem}>{children}</Box>
</Group>
);
};

View File

@@ -1,136 +0,0 @@
// @ts-nocheck
import React from 'react';
import classNames from 'classnames';
import { T } from '@/components';
import { saveInvoke } from '@/utils';
import '@/style/pages/Subscription/PlanRadio.scss';
import '@/style/pages/Subscription/PlanPeriodRadio.scss';
export function SubscriptionPlans({ value, plans, onSelect }) {
const handleSelect = (value) => {
onSelect && onSelect(value);
};
return (
<div className={'plan-radios'}>
{plans.map((plan) => (
<SubscriptionPlan
name={plan.name}
description={plan.description}
slug={plan.slug}
price={plan.price}
currencyCode={plan.currencyCode}
value={plan.slug}
onSelected={handleSelect}
selectedOption={value}
/>
))}
</div>
);
}
export function SubscriptionPlan({
name,
description,
price,
currencyCode,
value,
selectedOption,
onSelected,
}) {
const handlePlanClick = () => {
saveInvoke(onSelected, value);
};
return (
<div
id={'basic-plan'}
className={classNames('plan-radio', {
'is-selected': selectedOption === value,
})}
onClick={handlePlanClick}
>
<div className={'plan-radio__header'}>
<div className={'plan-radio__name'}>{name}</div>
</div>
<div className={'plan-radio__description'}>
<ul>
{description.map((line) => (
<li>{line}</li>
))}
</ul>
</div>
<div className={'plan-radio__price'}>
<span className={'plan-radio__amount'}>
{price} {currencyCode}
</span>
<span className={'plan-radio__period'}>
<T id={'monthly'} />
</span>
</div>
</div>
);
}
/**
* Subscription periods.
*/
export function SubscriptionPeriods({ periods, selectedPeriod, onPeriodSelect }) {
const handleSelected = (value) => {
saveInvoke(onPeriodSelect, value);
};
return (
<div className={'plan-periods'}>
{periods.map((period) => (
<SubscriptionPeriod
period={period.slug}
label={period.label}
onSelected={handleSelected}
price={period.price}
selectedPeriod={selectedPeriod}
/>
))}
</div>
);
}
/**
* Billing period.
*/
export function SubscriptionPeriod({
// #ownProps
label,
selectedPeriod,
onSelected,
period,
price,
currencyCode,
}) {
const handlePeriodClick = () => {
saveInvoke(onSelected, period);
};
return (
<div
id={`plan-period-${period}`}
className={classNames(
{ 'is-selected': period === selectedPeriod },
'period-radio',
)}
onClick={handlePeriodClick}
>
<span className={'period-radio__label'}>{label}</span>
<div className={'period-radio__price'}>
<span className={'period-radio__amount'}>
{price} {currencyCode}
</span>
<span className={'period-radio__period'}>{label}</span>
</div>
</div>
);
}

View File

@@ -13,7 +13,6 @@ export * from './PdfPreview';
export * from './Details';
export * from './TotalLines/index';
export * from './Alert';
export * from './Subscriptions';
export * from './Dashboard';
export * from './Drawer';
export * from './Forms';