feat: upgrade the subscription plans

This commit is contained in:
Ahmed Bouhuolia
2024-07-13 18:19:18 +02:00
parent 81b26c6f13
commit eb3f23554f
14 changed files with 397 additions and 156 deletions

View File

@@ -3,3 +3,7 @@
margin: 0 auto;
padding: 0 40px;
}
.periodSwitch {
margin: 0;
}

View File

@@ -1,27 +1,51 @@
// @ts-nocheck
import { AppToaster, Group, T } from '@/components';
import { useGetLemonSqueezyCheckout } from '@/hooks/query';
import { Intent } from '@blueprintjs/core';
import * as R from 'ramda';
import { AppToaster } from '@/components';
import { useGetLemonSqueezyCheckout } from '@/hooks/query';
import { PricingPlan } from '@/components/PricingPlan/PricingPlan';
import { SubscriptionPlansPeriod } from '@/store/plans/plans.reducer';
import {
WithPlansProps,
withPlans,
} from '@/containers/Subscriptions/withPlans';
interface SubscriptionPricingFeature {
text: string;
hint?: string;
hintLabel?: string;
style?: Record<string, string>;
}
interface SubscriptionPricingProps {
slug: string;
label: string;
description: string;
features?: Array<String>;
features?: Array<SubscriptionPricingFeature>;
featured?: boolean;
price: string;
pricePeriod: string;
monthlyPrice: string;
monthlyPriceLabel: string;
annuallyPrice: string;
annuallyPriceLabel: string;
}
function SubscriptionPricing({
featured,
interface SubscriptionPricingCombinedProps
extends SubscriptionPricingProps,
WithPlansProps {}
function SubscriptionPlanRoot({
label,
description,
featured,
features,
price,
pricePeriod,
}: SubscriptionPricingProps) {
monthlyPrice,
monthlyPriceLabel,
annuallyPrice,
annuallyPriceLabel,
// #withPlans
plansPeriod,
}: SubscriptionPricingCombinedProps) {
const { mutateAsync: getLemonCheckout, isLoading } =
useGetLemonSqueezyCheckout();
@@ -42,37 +66,34 @@ function SubscriptionPricing({
return (
<PricingPlan featured={featured}>
{featured && <PricingPlan.Featured>Most Popular</PricingPlan.Featured>}
<PricingPlan.Header label={label} description={description} />
<PricingPlan.Price price={price} subPrice={pricePeriod} />
{plansPeriod === SubscriptionPlansPeriod.Monthly ? (
<PricingPlan.Price price={monthlyPrice} subPrice={monthlyPriceLabel} />
) : (
<PricingPlan.Price
price={annuallyPrice}
subPrice={annuallyPriceLabel}
/>
)}
<PricingPlan.BuyButton loading={isLoading} onClick={handleClick}>
Subscribe
</PricingPlan.BuyButton>
<PricingPlan.Features>
{features?.map((feature) => (
<PricingPlan.FeatureLine>{feature}</PricingPlan.FeatureLine>
<PricingPlan.FeatureLine
hintLabel={feature.hintLabel}
hintContent={feature.hint}
>
{feature.text}
</PricingPlan.FeatureLine>
))}
</PricingPlan.Features>
</PricingPlan>
);
}
export function SubscriptionPlans({ plans }) {
return (
<Group spacing={18} noWrap align='stretch'>
{plans.map((plan, index) => (
<SubscriptionPricing
key={index}
slug={plan.slug}
label={plan.name}
description={plan.description}
features={plan.features}
featured={plan.featured}
price={plan.price}
pricePeriod={plan.pricePeriod}
/>
))}
</Group>
);
}
export const SubscriptionPlan = R.compose(
withPlans(({ plansPeriod }) => ({ plansPeriod })),
)(SubscriptionPlanRoot);

View File

@@ -0,0 +1,26 @@
import { Group } from '@/components';
import { SubscriptionPlan } from './SubscriptionPlan';
import { useSubscriptionPlans } from './hooks';
export function SubscriptionPlans() {
const subscriptionPlans = useSubscriptionPlans();
return (
<Group spacing={14} noWrap align="stretch">
{subscriptionPlans.map((plan, index) => (
<SubscriptionPlan
key={index}
slug={plan.slug}
label={plan.name}
description={plan.description}
features={plan.features}
featured={plan.featured}
monthlyPrice={plan.monthlyPrice}
monthlyPriceLabel={plan.monthlyPriceLabel}
annuallyPrice={plan.annuallyPrice}
annuallyPriceLabel={plan.annuallyPriceLabel}
/>
))}
</Group>
);
}

View File

@@ -0,0 +1,46 @@
import { ChangeEvent } from 'react';
import * as R from 'ramda';
import { Intent, Switch, Tag, Text } from '@blueprintjs/core';
import { Group } from '@/components';
import withSubscriptionPlansActions, {
WithSubscriptionPlansActionsProps,
} from '@/containers/Subscriptions/withSubscriptionPlansActions';
import { SubscriptionPlansPeriod } from '@/store/plans/plans.reducer';
import styles from './SetupSubscription.module.scss';
interface SubscriptionPlansPeriodsSwitchCombinedProps
extends WithSubscriptionPlansActionsProps {}
function SubscriptionPlansPeriodSwitcherRoot({
// #withSubscriptionPlansActions
changeSubscriptionPlansPeriod,
}: SubscriptionPlansPeriodsSwitchCombinedProps) {
// Handles the period switch change.
const handleSwitchChange = (event: ChangeEvent<HTMLInputElement>) => {
changeSubscriptionPlansPeriod(
event.currentTarget.checked
? SubscriptionPlansPeriod.Annually
: SubscriptionPlansPeriod.Monthly,
);
};
return (
<Group position={'center'} spacing={10} style={{ marginBottom: '1.2rem' }}>
<Text>Pay Monthly</Text>
<Switch
large
onChange={handleSwitchChange}
className={styles.periodSwitch}
/>
<Text>
Pay Yearly{' '}
<Tag minimal intent={Intent.NONE}>
25% Off All Year
</Tag>
</Text>
</Group>
);
}
export const SubscriptionPlansPeriodSwitcher = R.compose(
withSubscriptionPlansActions,
)(SubscriptionPlansPeriodSwitcherRoot);

View File

@@ -1,29 +1,21 @@
// @ts-nocheck
import { Callout } from '@blueprintjs/core';
import { SubscriptionPlans } from './SubscriptionPlan';
import withPlans from '../../Subscriptions/withPlans';
import { compose } from '@/utils';
import { SubscriptionPlans } from './SubscriptionPlans';
import { SubscriptionPlansPeriodSwitcher } from './SubscriptionPlansPeriodSwitcher';
/**
* Billing plans.
*/
function SubscriptionPlansSectionRoot({ plans }) {
export function SubscriptionPlansSection() {
return (
<section>
<Callout
style={{ marginBottom: '1.5rem' }}
icon={null}
title={'Early Adopter Plan'}
>
We're looking for 200 early adopters, when you subscribe you'll get the
full features and unlimited users for a year regardless of the
subscribed plan.
<Callout style={{ marginBottom: '2rem' }} icon={null}>
Simple plans. Simple prices. Only pay for what you really need. All
plans come with award-winning 24/7 customer support. Prices do not
include applicable taxes.
</Callout>
<SubscriptionPlans plans={plans} />
<SubscriptionPlansPeriodSwitcher />
<SubscriptionPlans />
</section>
);
}
export const SubscriptionPlansSection = compose(
withPlans(({ plans }) => ({ plans })),
)(SubscriptionPlansSectionRoot);

View File

@@ -0,0 +1,5 @@
import { SubscriptionPlans } from '@/constants/subscriptionModels';
export const useSubscriptionPlans = () => {
return SubscriptionPlans;
};

View File

@@ -1,17 +1,35 @@
// @ts-nocheck
import { connect } from 'react-redux';
import { MapStateToProps, connect } from 'react-redux';
import {
getPlansPeriodSelector,
getPlansSelector,
} from '@/store/plans/plans.selectors';
import { ApplicationState } from '@/store/reducers';
export default (mapState) => {
const mapStateToProps = (state, props) => {
export interface WithPlansProps {
plans: ReturnType<ReturnType<typeof getPlansSelector>>;
plansPeriod: ReturnType<ReturnType<typeof getPlansPeriodSelector>>;
}
type MapState<Props> = (
mapped: WithPlansProps,
state: ApplicationState,
props: Props,
) => any;
export function withPlans<Props>(mapState?: MapState<Props>) {
const mapStateToProps: MapStateToProps<
WithPlansProps,
Props,
ApplicationState
> = (state, props) => {
const getPlans = getPlansSelector();
const getPlansPeriod = getPlansPeriodSelector();
const mapped = {
plans: getPlans(state, props),
plans: getPlans(state),
plansPeriod: getPlansPeriod(state),
};
return mapState ? mapState(mapped, state, props) : mapped;
};
return connect(mapStateToProps);
};
}

View File

@@ -1,9 +1,22 @@
// @ts-nocheck
import { connect } from 'react-redux';
import { initSubscriptionPlans } from '@/store/plans/plans.actions';
import { MapDispatchToProps, connect } from 'react-redux';
import {
SubscriptionPlansPeriod,
changePlansPeriod,
initSubscriptionPlans,
} from '@/store/plans/plans.reducer';
export const mapDispatchToProps = (dispatch) => ({
export interface WithSubscriptionPlansActionsProps {
initSubscriptionPlans: () => void;
changeSubscriptionPlansPeriod: (period: SubscriptionPlansPeriod) => void;
}
export const mapDispatchToProps: MapDispatchToProps<
WithSubscriptionPlansActionsProps,
{}
> = (dispatch: any) => ({
initSubscriptionPlans: () => dispatch(initSubscriptionPlans()),
changeSubscriptionPlansPeriod: (period: SubscriptionPlansPeriod) =>
dispatch(changePlansPeriod({ period })),
});
export default connect(null, mapDispatchToProps);
export default connect(null, mapDispatchToProps);