mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 14:20:31 +00:00
feat: abstract the pricing plans for setup and billing page
This commit is contained in:
@@ -8,7 +8,7 @@ export class GetSubscriptionsTransformer extends Transformer {
|
|||||||
public includeAttributes = (): string[] => {
|
public includeAttributes = (): string[] => {
|
||||||
return [
|
return [
|
||||||
'canceledAtFormatted',
|
'canceledAtFormatted',
|
||||||
'cancelsAtFormatted',
|
'endsAtFormatted',
|
||||||
'trialStartsAtFormatted',
|
'trialStartsAtFormatted',
|
||||||
'trialEndsAtFormatted',
|
'trialEndsAtFormatted',
|
||||||
'statusFormatted',
|
'statusFormatted',
|
||||||
@@ -42,13 +42,13 @@ export class GetSubscriptionsTransformer extends Transformer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the cancels at formatted.
|
* Retrieves the ends at date formatted.
|
||||||
* @param subscription
|
* @param subscription
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
public cancelsAtFormatted = (subscription) => {
|
public endsAtFormatted = (subscription) => {
|
||||||
return subscription.cancelsAt
|
return subscription.cancelsAt
|
||||||
? this.formatDate(subscription.cancelsAt)
|
? this.formatDate(subscription.endsAt)
|
||||||
: null;
|
: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
exports.up = function (knex) {
|
exports.up = function (knex) {
|
||||||
return knex.schema.table('subscription_plan_subscriptions', (table) => {
|
return knex.schema.table('subscription_plan_subscriptions', (table) => {
|
||||||
table.dateTime('trial_starts_at').nullable();
|
|
||||||
table.dateTime('trial_ends_at').nullable();
|
table.dateTime('trial_ends_at').nullable();
|
||||||
|
table.dropColumn('cancels_at');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.down = function (knex) {
|
exports.down = function (knex) {
|
||||||
return knex.schema.table('subscription_plan_subscriptions', (table) => {
|
return knex.schema.table('subscription_plan_subscriptions', (table) => {
|
||||||
table.dropColumn('trial_starts_at').nullable();
|
|
||||||
table.dropColumn('trial_ends_at').nullable();
|
table.dropColumn('trial_ends_at').nullable();
|
||||||
|
table.dateTime('cancels_at').nullable();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,16 +4,14 @@ import moment from 'moment';
|
|||||||
import SubscriptionPeriod from '@/services/Subscription/SubscriptionPeriod';
|
import SubscriptionPeriod from '@/services/Subscription/SubscriptionPeriod';
|
||||||
|
|
||||||
export default class PlanSubscription extends mixin(SystemModel) {
|
export default class PlanSubscription extends mixin(SystemModel) {
|
||||||
lemonSubscriptionId: number;
|
public lemonSubscriptionId: number;
|
||||||
|
|
||||||
canceledAt: Date;
|
public endsAt: Date;
|
||||||
cancelsAt: Date;
|
public startsAt: Date;
|
||||||
|
|
||||||
trialStartsAt: Date;
|
public canceledAt: Date;
|
||||||
trialEndsAt: Date;
|
|
||||||
|
|
||||||
endsAt: Date;
|
public trialEndsAt: Date;
|
||||||
startsAt: Date;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table name.
|
* Table name.
|
||||||
@@ -109,26 +107,15 @@ export default class PlanSubscription extends mixin(SystemModel) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the subscription is expired.
|
* Check if the subscription is active.
|
||||||
* Expired mens the user his lost the right to use the product.
|
|
||||||
* @returns {Boolean}
|
|
||||||
*/
|
|
||||||
public expired() {
|
|
||||||
return this.ended() && !this.onTrial();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if paid subscription is active.
|
|
||||||
* @return {Boolean}
|
* @return {Boolean}
|
||||||
*/
|
*/
|
||||||
public active() {
|
public active() {
|
||||||
return (
|
return this.onTrial() || !this.ended();
|
||||||
!this.canceled() && !this.onTrial() && !this.ended() && this.started()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if subscription is inactive.
|
* Check if the subscription is inactive.
|
||||||
* @return {Boolean}
|
* @return {Boolean}
|
||||||
*/
|
*/
|
||||||
public inactive() {
|
public inactive() {
|
||||||
@@ -164,11 +151,7 @@ export default class PlanSubscription extends mixin(SystemModel) {
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
public canceled() {
|
public canceled() {
|
||||||
return (
|
return !!this.canceledAt;
|
||||||
this.canceledAt ||
|
|
||||||
(this.cancelsAt && moment().isAfter(this.cancelsAt)) ||
|
|
||||||
false
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
import { Group, GroupProps } from '@/components';
|
// @ts-nocheck
|
||||||
import { SubscriptionPlan } from './SubscriptionPlan';
|
import * as R from 'ramda';
|
||||||
|
import { Intent } from '@blueprintjs/core';
|
||||||
|
import { AppToaster, Group, GroupProps } from '@/components';
|
||||||
|
import { SubscriptionPlansPeriod } from '@/store/plans/plans.reducer';
|
||||||
|
import { SubscriptionPlan } from '@/containers/Subscriptions/component/SubscriptionPlan';
|
||||||
|
import { useGetLemonSqueezyCheckout } from '@/hooks/query';
|
||||||
import { useSubscriptionPlans } from './hooks';
|
import { useSubscriptionPlans } from './hooks';
|
||||||
|
import { withPlans } from '@/containers/Subscriptions/withPlans';
|
||||||
|
import { withSubscriptionPlanMapper } from '@/containers/Subscriptions/component/withSubscriptionPlanMapper';
|
||||||
|
|
||||||
interface SubscriptionPlansProps {
|
interface SubscriptionPlansProps {
|
||||||
wrapProps?: GroupProps;
|
wrapProps?: GroupProps;
|
||||||
@@ -9,29 +16,51 @@ interface SubscriptionPlansProps {
|
|||||||
|
|
||||||
export function SubscriptionPlans({
|
export function SubscriptionPlans({
|
||||||
wrapProps,
|
wrapProps,
|
||||||
onSubscribe
|
onSubscribe,
|
||||||
}: SubscriptionPlansProps) {
|
}: SubscriptionPlansProps) {
|
||||||
const subscriptionPlans = useSubscriptionPlans();
|
const subscriptionPlans = useSubscriptionPlans();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group spacing={14} noWrap align="stretch" {...wrapProps}>
|
<Group spacing={14} noWrap align="stretch" {...wrapProps}>
|
||||||
{subscriptionPlans.map((plan, index) => (
|
{subscriptionPlans.map((plan, index) => (
|
||||||
<SubscriptionPlan
|
<SubscriptionPlanMapped key={index} plan={plan} />
|
||||||
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}
|
|
||||||
monthlyVariantId={plan.monthlyVariantId}
|
|
||||||
annuallyVariantId={plan.annuallyVariantId}
|
|
||||||
onSubscribe={onSubscribe}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SubscriptionPlanMapped = R.compose(
|
||||||
|
withSubscriptionPlanMapper,
|
||||||
|
withPlans(({ plansPeriod }) => ({ plansPeriod })),
|
||||||
|
)(({ plansPeriod, monthlyVariantId, annuallyVariantId, ...props }) => {
|
||||||
|
const { mutateAsync: getLemonCheckout, isLoading } =
|
||||||
|
useGetLemonSqueezyCheckout();
|
||||||
|
|
||||||
|
const handleSubscribeBtnClick = () => {
|
||||||
|
const variantId =
|
||||||
|
SubscriptionPlansPeriod.Monthly === plansPeriod
|
||||||
|
? monthlyVariantId
|
||||||
|
: annuallyVariantId;
|
||||||
|
|
||||||
|
getLemonCheckout({ variantId })
|
||||||
|
.then((res) => {
|
||||||
|
const checkoutUrl = res.data.data.attributes.url;
|
||||||
|
window.LemonSqueezy.Url.Open(checkoutUrl);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: 'Something went wrong!',
|
||||||
|
intent: Intent.DANGER,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<SubscriptionPlan
|
||||||
|
{...props}
|
||||||
|
onSubscribe={handleSubscribeBtnClick}
|
||||||
|
subscribeButtonProps={{
|
||||||
|
loading: isLoading,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|||||||
@@ -33,22 +33,22 @@
|
|||||||
margin-left: 6px;
|
margin-left: 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&:global(.bp4-intent-success){
|
||||||
|
color: #3e703e;
|
||||||
|
}
|
||||||
|
&:global(.bp4-intent-danger){
|
||||||
|
color: #A82A2A;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.periodStatus{
|
.periodStatus{
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: #A82A2A;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
|
||||||
.periodText{
|
|
||||||
color: #AF6161;
|
|
||||||
}
|
}
|
||||||
.priceAmount {
|
.priceAmount {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
.pricePeriod {
|
|
||||||
color: #8F99A8;
|
|
||||||
}
|
|
||||||
.subscribeButton{
|
.subscribeButton{
|
||||||
border-radius: 32px;
|
border-radius: 32px;
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
|
import clsx from 'classnames';
|
||||||
|
import { includes } from 'lodash';
|
||||||
import { Box, Group, Stack } from '@/components';
|
import { Box, Group, Stack } from '@/components';
|
||||||
import { Button, Card, Intent, Text } from '@blueprintjs/core';
|
import { Button, Card, Classes, Intent, Text } from '@blueprintjs/core';
|
||||||
import withAlertActions from '../Alert/withAlertActions';
|
import withAlertActions from '../Alert/withAlertActions';
|
||||||
import styles from './BillingSubscription.module.scss';
|
import styles from './BillingSubscription.module.scss';
|
||||||
import withDrawerActions from '../Drawer/withDrawerActions';
|
import withDrawerActions from '../Drawer/withDrawerActions';
|
||||||
import { DRAWERS } from '@/constants/drawers';
|
import { DRAWERS } from '@/constants/drawers';
|
||||||
import { useBillingPageBoot } from './BillingPageBoot';
|
import { useBillingPageBoot } from './BillingPageBoot';
|
||||||
|
import { getSubscriptionStatusText } from './_utils';
|
||||||
|
|
||||||
function SubscriptionRoot({ openAlert, openDrawer }) {
|
function SubscriptionRoot({ openAlert, openDrawer }) {
|
||||||
const { mainSubscription } = useBillingPageBoot();
|
const { mainSubscription } = useBillingPageBoot();
|
||||||
@@ -36,11 +39,24 @@ function SubscriptionRoot({ openAlert, openDrawer }) {
|
|||||||
<Stack spacing={6}>
|
<Stack spacing={6}>
|
||||||
<h1 className={styles.title}>{mainSubscription.planName}</h1>
|
<h1 className={styles.title}>{mainSubscription.planName}</h1>
|
||||||
|
|
||||||
<Group spacing={0} className={styles.period}>
|
<Group
|
||||||
|
spacing={0}
|
||||||
|
className={clsx(styles.period, {
|
||||||
|
[Classes.INTENT_DANGER]: includes(
|
||||||
|
['on_trial', 'inactive'],
|
||||||
|
mainSubscription.status,
|
||||||
|
),
|
||||||
|
[Classes.INTENT_SUCCESS]: includes(
|
||||||
|
['active', 'canceled'],
|
||||||
|
mainSubscription.status,
|
||||||
|
),
|
||||||
|
})}
|
||||||
|
>
|
||||||
<Text className={styles.periodStatus}>
|
<Text className={styles.periodStatus}>
|
||||||
{mainSubscription.statusFormatted}
|
{mainSubscription.statusFormatted}
|
||||||
</Text>
|
</Text>
|
||||||
<Text className={styles.periodText}>Trial ends in 10 days.</Text>
|
|
||||||
|
<SubscriptionStatusText subscription={mainSubscription} />
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
@@ -131,3 +147,11 @@ export const Subscription = R.compose(
|
|||||||
withAlertActions,
|
withAlertActions,
|
||||||
withDrawerActions,
|
withDrawerActions,
|
||||||
)(SubscriptionRoot);
|
)(SubscriptionRoot);
|
||||||
|
|
||||||
|
function SubscriptionStatusText({ subscription }) {
|
||||||
|
const text = getSubscriptionStatusText(subscription);
|
||||||
|
|
||||||
|
if (!text) return null;
|
||||||
|
|
||||||
|
return <Text className={styles.periodText}>{text}</Text>;
|
||||||
|
}
|
||||||
|
|||||||
17
packages/webapp/src/containers/Subscriptions/_utils.ts
Normal file
17
packages/webapp/src/containers/Subscriptions/_utils.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
export const getSubscriptionStatusText = (subscription) => {
|
||||||
|
if (subscription.status === 'on_trial') {
|
||||||
|
return subscription.onTrial
|
||||||
|
? `Trials ends in ${subscription.trialEndsAtFormatted}`
|
||||||
|
: `Trial ended ${subscription.trialEndsAtFormatted}`;
|
||||||
|
} else if (subscription.status === 'active') {
|
||||||
|
return subscription.endsAtFormatted
|
||||||
|
? `Renews in ${subscription.endsAtFormatted}`
|
||||||
|
: 'Lifetime subscription';
|
||||||
|
} else if (subscription.status === 'canceled') {
|
||||||
|
return subscription.ended
|
||||||
|
? `Expires ${subscription.endsAtFormatted}`
|
||||||
|
: `Expired ${subscription.endsAtFormatted}`;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
@@ -1,14 +1,12 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { Intent } from '@blueprintjs/core';
|
|
||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import { AppToaster } from '@/components';
|
|
||||||
import { useGetLemonSqueezyCheckout } from '@/hooks/query';
|
|
||||||
import { PricingPlan } from '@/components/PricingPlan/PricingPlan';
|
import { PricingPlan } from '@/components/PricingPlan/PricingPlan';
|
||||||
import { SubscriptionPlansPeriod } from '@/store/plans/plans.reducer';
|
import { SubscriptionPlansPeriod } from '@/store/plans/plans.reducer';
|
||||||
import {
|
import {
|
||||||
WithPlansProps,
|
WithPlansProps,
|
||||||
withPlans,
|
withPlans,
|
||||||
} from '@/containers/Subscriptions/withPlans';
|
} from '@/containers/Subscriptions/withPlans';
|
||||||
|
import { ButtonProps } from '@blueprintjs/core';
|
||||||
|
|
||||||
interface SubscriptionPricingFeature {
|
interface SubscriptionPricingFeature {
|
||||||
text: string;
|
text: string;
|
||||||
@@ -27,9 +25,8 @@ interface SubscriptionPricingProps {
|
|||||||
monthlyPriceLabel: string;
|
monthlyPriceLabel: string;
|
||||||
annuallyPrice: string;
|
annuallyPrice: string;
|
||||||
annuallyPriceLabel: string;
|
annuallyPriceLabel: string;
|
||||||
monthlyVariantId?: string;
|
|
||||||
annuallyVariantId?: string;
|
|
||||||
onSubscribe?: (variantId: number) => void;
|
onSubscribe?: (variantId: number) => void;
|
||||||
|
subscribeButtonProps?: Optional<ButtonProps>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SubscriptionPricingCombinedProps
|
interface SubscriptionPricingCombinedProps
|
||||||
@@ -45,35 +42,14 @@ function SubscriptionPlanRoot({
|
|||||||
monthlyPriceLabel,
|
monthlyPriceLabel,
|
||||||
annuallyPrice,
|
annuallyPrice,
|
||||||
annuallyPriceLabel,
|
annuallyPriceLabel,
|
||||||
monthlyVariantId,
|
|
||||||
annuallyVariantId,
|
|
||||||
onSubscribe,
|
onSubscribe,
|
||||||
|
subscribeButtonProps,
|
||||||
|
|
||||||
// #withPlans
|
// #withPlans
|
||||||
plansPeriod,
|
plansPeriod,
|
||||||
}: SubscriptionPricingCombinedProps) {
|
}: SubscriptionPricingCombinedProps) {
|
||||||
const { mutateAsync: getLemonCheckout, isLoading } =
|
|
||||||
useGetLemonSqueezyCheckout();
|
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
const variantId =
|
onSubscribe && onSubscribe();
|
||||||
SubscriptionPlansPeriod.Monthly === plansPeriod
|
|
||||||
? monthlyVariantId
|
|
||||||
: annuallyVariantId;
|
|
||||||
|
|
||||||
onSubscribe && onSubscribe(variantId);
|
|
||||||
|
|
||||||
// getLemonCheckout({ variantId })
|
|
||||||
// .then((res) => {
|
|
||||||
// const checkoutUrl = res.data.data.attributes.url;
|
|
||||||
// window.LemonSqueezy.Url.Open(checkoutUrl);
|
|
||||||
// })
|
|
||||||
// .catch(() => {
|
|
||||||
// AppToaster.show({
|
|
||||||
// message: 'Something went wrong!',
|
|
||||||
// intent: Intent.DANGER,
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -89,7 +65,7 @@ function SubscriptionPlanRoot({
|
|||||||
subPrice={annuallyPriceLabel}
|
subPrice={annuallyPriceLabel}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<PricingPlan.BuyButton loading={isLoading} onClick={handleClick}>
|
<PricingPlan.BuyButton onClick={handleClick} {...subscribeButtonProps}>
|
||||||
Subscribe
|
Subscribe
|
||||||
</PricingPlan.BuyButton>
|
</PricingPlan.BuyButton>
|
||||||
|
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
|
||||||
|
interface WithSubscriptionPlanProps {
|
||||||
|
plan: any;
|
||||||
|
onSubscribe?: (variantId: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MappedSubscriptionPlanProps {
|
||||||
|
slug: string;
|
||||||
|
label: string;
|
||||||
|
description: string;
|
||||||
|
features: any[];
|
||||||
|
featured: boolean;
|
||||||
|
monthlyPrice: string;
|
||||||
|
monthlyPriceLabel: string;
|
||||||
|
annuallyPrice: string;
|
||||||
|
annuallyPriceLabel: string;
|
||||||
|
monthlyVariantId: number;
|
||||||
|
annuallyVariantId: number;
|
||||||
|
onSubscribe?: (variantId: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const withSubscriptionPlanMapper = <
|
||||||
|
P extends MappedSubscriptionPlanProps,
|
||||||
|
>(
|
||||||
|
WrappedComponent: React.ComponentType<P>,
|
||||||
|
) => {
|
||||||
|
return function WithSubscriptionPlanMapper(
|
||||||
|
props: WithSubscriptionPlanProps &
|
||||||
|
Omit<P, keyof MappedSubscriptionPlanProps>,
|
||||||
|
) {
|
||||||
|
const { plan, onSubscribe, ...restProps } = props;
|
||||||
|
|
||||||
|
const mappedProps: MappedSubscriptionPlanProps = {
|
||||||
|
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,
|
||||||
|
monthlyVariantId: plan.monthlyVariantId,
|
||||||
|
annuallyVariantId: plan.annuallyVariantId,
|
||||||
|
onSubscribe,
|
||||||
|
};
|
||||||
|
return <WrappedComponent {...mappedProps} {...(restProps as P)} />;
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,34 +1,11 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import { Callout, Classes, Intent } from '@blueprintjs/core';
|
import { Callout, Classes } from '@blueprintjs/core';
|
||||||
import { AppToaster, Box } from '@/components';
|
import { Box } from '@/components';
|
||||||
import { SubscriptionPlans } from '@/containers/Setup/SetupSubscription/SubscriptionPlans';
|
|
||||||
import { SubscriptionPlansPeriodSwitcher } from '@/containers/Setup/SetupSubscription/SubscriptionPlansPeriodSwitcher';
|
import { SubscriptionPlansPeriodSwitcher } from '@/containers/Setup/SetupSubscription/SubscriptionPlansPeriodSwitcher';
|
||||||
import { useChangeSubscriptionPlan } from '@/hooks/query/subscription';
|
import { ChangeSubscriptionPlans } from './ChangeSubscriptionPlans';
|
||||||
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
|
|
||||||
import { DRAWERS } from '@/constants/drawers';
|
|
||||||
|
|
||||||
function ChangeSubscriptionPlanContent({ closeDrawer }) {
|
|
||||||
const { mutateAsync: changeSubscriptionPlan } = useChangeSubscriptionPlan();
|
|
||||||
|
|
||||||
// Handle the subscribe button click.
|
|
||||||
const handleSubscribe = (variantId: number) => {
|
|
||||||
changeSubscriptionPlan({ variant_id: variantId })
|
|
||||||
.then(() => {
|
|
||||||
closeDrawer(DRAWERS.CHANGE_SUBSCARIPTION_PLAN);
|
|
||||||
AppToaster.show({
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
message: 'The subscription plan has been changed successfully.',
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
AppToaster.show({
|
|
||||||
intent: Intent.DANGER,
|
|
||||||
message: 'Something went wrong.',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
export default function ChangeSubscriptionPlanContent() {
|
||||||
return (
|
return (
|
||||||
<Box className={Classes.DRAWER_BODY}>
|
<Box className={Classes.DRAWER_BODY}>
|
||||||
<Box
|
<Box
|
||||||
@@ -45,10 +22,8 @@ function ChangeSubscriptionPlanContent({ closeDrawer }) {
|
|||||||
</Callout>
|
</Callout>
|
||||||
|
|
||||||
<SubscriptionPlansPeriodSwitcher />
|
<SubscriptionPlansPeriodSwitcher />
|
||||||
<SubscriptionPlans onSubscribe={handleSubscribe} />
|
<ChangeSubscriptionPlans />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default R.compose(withDrawerActions)(ChangeSubscriptionPlanContent);
|
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import { Intent } from '@blueprintjs/core';
|
||||||
|
import { AppToaster, Group } from '@/components';
|
||||||
|
import { SubscriptionPlan } from '../../component/SubscriptionPlan';
|
||||||
|
import { SubscriptionPlansPeriod } from '@/store/plans/plans.reducer';
|
||||||
|
import { useSubscriptionPlans } from '@/hooks/constants/useSubscriptionPlans';
|
||||||
|
import { useChangeSubscriptionPlan } from '@/hooks/query/subscription';
|
||||||
|
import { withSubscriptionPlanMapper } from '../../component/withSubscriptionPlanMapper';
|
||||||
|
import { withPlans } from '../../withPlans';
|
||||||
|
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
|
||||||
|
import { DRAWERS } from '@/constants/drawers';
|
||||||
|
|
||||||
|
export function ChangeSubscriptionPlans() {
|
||||||
|
const subscriptionPlans = useSubscriptionPlans();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group spacing={14} noWrap align="stretch">
|
||||||
|
{subscriptionPlans.map((plan, index) => (
|
||||||
|
<SubscriptionPlanMapped plan={plan} />
|
||||||
|
))}
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SubscriptionPlanMapped = R.compose(
|
||||||
|
withSubscriptionPlanMapper,
|
||||||
|
withDrawerActions,
|
||||||
|
withPlans(({ plansPeriod }) => ({ plansPeriod })),
|
||||||
|
)(
|
||||||
|
({
|
||||||
|
openDrawer,
|
||||||
|
closeDrawer,
|
||||||
|
monthlyVariantId,
|
||||||
|
annuallyVariantId,
|
||||||
|
plansPeriod,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const { mutateAsync: changeSubscriptionPlan, isLoading } =
|
||||||
|
useChangeSubscriptionPlan();
|
||||||
|
|
||||||
|
// Handles the subscribe button click.
|
||||||
|
const handleSubscribe = () => {
|
||||||
|
const variantId =
|
||||||
|
plansPeriod === SubscriptionPlansPeriod.Monthly
|
||||||
|
? monthlyVariantId
|
||||||
|
: annuallyVariantId;
|
||||||
|
|
||||||
|
changeSubscriptionPlan({ variant_id: variantId })
|
||||||
|
.then(() => {
|
||||||
|
closeDrawer(DRAWERS.CHANGE_SUBSCARIPTION_PLAN);
|
||||||
|
AppToaster.show({
|
||||||
|
message: 'The subscription plan has been changed.',
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: 'Something went wrong.',
|
||||||
|
intent: Intent.DANGER,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<SubscriptionPlan
|
||||||
|
{...props}
|
||||||
|
onSubscribe={handleSubscribe}
|
||||||
|
subscribeButtonProps={{ loading: isLoading }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { SubscriptionPlans } from '@/constants/subscriptionModels';
|
||||||
|
|
||||||
|
export const useSubscriptionPlans = () => {
|
||||||
|
return SubscriptionPlans;
|
||||||
|
};
|
||||||
@@ -92,7 +92,7 @@ export function useResumeMainSubscription(
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ChangeMainSubscriptionPlanValues {
|
interface ChangeMainSubscriptionPlanValues {
|
||||||
variantId: string;
|
variant_id: string;
|
||||||
}
|
}
|
||||||
interface ChangeMainSubscriptionPlanResponse {}
|
interface ChangeMainSubscriptionPlanResponse {}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user