mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-05-31 23:19:02 +00:00
feat: optimize the onboarding subscription experience.
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
|
||||
.root{
|
||||
margin: 0 auto;
|
||||
padding: 0 40px;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// @ts-nocheck
|
||||
import { useEffect } from 'react';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import { Box } from '@/components';
|
||||
import { SubscriptionPlansSection } from './SubscriptionPlansSection';
|
||||
import withSubscriptionPlansActions from '../../Subscriptions/withSubscriptionPlansActions';
|
||||
import styles from './SetupSubscription.module.scss';
|
||||
|
||||
/**
|
||||
* Subscription step of wizard setup.
|
||||
*/
|
||||
function SetupSubscription({
|
||||
// #withSubscriptionPlansActions
|
||||
initSubscriptionPlans,
|
||||
}) {
|
||||
useEffect(() => {
|
||||
initSubscriptionPlans();
|
||||
}, [initSubscriptionPlans]);
|
||||
|
||||
useEffect(() => {
|
||||
window.LemonSqueezy.Setup({
|
||||
eventHandler: (event) => {
|
||||
// Do whatever you want with this event data
|
||||
if (event.event === 'Checkout.Success') {
|
||||
}
|
||||
},
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box className={styles.root}>
|
||||
<SubscriptionPlansSection />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default R.compose(withSubscriptionPlansActions)(SetupSubscription);
|
||||
@@ -1,28 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import { Form } from 'formik';
|
||||
import SubscriptionPlansSection from './SubscriptionPlansSection';
|
||||
import SubscriptionPeriodsSection from './SubscriptionPeriodsSection';
|
||||
import { Button, Intent } from '@blueprintjs/core';
|
||||
import { T } from '@/components';
|
||||
|
||||
function StepSubscriptionActions() {
|
||||
return (
|
||||
<div>
|
||||
<Button type="submit" intent={Intent.PRIMARY} large={true}>
|
||||
<T id={'submit_voucher'} />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function SetupSubscriptionForm() {
|
||||
return (
|
||||
<Form>
|
||||
<div class="billing-plans">
|
||||
<SubscriptionPlansSection />
|
||||
<SubscriptionPeriodsSection />
|
||||
<StepSubscriptionActions />
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { T } from '@/components';
|
||||
|
||||
import { PaymentMethodTabs } from '../../Subscriptions/SubscriptionTabs';
|
||||
|
||||
export default ({ formik, title, description }) => {
|
||||
return (
|
||||
<section class="billing-plans__section">
|
||||
<h1 className="title">
|
||||
<T id={'setup.plans.payment_methods.title'} />
|
||||
</h1>
|
||||
<p className="paragraph">
|
||||
<T id={'setup.plans.payment_methods.description'} />
|
||||
</p>
|
||||
|
||||
<PaymentMethodTabs formik={formik} />
|
||||
</section>
|
||||
);
|
||||
};
|
||||
@@ -1,48 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Field } from 'formik';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import { T, SubscriptionPeriods } from '@/components';
|
||||
|
||||
import withPlan from '../../Subscriptions/withPlan';
|
||||
|
||||
const SubscriptionPeriodsEnhanced = R.compose(
|
||||
withPlan(({ plan }) => ({ plan })),
|
||||
)(({ plan, ...restProps }) => {
|
||||
// Can't continue if the current plan of the form not selected.
|
||||
if (!plan) {
|
||||
return null;
|
||||
}
|
||||
return <SubscriptionPeriods periods={plan.periods} {...restProps} />;
|
||||
});
|
||||
|
||||
/**
|
||||
* Billing periods.
|
||||
*/
|
||||
export default function SubscriptionPeriodsSection() {
|
||||
return (
|
||||
<section class="billing-plans__section">
|
||||
<h1 class="title">
|
||||
<T id={'setup.plans.select_period.title'} />
|
||||
</h1>
|
||||
<div class="description">
|
||||
<p className="paragraph">
|
||||
<T id={'setup.plans.select_period.description'} />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Field name={'period'}>
|
||||
{({ form: { setFieldValue, values }, field: { value } }) => (
|
||||
<SubscriptionPeriodsEnhanced
|
||||
planSlug={values.plan_slug}
|
||||
selectedPeriod={value}
|
||||
onPeriodSelect={(period) => {
|
||||
setFieldValue('period', period);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// @ts-nocheck
|
||||
import { AppToaster, Group, T } from '@/components';
|
||||
import { useGetLemonSqueezyCheckout } from '@/hooks/query';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import { PricingPlan } from '@/components/PricingPlan/PricingPlan';
|
||||
|
||||
interface SubscriptionPricingProps {
|
||||
slug: string;
|
||||
label: string;
|
||||
description: string;
|
||||
features?: Array<String>;
|
||||
featured?: boolean;
|
||||
price: string;
|
||||
pricePeriod: string;
|
||||
}
|
||||
|
||||
function SubscriptionPricing({
|
||||
featured,
|
||||
label,
|
||||
description,
|
||||
features,
|
||||
price,
|
||||
pricePeriod,
|
||||
}: SubscriptionPricingProps) {
|
||||
const { mutateAsync: getLemonCheckout, isLoading } =
|
||||
useGetLemonSqueezyCheckout();
|
||||
|
||||
const handleClick = () => {
|
||||
getLemonCheckout({ variantId: '337977' })
|
||||
.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 (
|
||||
<PricingPlan featured={featured}>
|
||||
{featured && <PricingPlan.Featured>Most Popular</PricingPlan.Featured>}
|
||||
|
||||
<PricingPlan.Header label={label} description={description} />
|
||||
<PricingPlan.Price price={price} subPrice={pricePeriod} />
|
||||
<PricingPlan.BuyButton loading={isLoading} onClick={handleClick}>
|
||||
Subscribe
|
||||
</PricingPlan.BuyButton>
|
||||
|
||||
<PricingPlan.Features>
|
||||
{features?.map((feature) => (
|
||||
<PricingPlan.FeatureLine>{feature}</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>
|
||||
);
|
||||
}
|
||||
@@ -1,42 +1,25 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Field } from 'formik';
|
||||
import { T } from '@/components';
|
||||
|
||||
import { T, SubscriptionPlans } from '@/components';
|
||||
|
||||
import { compose } from '@/utils';
|
||||
import { SubscriptionPlans } from './SubscriptionPlan';
|
||||
import withPlans from '../../Subscriptions/withPlans';
|
||||
import { compose } from '@/utils';
|
||||
|
||||
/**
|
||||
* Billing plans.
|
||||
*/
|
||||
function SubscriptionPlansSection({ plans }) {
|
||||
function SubscriptionPlansSectionRoot({ plans }) {
|
||||
return (
|
||||
<section class="billing-plans__section">
|
||||
<h1 class="title">
|
||||
<T id={'setup.plans.select_plan.title'} />
|
||||
</h1>
|
||||
<div class="description">
|
||||
<p className="paragraph">
|
||||
<T id={'setup.plans.select_plan.description'} />
|
||||
</p>
|
||||
</div>
|
||||
<section>
|
||||
<p className="paragraph" style={{ marginBottom: '1.2rem' }}>
|
||||
<T id={'setup.plans.select_plan.description'} />
|
||||
</p>
|
||||
|
||||
<Field name={'plan_slug'}>
|
||||
{({ form: { setFieldValue }, field: { value } }) => (
|
||||
<SubscriptionPlans
|
||||
value={value}
|
||||
plans={plans}
|
||||
onSelect={(value) => {
|
||||
setFieldValue('plan_slug', value);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
<SubscriptionPlans plans={plans} />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withPlans(({ plans }) => ({ plans })))(
|
||||
SubscriptionPlansSection,
|
||||
);
|
||||
export const SubscriptionPlansSection = compose(
|
||||
withPlans(({ plans }) => ({ plans })),
|
||||
)(SubscriptionPlansSectionRoot);
|
||||
|
||||
Reference in New Issue
Block a user