Merge pull request #404 from bigcapitalhq/optimize-ui-onboarding

feat: optimize the onboarding subscription experience.
This commit is contained in:
Ahmed Bouhuolia
2024-04-15 14:53:52 +02:00
committed by GitHub
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';

View File

@@ -54,7 +54,6 @@ function SetupLeftSectionHeader() {
<p className={'content__text'}>
<T id={'setup.left_side.description'} />
</p>
<div class="content__divider"></div>
<div className={'content__organization'}>
<span class="signout">

View File

@@ -29,10 +29,7 @@ function SetupRightSection({
}) {
return (
<section className={'setup-page__right-section'}>
<SetupWizardContent
setupStepId={setupStepId}
setupStepIndex={setupStepIndex}
/>
<SetupWizardContent stepId={setupStepId} stepIndex={setupStepIndex} />
<SetupDialogs />
</section>
);

View File

@@ -1,9 +0,0 @@
// @ts-nocheck
import React from 'react';
export default function SetupSteps({ step, children }) {
const activeStep = React.Children.toArray(children).filter(
(child) => child.props.id === step.id,
);
return activeStep;
}

View File

@@ -1,70 +0,0 @@
// @ts-nocheck
import React from 'react';
import { Formik } from 'formik';
import * as R from 'ramda';
import '@/style/pages/Setup/Subscription.scss';
import SetupSubscriptionForm from './SetupSubscription/SetupSubscriptionForm';
import { getSubscriptionFormSchema } from './SubscriptionForm.schema';
import withSubscriptionPlansActions from '../Subscriptions/withSubscriptionPlansActions';
import { useGetLemonSqueezyCheckout } from '@/hooks/query/subscriptions';
/**
* Subscription step of wizard setup.
*/
function SetupSubscription({
// #withSubscriptionPlansActions
initSubscriptionPlans,
}) {
React.useEffect(() => {
initSubscriptionPlans();
}, [initSubscriptionPlans]);
React.useEffect(() => {
window.LemonSqueezy.Setup({
eventHandler: (event) => {
// Do whatever you want with this event data
if (event.event === 'Checkout.Success') {
}
},
});
}, []);
// Initial values.
const initialValues = {
plan_slug: 'essentials',
period: 'month',
license_code: '',
};
const { mutateAsync: getLemonCheckout } = useGetLemonSqueezyCheckout();
// Handle form submit.
const handleSubmit = (values) => {
getLemonCheckout({ variantId: '337977' })
.then((res) => {
const checkoutUrl = res.data.data.attributes.url;
window.LemonSqueezy.Url.Open(checkoutUrl);
})
.catch(() => {});
};
// Retrieve momerized subscription form schema.
const SubscriptionFormSchema = React.useMemo(
() => getSubscriptionFormSchema(),
[],
);
return (
<div className={'setup-subscription-form'}>
<Formik
validationSchema={SubscriptionFormSchema}
initialValues={initialValues}
component={SetupSubscriptionForm}
onSubmit={handleSubmit}
/>
</div>
);
}
export default R.compose(withSubscriptionPlansActions)(SetupSubscription);

View File

@@ -0,0 +1,5 @@
.root{
margin: 0 auto;
padding: 0 40px;
}

View File

@@ -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);

View File

@@ -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>
);
}

View File

@@ -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>
);
};

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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);

View File

@@ -0,0 +1,3 @@
.items {
padding: 40px 40px 20px;
}

View File

@@ -1,30 +1,50 @@
// @ts-nocheck
import React from 'react';
import SetupSteps from './SetupSteps';
import WizardSetupSteps from './WizardSetupSteps';
import SetupSubscription from './SetupSubscription';
import SetupSubscription from './SetupSubscription/SetupSubscription';
import SetupOrganizationPage from './SetupOrganizationPage';
import SetupInitializingForm from './SetupInitializingForm';
import SetupCongratsPage from './SetupCongratsPage';
import { Stepper } from '@/components/Stepper';
import styles from './SetupWizardContent.module.scss';
interface SetupWizardContentProps {
stepIndex: number;
stepId: string;
}
/**
* Setup wizard content.
*/
export default function SetupWizardContent({ setupStepIndex, setupStepId }) {
export default function SetupWizardContent({
stepIndex,
stepId,
}: SetupWizardContentProps) {
return (
<div class="setup-page__content">
<WizardSetupSteps currentStep={setupStepIndex} />
<Stepper
active={stepIndex}
classNames={{
content: styles.content,
items: styles.items,
}}
>
<Stepper.Step label={'Subscription'}>
<SetupSubscription />
</Stepper.Step>
<div class="setup-page-form">
<SetupSteps step={{ id: setupStepId }}>
<SetupSubscription id="subscription" />
<Stepper.Step label={'Organization'}>
<SetupOrganizationPage id="organization" />
</Stepper.Step>
<Stepper.Step label={'Initiializing'}>
<SetupInitializingForm id={'initializing'} />
</Stepper.Step>
<Stepper.Step label={'Congrats'}>
<SetupCongratsPage id="congrats" />
</SetupSteps>
</div>
</Stepper.Step>
</Stepper>
</div>
);
}

View File

@@ -45,7 +45,7 @@ export const useOrganizationSubscriptions = (props) => {
};
/**
* Fetches the checkout url of the lemon squeezy.
* Fetches the checkout url of the Lemon Squeezy.
*/
export const useGetLemonSqueezyCheckout = (props = {}) => {
const apiRequest = useApiRequest();

View File

@@ -0,0 +1,26 @@
import * as React from 'react';
interface Props extends React.SVGProps<SVGSVGElement> {
height: number;
width: number;
}
export const CheckCircled = ({ width, height, ...props }: Props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={width}
height={height}
fill="none"
{...props}
>
<g fill="#3FA40D" fillRule="evenodd" clipPath="url(#a)" clipRule="evenodd">
<path d="M9.21 3.915a.562.562 0 0 1 0 .795L5.647 8.272a.563.563 0 0 1-.795 0L2.978 6.397a.562.562 0 0 1 .796-.795L5.25 7.08l3.165-3.165a.563.563 0 0 1 .795 0Z" />
<path d="M6 10.875A4.875 4.875 0 0 0 10.875 6 4.87 4.87 0 0 0 6 1.125a4.875 4.875 0 1 0 0 9.75ZM6 12a6 6 0 0 0 6-6c0-3.314-2.678-6-6-6a6 6 0 0 0 0 12Z" />
</g>
<defs>
<clipPath id="a">
<path fill="#fff" d="M0 0h12v12H0z" />
</clipPath>
</defs>
</svg>
);

View File

@@ -1093,13 +1093,6 @@ export const getDashboardRoutes = () => [
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
// Subscription billing.
{
path: `/billing`,
component: lazy(() => import('@/containers/Subscriptions/BillingForm')),
breadcrumb: intl.get('new_billing'),
subscriptionInactive: [SUBSCRIPTION_TYPE.MAIN],
},
// Payment modes.
{
path: `/payment-mades/import`,

View File

@@ -26,7 +26,7 @@ export default (mapState) => {
const mapped = {
...condits,
setupStepId: setupStep?.step,
setupStepIndex: scenarios.indexOf(setupStep) + 1,
setupStepIndex: scenarios.indexOf(setupStep),
};
return mapState ? mapState(mapped, state, props) : mapped;
};

View File

@@ -3,22 +3,13 @@ import { createReducer } from '@reduxjs/toolkit';
import intl from 'react-intl-universal';
import t from '@/store/types';
const getSubscriptionPeriods = () => [
{
slug: 'month',
label: intl.get('plan.monthly'),
},
{
slug: 'year',
label: intl.get('plan.yearly'),
},
];
const getSubscriptionPlans = () => [
{
name: intl.get('plan.capital_basic.title'),
slug: 'capital_basic',
description: [
description:
'Manage recurring and one-time billing, including subscriptions and invoices.',
features: [
intl.get('plan.feature.sales_invoices'),
intl.get('plan.feature.sales_estimates'),
intl.get('plan.feature.customers'),
@@ -27,25 +18,15 @@ const getSubscriptionPlans = () => [
intl.get('plan.feature.expenses_tracking'),
intl.get('plan.feature.basic_financial_reports'),
],
price: '55',
periods: [
{
slug: 'month',
label: intl.get('plan.monthly'),
price: '55',
},
{
slug: 'year',
label: intl.get('plan.yearly'),
price: '595',
},
],
currencyCode: 'LYD',
price: '$29',
pricePeriod: 'Per Year',
},
{
name: intl.get('plan.capital_plus.title'),
slug: 'capital_plus',
description: [
description:
'Manage recurring and one-time billing, including subscriptions and invoices.',
features: [
intl.get('plan.feature.all_capital_basic'),
intl.get('plan.feature.predefined_user_roles'),
intl.get('plan.feature.custom_tables_views'),
@@ -53,25 +34,16 @@ const getSubscriptionPlans = () => [
intl.get('plan.feature.plus_financial_reports'),
intl.get('plan.feature.custom_fields_resources'),
],
price: '75',
periods: [
{
slug: 'month',
label: intl.get('plan.monthly'),
price: '75',
},
{
slug: 'year',
label: intl.get('plan.yearly'),
price: '795',
},
],
currencyCode: 'LYD',
price: '$29',
pricePeriod: 'Per Year',
featured: true,
},
{
name: intl.get('plan.essential.title'),
slug: 'essentials',
description: [
description:
'Manage recurring and one-time billing, including subscriptions and invoices.',
features: [
intl.get('plan.feature.all_capital_plus'),
intl.get('plan.feature.sales_purchases_order'),
intl.get('plan.feature.purchase_invoices'),
@@ -81,47 +53,35 @@ const getSubscriptionPlans = () => [
intl.get('plan.feature.inventory_reports'),
intl.get('plan.feature.landed_cost'),
],
price: '95',
periods: [
{
slug: 'month',
label: intl.get('plan.monthly'),
price: '95',
},
{
slug: 'year',
label: intl.get('plan.yearly'),
price: '995',
},
],
currencyCode: 'LYD',
},
{
name: intl.get('plan.capital_enterprise.title'),
slug: 'enterprise',
description: [
intl.get('plan.feature.all_capital_essential'),
intl.get('plan.feature.multiply_branches'),
intl.get('plan.feature.multiply_warehouses'),
intl.get('plan.feature.accounting_dimensions'),
intl.get('plan.feature.warehouses_reports'),
intl.get('plan.feature.branches_reports'),
],
price: '120',
currencyCode: 'LYD',
periods: [
{
slug: 'month',
label: intl.get('plan.monthly'),
price: '120',
},
{
slug: 'year',
label: intl.get('plan.yearly'),
price: '1,195',
},
],
price: '$29',
pricePeriod: 'Per Year',
},
// {
// name: intl.get('plan.capital_enterprise.title'),
// slug: 'enterprise',
// description: [
// intl.get('plan.feature.all_capital_essential'),
// intl.get('plan.feature.multiply_branches'),
// intl.get('plan.feature.multiply_warehouses'),
// intl.get('plan.feature.accounting_dimensions'),
// intl.get('plan.feature.warehouses_reports'),
// intl.get('plan.feature.branches_reports'),
// ],
// price: '120',
// currencyCode: 'LYD',
// periods: [
// {
// slug: 'month',
// label: intl.get('plan.monthly'),
// price: '120',
// },
// {
// slug: 'year',
// label: intl.get('plan.yearly'),
// price: '1,195',
// },
// ],
// },
];
const initialState = {
@@ -135,9 +95,7 @@ export default createReducer(initialState, {
*/
[t.INIT_SUBSCRIPTION_PLANS]: (state) => {
const plans = getSubscriptionPlans();
const periods = getSubscriptionPeriods();
state.plans = plans;
state.periods = periods;
},
});

View File

@@ -16,7 +16,7 @@
max-width: 600px;
min-width: 600px;
@media only screen and (max-width: 1200px) {
@media only screen and (max-width: 1500px) {
min-width: 500px;
max-width: 500px;
}
@@ -54,7 +54,7 @@
top: 0;
width: 600px;
@media only screen and (max-width: 1200px) {
@media only screen and (max-width: 1500px) {
width: 500px;
}
@media only screen and (max-width: 1024px) {
@@ -99,6 +99,7 @@
&__organization {
font-size: 16px;
opacity: 0.75;
margin-top: 2.4rem;
span>a {
text-decoration: underline;
@@ -108,17 +109,8 @@
}
}
&__divider {
height: 1px;
width: 60%;
background: rgba(255, 255, 255, 0.25);
margin: 18px 0;
}
&__footer {
margin-top: auto;
border-top: 1px solid rgba(255, 255, 255, 0.25);
padding-top: 20px;
}
&__links {

View File

@@ -1,6 +1,5 @@
.setup-subscription-form{
margin: 0 auto;
padding: 0 80px;
margin-top: 40px;
padding: 0 40px;
}

View File

@@ -1,73 +0,0 @@
.billing-plans{
max-width: 753px;
.paragraph{
font-size: 15px;
}
&__section{
margin-bottom: 40px;
.title{
font-size: 20px;
font-weight: 600;
color: #6b7382;
margin-top: 0;
margin-bottom: 12px;
}
.bp4-tab-list {
border-bottom: 2px solid #e6e6e6;
width: 95%;
.bp4-tab-indicator-wrapper .bp4-tab-indicator{
bottom: -2px;
}
}
.bp4-tab-panel{
margin-top: 26px;
}
.subscribe-button {
.bp4-button {
background-color: #0063ff;
min-height: 41px;
width: 240px;
}
}
.plan-radios,
.plan-periods{
margin-top: 20px;
}
}
.license-container {
.bp4-button{
margin-top: 14px;
padding: 0 30px;
}
.form-group-license_code{
margin-top: 20px;
}
.bp4-form-content {
.bp4-input-group {
display: block;
position: relative;
}
.bp4-input {
position: relative;
width: 59%;
height: 41px;
}
}
h4 {
font-size: 18px;
font-weight: 400;
color: #444444;
}
p {
margin-top: 15px;
font-size: 14px;
}
}
}

View File

@@ -1,43 +0,0 @@
// Plan period radio component.
// ---------------------
.period-radios{
display: flex;
}
.period-radio{
display: inline-flex;
background-color: #fcfdff;
justify-content: space-between;
align-items: center;
width: 240px;
height: 36px;
border-radius: 5px;
padding: 8px 10px;
color: #000;
border: 1px solid #dcdcdc;
cursor: pointer;
text-decoration: none;
&.is-selected {
border: 1px solid #0069ff;
background-color: #fcfdff;
}
&:not(:first-child) {
margin-left: 20px;
}
&__amount{
font-weight: 600;
}
&__period{
color: #2f3863;
font-size: 14px;
font-weight: 500;
&::before {
content: '/';
display: inline-block;
margin: 0 2px;
}
}
}

View File

@@ -1,81 +0,0 @@
// Plan radio component.
// ---------------------
.plan-radios{
display: flex;
}
.plan-radio {
display: flex;
flex-direction: column;
width: 215px;
min-height: 277px;
border-radius: 5px;
padding: 15px;
border: 1px solid #dcdcdc;
background: #fcfdff;
text-decoration: none;
color: #000;
cursor: pointer;
&.is-selected {
border: 1px solid #0069ff;
background-color: #fcfdff;
}
&:not(:first-child) {
margin-left: 20px;
}
&__header {
display: flex;
justify-content: flex-start;
}
&__name {
background: #3657ff;
border-radius: 3px;
padding: 2px 10px;
font-size: 13px;
color: #fff;
margin-bottom: 18px;
height: 21px;
text-transform: uppercase;
}
&__description {
font-size: 14px;
font-weight: 400;
ul {
list-style: none;
li{
position: relative;
padding-left: 12px;
margin-bottom: 9px;
&:before{
content: '-';
position: absolute;
left: 0;
opacity: 0.6;
}
}
}
}
&__price {
margin-top: auto;
font-size: 15px;
padding-top: 10px;
}
&__amount {
font-weight: 600;
}
&__period {
font-weight: 400;
color: #2f3863;
&::before {
content: '/';
display: inline-block;
margin: 0 2px;
}
}
}